TT Lab
Get started
Learn Learning paths Courses

Terraform in Practice

Deleting It from Code Nearly Destroyed a Live Resource

Continue in TT Lab

Goal

You try a removed block that removes a resource only from the state without destroying it, ask the tool directly what this release's removed accepts and record it, rename only with moved, recreate just one with -replace and taint, and then let go of a whole module.

Why it matters

If you operate infrastructure code for a long time, a day comes when you must touch the ownership relationship between code and the real thing. Teams split, you move to another tool, you give back what you took over. But deleting a resource block from the configuration reads to the tool as 'destroy it' — it does not mean giving up ownership. So you need a declaration that states that meaning separately, and the reason for doing it with a declaration rather than a command is review and records. Who removed what from the state and when stays in a commit. There is one more trap here — the documentation always describes the latest release, but the release you use may be older. Knowing that the arguments a removed block accepts differ by release, and the habit of checking by asking the tool in your hands rather than the documentation, is the second thing to learn in this module.

Steps

  1. In /root/tfa-removed/keep.tf, put random_pet.keep (length 2) and a local_file.keep that writes its name to out/keep.txt, and in /root/tfa-removed/legacy.tf, put a local_file.legacy that writes the single line legacy service to out/legacy.txt, then init and apply. Then copy the state file to /root/tfa-removed/snapshots/before.tfstate, only once.
  2. Delete /root/tfa-removed/legacy.tf, and in /root/tfa-removed/removed.tf declare with a removed block that you will remove local_file.legacy from the state. Save the plan output to /root/tfa-removed/removed-plan.txt before applying, then apply. out/legacy.txt must remain as it is.
  3. Briefly put a lifecycle block (destroy = false) inside the removed block of /root/tfa-removed/removed.tf, run tofu plan, save the result to /root/tfa-removed/lifecycle-probe.txt, and then revert it to the original. Then write three lines in /root/tfa-removed/removed-facts.tsv with two tab-separated columns — tofu_version is this Pod's core version, removed_lifecycle is supported or unsupported, and removed_effect is forget or destroy.
  4. Rename random_pet.keep in /root/tfa-removed/keep.tf to random_pet.app (including references), declare in /root/tfa-removed/moved.tf with a moved block that it moves from the old address to the new address, and apply. Then write two lines before (the id of random_pet.keep recorded in the step 1 copy) and after (the id of random_pet.app in the current state) in /root/tfa-removed/moved.tsv with two tab-separated columns. The two values must be the same.
  5. Briefly bring back /root/tfa-removed/legacy.tf (with the same contents as in step 2), run tofu plan, save the output to /root/tfa-removed/conflict.txt, and then delete that file again. Leave the removed block as it is. At the end, the plan must be clean.
  6. Copy the state file to /root/tfa-removed/snapshots/before-replace.tfstate and then run tofu apply -replace=random_pet.app -auto-approve. Then write two lines before (the copy's id) and after (the current id) in /root/tfa-removed/replace.tsv with two tab-separated columns. The two values must be different.
  7. After marking with tofu taint random_pet.app, save the status value of that instance recorded in the state on one line to /root/tfa-removed/taint-status.txt and save the tofu plan output to /root/tfa-removed/taint-plan.txt. Then revert with tofu untaint and finish with a clean plan.
  8. In /root/tfa-removed/modules/archive/main.tf, create a module that writes two files out/archive-1.txt and out/archive-2.txt, call it as module "archive" in /root/tfa-removed/archive.tf, and apply. Then delete /root/tfa-removed/archive.tf and remove the whole module.archive from the state with a removed block in /root/tfa-removed/archive-removed.tf. Save the plan output to /root/tfa-removed/module-plan.txt, and the two files must remain on disk.

Notes

Put even the handed-over item in one state

In /root/tfa-removed/keep.tf, put random_pet.keep (length 2) and a local_file.keep that writes its name to out/keep.txt, and in /root/tfa-removed/legacy.tf, put a local_file.legacy that writes the single line legacy service to out/legacy.txt, then init and apply. Then copy the state file to /root/tfa-removed/snapshots/before.tfstate, only once.

To judge in later steps 'was this resource recreated, or is it as it was,' you must leave the current identifier somewhere. Take the copy only the first time — if you overwrite it later, the original to compare against disappears.

Remove only from the state and leave the real thing

Delete /root/tfa-removed/legacy.tf, and in /root/tfa-removed/removed.tf declare with a removed block that you will remove local_file.legacy from the state. Save the plan output to /root/tfa-removed/removed-plan.txt before applying, then apply. out/legacy.txt must remain as it is.

If you delete only the resource block from the configuration, the tool reads it as meaning to destroy it. A removed block is a declaration that separately says 'forget this address in the state.' Check that the word in the plan output indicating this action is not destroy.

Ask the tool what this release's removed accepts

Briefly put a lifecycle block (destroy = false) inside the removed block of /root/tfa-removed/removed.tf, run tofu plan, save the result to /root/tfa-removed/lifecycle-probe.txt, and then revert it to the original. Then write three lines in /root/tfa-removed/removed-facts.tsv with two tab-separated columns — tofu_version is this Pod's core version, removed_lifecycle is supported or unsupported, and removed_effect is forget or destroy.

The documentation always describes the latest release. What the release in your hands accepts can be known only by asking the tool, and writing down that answer saves the team's time. The effect is already told by the step 2 plan output.

Change only the name — without recreating the same thing

Rename random_pet.keep in /root/tfa-removed/keep.tf to random_pet.app (including references), declare in /root/tfa-removed/moved.tf with a moved block that it moves from the old address to the new address, and apply. Then write two lines before (the id of random_pet.keep recorded in the step 1 copy) and after (the id of random_pet.app in the current state) in /root/tfa-removed/moved.tsv with two tab-separated columns. The two values must be the same.

Without a moved block, the tool reads it as 'the old name vanished and a new name appeared,' and destroys and recreates. moved is a declaration that tells the tool only the address of the same object changed, so the identifier is kept as it is. Check against the step 1 copy that this is really so.

If you say forget and then declare it again

Briefly bring back /root/tfa-removed/legacy.tf (with the same contents as in step 2), run tofu plan, save the output to /root/tfa-removed/conflict.txt, and then delete that file again. Leave the removed block as it is. At the end, the plan must be clean.

If a declaration to forget and a declaration to create are at the same address at once, the tool cannot know what to do. This error passes tofu validate and occurs only in plan — think about why (resolving addresses is the job of the plan stage, not configuration validation).

Recreate just one

Copy the state file to /root/tfa-removed/snapshots/before-replace.tfstate and then run tofu apply -replace=random_pet.app -auto-approve. Then write two lines before (the copy's id) and after (the current id) in /root/tfa-removed/replace.tsv with two tab-separated columns. The two values must be different.

-replace puts 'destroy and recreate only this' into the plan without changing the configuration. You use it when the provider has gone strange, or to reset a boot state that does not show in the configuration. Also look in the plan at whether things that depend on this resource are recreated along with it.

Mark by hand and then revert

After marking with tofu taint random_pet.app, save the status value of that instance recorded in the state on one line to /root/tfa-removed/taint-status.txt and save the tofu plan output to /root/tfa-removed/taint-plan.txt. Then revert with tofu untaint and finish with a clean plan.

The mark does not touch the infrastructure and is written only into the state file — the next plan reads it and adds a replacement. Look at that instance's status with jq. Today you can do the same thing more safely with -replace (since it does not change the state in advance), but you need to know what this command does so you are not flustered when you meet something someone else has marked.

Let go of a whole module

In /root/tfa-removed/modules/archive/main.tf, create a module that writes two files out/archive-1.txt and out/archive-2.txt, call it as module "archive" in /root/tfa-removed/archive.tf, and apply. Then delete /root/tfa-removed/archive.tf and remove the whole module.archive from the state with a removed block in /root/tfa-removed/archive-removed.tf. Save the plan output to /root/tfa-removed/module-plan.txt, and the two files must remain on disk.

The from of removed can take not only a resource address but also a module address, and then everything inside that module is removed from the state at once. This is what the task of handing over one team's share wholesale looks like when splitting an organization. When setting file paths inside a module, it is less confusing to use paths based on the root.