Refactoring and Import Clinic
Kata: Rename, relocate, and adopt resources without destroying them. This exercises Cluster 4 -- plan review, refactoring, and blast-radius control -- in the single most dangerous area of day-to-day Terraform.
You will do three things:
- Rename a resource inside the same module using a
movedblock. - Extract a resource out of a root module into a child module using
movedacross the boundary. - Adopt an existing, out-of-band resource with an
importblock.
Retrieval Prompts
- What happens if you rename a resource's address in
.tfwithout amovedblock? - What is the difference between a
movedblock andterraform state mv? - What does an
importblock do thatterraform import(CLI) does not? - What
plansymbol should scare you the most, and why? - What is
prevent_destroyfor, and what is its weakness?
Compare and Distinguish
- Renaming a resource (
moved) vs replacing a resource (new address, destroy + create). Which does your situation call for? moved(refactor inside Terraform's knowledge) vsimport(teach Terraform about something it does not yet own). Never confuse these.terraform state rm(makes Terraform forget a resource exists) vsterraform destroy(deletes it). The words sound similar; the effects are opposite.-target(narrow what you apply) vs a separate root module (narrow what Terraform sees). Why is the second almost always better?
The Clinic -- Three Drills
Drill 1: Rename with moved
-
Start from Lab 1's state. Rename
aws_s3_bucket.artifactstoaws_s3_bucket.artifact_store. -
Without a
movedblock, runterraform plan. Capture the output (destroy + create). -
Add the
movedblock:moved {
from = aws_s3_bucket.artifacts
to = aws_s3_bucket.artifact_store
} -
Run
planagain. Capture the output (no-op). -
apply. Runterraform state listand confirm the new address.
Drill 2: moved Across a Module Boundary
-
Extract the bucket resource into
modules/storage-bucket/. -
In the root, write:
moved {
from = aws_s3_bucket.artifact_store
to = module.artifacts.aws_s3_bucket.this
} -
plan-> should be a no-op against real infra. -
apply. Confirm the state showsmodule.artifacts.aws_s3_bucket.this.
Drill 3: Adopt with import
-
Out of band (via the console or CLI), create a new S3 bucket named
legacy-logs-<yourname>. -
In Terraform, add a resource block for it:
resource "aws_s3_bucket" "legacy_logs" {
bucket = "legacy-logs-<yourname>"
}
import {
to = aws_s3_bucket.legacy_logs
id = "legacy-logs-<yourname>"
} -
Run
plan. The plan should showimport+ possibly a~for attributes Terraform needs to set. -
Read the plan carefully. Fix the config until the plan is an import and nothing else.
-
apply. Remove theimportblock after success.
Common Mistake Check
Identify and fix:
moved { from = module.a.x to = module.b.x }where moduleastill exists and has its ownx. What happens?- An
importblock whoseidis wrong (typo). Plan looks fine -- what doesapplydo? - Renaming with
terraform state mvin one engineer's terminal instead of using amovedblock in code. What does the next PR reviewer see? - An engineer saw
replacedin a plan for production RDS and apply'd anyway because the PR description said "no-op." What is the process failure, not the engineer failure? prevent_destroy = truewas added to a bucket, then an engineer removed it with a one-line PR to let destroy proceed. How do you prevent this pattern?
Evidence Check
This page is complete only when you can:
- rename resources inside and across modules without a destroy/create
- adopt out-of-band resources with
importblocks and remove the block after success - read any plan and identify every
~,-/+, and<= - state what
terraform statecommand you would use, and what command you would refuse to use, in an on-call incident - design a PR review policy for refactors that catches the mistakes above before
apply