TT Lab
Get started
Learn Learning paths Courses

Terraform in Practice

Moving, Removing and Importing State

Continue in TT Lab

Goal

You safely move the address of an already applied resource, bring a resource that was outside management into the state, and prove through an exit code that, as a result, the code and state do not diverge.

Why it matters

The state file is the tool's memory. The code says "what should exist," the real thing says "what exists," and the state says "what I last knew." Refactoring changes only the code among these three, so if you do not move the state with it, the tool judges that the old resource vanished and a new resource appeared and plans destruction and creation. That is why a trivial task like renaming becomes a dangerous task in production. Conversely, state rm, unlike its name, does not delete the real thing — it is a declaration of giving up management that deletes only from the ledger, and if the declaration remains in the code, the next plan tries to create that resource again. You must know this asymmetry precisely for state surgery not to be scary. And make a copy before every surgery. The difference between a reversible operation and an irreversible one is that one file.

Steps

  1. Declare three resources in /root/tf/surgery/main.tf, run tofu init, and apply — local_file.legacy (file /root/tf/surgery/files/legacy.txt), local_file.notes (file /root/tf/surgery/files/notes.txt), and random_pet.orphan. Then copy the state file to /root/tf/surgery/backup/terraform.tfstate.bak. The copy must be valid JSON, its lineage must equal the current state's, and it must have 3 or more resources.
  2. Save the result of tofu state list to /root/tf/surgery/out/state-list.txt (3 or more lines) and the result of tofu state show local_file.legacy to /root/tf/surgery/out/show-legacy.txt. The second file must show the id attribute line.
  3. Change the state address with tofu state mv local_file.legacy local_file.renamed, and in the same task also change the resource name in the code to renamed. The old local_file.legacy block must be deleted completely, not commented out, and must not remain in any .tf file in the working directory. If you move only the state and leave the code as it is, the next plan tries to create the old-named resource again. If you save the tofu plan output to /root/tf/surgery/out/plan-after-rename.txt, it must be No changes.
  4. Create a module in the /root/tf/surgery/modules/archive/ directory and declare the same local_file resource there under the name renamed (argument values such as file path and contents must be exactly identical to before the move). Add a module "archive" block at the root, run tofu init, run tofu state mv local_file.renamed module.archive.local_file.renamed, and delete that resource block from the root main.tf (here too, it is deletion, not commenting out — the resource declaration must now exist only in the module file). If you save the tofu plan output to /root/tf/surgery/out/plan-after-module.txt, it must be No changes.
  5. Run tofu state rm random_pet.orphan, then save the tofu plan output to /root/tf/surgery/out/plan-after-rm.txt. The plan must say it will create random_pet.orphan again (1 to add). Once you have confirmed it, also delete that resource block from main.tf, and write the difference between state rm and destroy in one line in /root/tf/surgery/out/rm-note.txt.
  6. Declare resource "terraform_data" "adopted" in main.tf as an empty shell, put an import block above it specifying to = terraform_data.adopted and id = "labhub-adopted", and apply. The id attribute of terraform_data.adopted in the state must be labhub-adopted.
  7. Declare resource "terraform_data" "legacy_job" in main.tf, and this time run tofu import terraform_data.legacy_job labhub-legacy on the command line. If you save the output to /root/tf/surgery/out/import.txt, the phrase Import successful must appear, and the id of terraform_data.legacy_job in the state must be labhub-legacy.
  8. Run tofu plan -detailed-exitcode and write only the exit code to /root/tf/surgery/out/final-exit.txt (it must be 0). Then create /root/tf/surgery/out/inventory.json, put all addresses of the current state as an array in addresses, and put true in backup_taken. The number of addresses must be exactly equal to the number of instances in the state, and the list must include addresses starting with module.archive. and the imported adopted address.

Notes

Secure a copy of the state before surgery

Declare three resources in /root/tf/surgery/main.tf, run tofu init, and apply — local_file.legacy (file /root/tf/surgery/files/legacy.txt), local_file.notes (file /root/tf/surgery/files/notes.txt), and random_pet.orphan. Then copy the state file to /root/tf/surgery/backup/terraform.tfstate.bak. The copy must be valid JSON, its lineage must equal the current state's, and it must have 3 or more resources.

Copy the file before every task that touches the state. To check whether the copy is of the same state, compare the lineage values.

Look at the state list and an individual resource

Save the result of tofu state list to /root/tf/surgery/out/state-list.txt (3 or more lines) and the result of tofu state show local_file.legacy to /root/tf/surgery/out/show-legacy.txt. The second file must show the id attribute line.

The list command and the command that shows all attributes of a particular address are different. The latter must show the id attribute.

Rename only, with state mv

Change the state address with tofu state mv local_file.legacy local_file.renamed, and in the same task also change the resource name in the code to renamed. The old local_file.legacy block must be deleted completely, not commented out, and must not remain in any .tf file in the working directory. If you move only the state and leave the code as it is, the next plan tries to create the old-named resource again. If you save the tofu plan output to /root/tf/surgery/out/plan-after-rename.txt, it must be No changes.

The code and the state must point to the same name for the plan to be empty. If you move only the state, the next plan tries to create it again under the old name, and if you fix only the code, destruction and creation appear side by side. The old-named block is deleted, not commented out.

Move a resource into a module

Create a module in the /root/tf/surgery/modules/archive/ directory and declare the same local_file resource there under the name renamed (argument values such as file path and contents must be exactly identical to before the move). Add a module "archive" block at the root, run tofu init, run tofu state mv local_file.renamed module.archive.local_file.renamed, and delete that resource block from the root main.tf (here too, it is deletion, not commenting out — the resource declaration must now exist only in the module file). If you save the tofu plan output to /root/tf/surgery/out/plan-after-module.txt, it must be No changes.

Create the module block first, run init, and then move. The plan is empty only if the argument values of the resource inside the module are exactly the same as before the move.

Prove that state rm does not delete the real thing

Run tofu state rm random_pet.orphan, then save the tofu plan output to /root/tf/surgery/out/plan-after-rm.txt. The plan must say it will create random_pet.orphan again (1 to add). Once you have confirmed it, also delete that resource block from main.tf, and write the difference between state rm and destroy in one line in /root/tf/surgery/out/rm-note.txt.

It is a command that deletes only from the ledger. See what the next plan tries to do if the declaration remains in the code.

Bring in a resource with an import block

Declare resource "terraform_data" "adopted" in main.tf as an empty shell, put an import block above it specifying to = terraform_data.adopted and id = "labhub-adopted", and apply. The id attribute of terraform_data.adopted in the state must be labhub-adopted.

You must create the place to bring it into (the resource block) first. In the block, write where to (to) and what (id) to bring in.

Bring in with a command-line import

Declare resource "terraform_data" "legacy_job" in main.tf, and this time run tofu import terraform_data.legacy_job labhub-legacy on the command line. If you save the output to /root/tf/surgery/out/import.txt, the phrase Import successful must appear, and the id of terraform_data.legacy_job in the state must be labhub-legacy.

This is the way to do the same thing with immediate execution. A success message remains in the output, so save it to a file.

Prove the match after surgery with an exit code

Run tofu plan -detailed-exitcode and write only the exit code to /root/tf/surgery/out/final-exit.txt (it must be 0). Then create /root/tf/surgery/out/inventory.json, put all addresses of the current state as an array in addresses, and put true in backup_taken. The number of addresses must be exactly equal to the number of instances in the state, and the list must include addresses starting with module.archive. and the imported adopted address.

A verdict a person reads with their eyes is not automation. Judge by the plan's exit code, and gather the state's addresses in one place and check the count.