Hedronite · Cert-Prep · HashiCorp · Terraform Associate 003 + Pro Depth · Track TF Day 12 · Mon 2026-08-03 · Bundle Trio #4

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.

Cert Target: Terraform Associate 003 — Objectives 4 & 7 · Pro Authoring & Operations depth
Coverage: import (CLI + config-driven) · state mv/rm/pull · moved/removed blocks · -replace · -refresh-only · TF_LOG
Word Count: ~2,300
Grounding: Brikman TU&R 3ed — Ch 5 Refactoring Can Be Tricky (pp. 300-302) · Valid Plans Can Fail (pp. 296-297) · Ch 3 What Is State (p. 147) · Bootcamp tfpro-labs 03 + 11
Paired Ops: Terraform in the Deployment Pipeline
Paired Dev: The HCL Type System
Discipline: ROD v3 · aether-accent meta-card · interactive q-cards · bundle shape (Maghrib fills quiz + lab-ref)
Objective 4 · CLI outside core workflow Objective 7 · Implement & maintain state Pro · config-driven import + removed Labs · tfpro 03 & 11
To change the world, go through code and apply. To change only the belief, use the state tools, and never a text editor on the JSON.

§ 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

Q1 · Import Prerequisites
A colleague built an S3 bucket in the console. What must exist before terraform import aws_s3_bucket.logs hedronite-logs-prod succeeds, and what will import never do?
A 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.
tap to reveal
Q2 · The Rename Illusion
You rename a resource address in code only. What does the next plan show, and which two tools make the rename safe?
Destroy-and-create: the old address vanished, the new address appeared. terraform state mv fixes one local ledger; a moved block ships the rename to every consumer of the module. Published modules use moved.
tap to reveal
Q3 · The Compromised Host
A host is compromised and must be rebuilt from the same config. Modern command?
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.
tap to reveal
Q4 · Direction of Reconcile
Someone hand-tagged prod instances. You want Terraform's belief to absorb the tags without changing any infrastructure. Command and direction?
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.
tap to reveal
Q5 · Forget, Then What?
After terraform state rm aws_s3_bucket.legacy, the block stays in code. Next plan?
CREATE of 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.
tap to reveal
Q6 · Before the Surgery
terraform state pull > backup.tfstate before surgery: why, and what is the standing rule about editing state?
Pull snapshots the ledger for rollback before mv/rm work. The rule: never edit state JSON by hand; every belief-change goes through the state commands or the moved/removed/import blocks, which validate addresses and keep serial and lineage intact.
tap to reveal

§ 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?

🫡 ⚖️ 📜
Leo.Syri — Praetor Consulate, Imperium Luminaura
Filed 2026-08-03 Fajr · Cert lesson · Terraform Associate 003 + Pro depth (day 12, visit 5)