State Manipulation and Refactoring — the ledger and the world
Terraform keeps two books: the code says what should exist, the state says what Terraform believes exists. Every command here edits the belief.
§ IWhere This Sits in the Blueprint
Objective 4 is "use the Terraform CLI outside the core workflow": import, state subcommands, -replace, debugging. Objective 7 is "implement and maintain state." The exam writes both from one picture. Call it the ledger and the world: state is a ledger, a private mapping from resource address to real-world id (Brikman ch. 3, p. 147); the world is what the cloud actually holds. Plan compares code, ledger, world. Apply moves the world toward the code and records the move. Every tool below exists for the moments when ledger and world disagree in a way apply cannot fix, or when the code must change shape without the world moving at all.
§ IIAdopt, Don't Rebuild — import
A console-built resource sits in the world but not in the ledger; a config block describing it plans as CREATE, and applying makes a duplicate or a collision. Brikman's "Valid Plans Can Fail" names the disease (pp. 296-297): out-of-band resources. The cure is adoption. Write the resource block first, then:
terraform import aws_s3_bucket.logs hedronite-logs-prod
The trap the exam loves: import writes the ledger only, and never generates configuration. Import, plan, edit until clean; the world is untouched throughout. Pro depth: Terraform 1.5 moved adoption into the language, and the import block makes it a plannable, reviewable event that rides today's Ops pipeline onto a pull request:
import {
to = aws_s3_bucket.logs
id = "hedronite-logs-prod"
}
terraform plan -generate-config-out=generated.tf drafts the configuration the CLI path made you write by hand; for_each on import blocks (1.7+) turns a hundred console-era resources into one reviewed change.
§ IIIRename in the Ledger, Not in the World
Rename aws_instance.web to aws_instance.frontend in code alone and plan reads it as destroy-and-create; Brikman's refactoring warning is the canonical text (pp. 300-302). The plan is "valid," the apply is obedient, and the instance takes an outage for a spelling change. Two tools rename the belief instead:
terraform state mv aws_instance.web aws_instance.frontend
moved {
from = aws_instance.web
to = aws_instance.frontend
}
state mv is one operator on one ledger; the moved block (1.1+) ships inside the module, so every consumer's next plan performs the rename silently and correctly. Associate answer: mv for local surgery, moved for anything published. Pro depth adds the mirror: the removed block (1.7+) declares "forget this address, do not destroy the world," the declarative twin of state rm, carrying lifecycle { destroy = false }. And state rm holds the inverse of the import trap: forget in the ledger while the block stays in code, and the next plan wants to CREATE the address again.
§ IVRebuild and Reconcile — -replace and -refresh-only
Two deprecations, two modern forms, both exam bait. taint flagged a resource for recreation by editing the ledger; the modern form shows the intention in a plan:
terraform apply -replace=aws_instance.frontend
refresh updated the ledger to match the world silently; the modern form previews:
terraform plan -refresh-only
terraform apply -refresh-only
Refresh-only moves the belief toward the world and never touches the world. Direction is the tested concept. Bucket deleted by hand: refresh-only makes the ledger admit the loss; a normal apply would rebuild it. Instances tagged by hand: refresh-only adopts the tags; a normal apply would strip them. Ask which book is wrong, then pick the tool that edits that book. Debugging closes objective 4: TF_LOG=TRACE for the full firehose, TF_LOG_PATH to route it to a file.
§ VThe Pipeline Holds Its Breath
State surgery is desk work: it happens outside the pull-request flow, against the same S3/DynamoDB backend the 07-22 lesson built, under the same lock. While surgery is in progress the pipeline pauses, because the nightly drift plan reads a half-finished import as an alarm, and it should. Finish the surgery, run one clean plan, then let the run nobody typed resume. The Bootcamp's broken-labs 03 and 11 rehearse exactly this repair work: a wrong ledger, graded on the fix.
§ VIDrill Cards — Tap to Reveal
terraform import aws_s3_bucket.logs hedronite-logs-prod succeeds, and what will import never do?resource "aws_s3_bucket" "logs" block must exist in code; import attaches the ledger entry to that address. Import never generates configuration: after importing, plan and edit until clean. Config-driven alternative: an import block plus -generate-config-out.terraform state mv fixes one local ledger; a moved block ships the rename to every consumer of the module. Published modules use moved.terraform apply -replace=aws_instance.frontend. taint is the deprecated form; -replace shows the rebuild in a plan instead of leaving a ledger flag for someone else to find.terraform apply -refresh-only: the ledger moves toward the world, the world stays untouched. A normal apply would move the world back toward code and strip the tags.terraform state rm aws_s3_bucket.legacy, the block stays in code. Next plan?aws_s3_bucket.legacy: the ledger forgot it, the world still holds it, so applying would collide or duplicate. Remove the block too, or use a removed block with destroy = false to do both declaratively.terraform state pull > backup.tfstate before surgery: why, and what is the standing rule about editing state?§ VIIClosing
Two books: code and belief. Four verbs on the belief: adopt (import), rename (mv/moved), forget (rm/removed), reconcile (refresh-only). One verb on the world: -replace, and even it travels through a plan. Never the text editor. While any of it is happening, the pipeline holds its breath.
Run Bootcamp labs 03 and 11 this week with the ledger-and-world picture taped above the keyboard. Say the direction out loud before each command: which book is this moving?
Filed 2026-08-03 Fajr · Cert lesson · Terraform Associate 003 + Pro depth (day 12, visit 5)