Moving, Removing and Importing State
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
- Declare three resources in
/root/tf/surgery/main.tf, runtofu init, and apply —local_file.legacy(file/root/tf/surgery/files/legacy.txt),local_file.notes(file/root/tf/surgery/files/notes.txt), andrandom_pet.orphan. Then copy the state file to/root/tf/surgery/backup/terraform.tfstate.bak. The copy must be valid JSON, itslineagemust equal the current state's, and it must have 3 or more resources. - Save the result of
tofu state listto/root/tf/surgery/out/state-list.txt(3 or more lines) and the result oftofu state show local_file.legacyto/root/tf/surgery/out/show-legacy.txt. The second file must show theidattribute line. - 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 torenamed. The oldlocal_file.legacyblock must be deleted completely, not commented out, and must not remain in any.tffile 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 thetofu planoutput to/root/tf/surgery/out/plan-after-rename.txt, it must beNo changes. - Create a module in the
/root/tf/surgery/modules/archive/directory and declare the samelocal_fileresource there under the namerenamed(argument values such as file path and contents must be exactly identical to before the move). Add amodule "archive"block at the root, runtofu init, runtofu state mv local_file.renamed module.archive.local_file.renamed, and delete that resource block from the rootmain.tf(here too, it is deletion, not commenting out — the resource declaration must now exist only in the module file). If you save thetofu planoutput to/root/tf/surgery/out/plan-after-module.txt, it must beNo changes. - Run
tofu state rm random_pet.orphan, then save thetofu planoutput to/root/tf/surgery/out/plan-after-rm.txt. The plan must say it will createrandom_pet.orphanagain (1 to add). Once you have confirmed it, also delete that resource block frommain.tf, and write the difference betweenstate rmanddestroyin one line in/root/tf/surgery/out/rm-note.txt. - Declare
resource "terraform_data" "adopted"inmain.tfas an empty shell, put animportblock above it specifyingto = terraform_data.adoptedandid = "labhub-adopted", and apply. Theidattribute ofterraform_data.adoptedin the state must belabhub-adopted. - Declare
resource "terraform_data" "legacy_job"inmain.tf, and this time runtofu import terraform_data.legacy_job labhub-legacyon the command line. If you save the output to/root/tf/surgery/out/import.txt, the phraseImport successfulmust appear, and theidofterraform_data.legacy_jobin the state must belabhub-legacy. - Run
tofu plan -detailed-exitcodeand write only the exit code to/root/tf/surgery/out/final-exit.txt(it must be0). Then create/root/tf/surgery/out/inventory.json, put all addresses of the current state as an array inaddresses, and puttrueinbackup_taken. The number of addresses must be exactly equal to the number of instances in the state, and the list must include addresses starting withmodule.archive.and the importedadoptedaddress.
Notes
- The state file for this lab is
/root/tf/surgery/terraform.tfstate. Be sure to keep the pre-surgery copy separately. tofu state mvtakes the old address and the new address as arguments. When moving into a module, putmodule.<이름>.in front of the new address (where the placeholder is the module name).- When you add a new module, be sure to run
tofu initbeforestate mv. You cannot move to the address of a module that is not installed. -detailed-exitcodegives 0 for no changes, 1 for an error, and 2 when there are changes. To capture the exit code, you must read$?right after the command.state mvand the code edit are a pair. If you do only one of the two, the next plan immediately goes out of step — if you move only the state, it tries to create the resource again under the old name, and if you fix only the code, destruction and creation appear side by side. That is why in practice themovedblock, which expresses both at once, is preferred.- Common mistake 1: misunderstanding
state rmas a delete command. It deletes only from the ledger, so you must also clean up the code block for the plan to go quiet. - Common mistake 2: trying to import without a resource block to bring it into. The tool looks in the code for a place to put that ID.
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.