Terraform Associate study materials
Terraform Associate study resources

Fourteen Minute Plans and a Forty Terabyte Import

Terraform Associate · Put your knowledge to work in context.

All case studies

Fourteen Minute Plans and a Forty Terabyte Import

A team manages roughly 900 resources across three root modules sharing one S3 backend, on Terraform 1.6 with hashicorp/aws pinned at ~> 4.0. A full plan against prod takes fourteen minutes, so engineers have started running terraform plan -target=module.app -refresh=false before every PR, and one engineer has applied with -target twice this month. The provider's current major is 5.x and plans print a dozen deprecation warnings. Separately, a bucket created by hand for a data dump - 40 TB inside - must be brought under management, and an EKS-managed tag shows up as a diff on three node groups in every single plan.

1. Assess the `-target` plus `-refresh=false` habit. What can it hide, and what would you do instead?

Read the worked answer

-target produces a partial plan, and applying it writes partial state. Terraform evaluates only the targeted resources and their dependencies; anything outside that set is not read, not updated, and not reconciled. Repeated targeted applies accumulate a state file that no full plan has validated, which is exactly how you end up with the resources-that-exist-but-are-not-tracked problem later. It is documented as an escape hatch for recovery, not a planning mode.

-refresh=false plans against stale state. Drift is invisible, and worse, a destroy decision may be made from data that no longer reflects reality. Applying it can produce a plan that is wrong in the expensive direction.

Instead:

  • Split state by blast radius and lifecycle - network, data, app - so a PR touches a plan that finishes in two or three minutes.
  • Run the full plan in CI on every PR and post it as a comment; nobody should be waiting at a terminal.
  • Save plans (-out) and use them for apply so the reviewed artefact is the applied artefact.
  • If refresh really is the bottleneck, consider -refresh-only runs on a schedule plus -parallelism tuning, and measure before assuming.

Fix the plan latency; do not institutionalise the workaround.

2. Walk through importing the 40 TB bucket without risking the data. What does import actually do, and where do people go wrong?

Read the worked answer

Import only writes state. It reads the real object and records it at an address; it does not change the object, and it does not generate correct configuration by itself.

Sequence:

  1. Write the resource block first, matching reality as closely as you can - region, versioning, encryption, lifecycle rules, public access block, tags, and critically force_destroy = false.
  2. Use terraform plan -generate-config-out=generated.tf (1.5+) with an import block to draft the configuration, then review it by hand. Generated config is a starting point, not an answer.
  3. Add lifecycle { prevent_destroy = true } for the transition period.
  4. Apply the import - a no-op change beyond adding to state.
  5. Run a full plan and read it line by line. Any attribute the config omits that is non-default will appear as a proposed change, and a few of them are destructive (ACLs, policy replacement, lifecycle rule replacement).
  6. Only when the plan is clean, and after confirming versioning is on and there is an independent copy, remove the training wheels.

Where people go wrong: applying the import and a large config rewrite in the same change, and assuming that because import is safe, the next apply is too.

3. The EKS-managed tag on three node groups shows a diff on every plan. How do you stop the noise without losing drift detection?

Read the worked answer

The tag is being written by the EKS control plane after Terraform applies, so the next refresh reads a value the config does not declare. Terraform then proposes removing it, AWS re-adds it, and the cycle repeats.

Options, in order of preference:

  1. Ignore only that key, not the whole map: lifecycle { ignore_changes = [tags["eks:nodegroup-name"]] }. You keep drift detection for every tag you actually own, including the ones your tagging policy depends on.
  2. Declare the tag. If the value is deterministic, put it in the config and stop fighting.
  3. Manage tags outside Terraform for resources the platform owns, and document which system is authoritative - acceptable, but only when written down, otherwise the next engineer re-adds the tag in code and the loop returns.

What not to do: ignore_changes = [tags] or ignore_changes = all. Both feel tidy and both remove your ability to detect real tag drift, which is often the earliest signal that something changed outside Terraform. Noise suppression should be surgical.

This is also a signal worth following: if three node groups are being mutated post-apply, find out what else is mutating resources, because the next difference may not be a tag.