TT Lab
Get started
Learn Learning paths Courses

Terraform in Practice

Destroying and Letting Go Are Different Things

Continue in TT Lab

In one sentence

Deleting a resource block from the configuration means "destroy it." The meaning "we no longer manage this" must be stated separately with a removed block.

Why this was needed — deleting and letting go were the same sentence

Say a team manages three databases and hands one of them to another team. Deleting that block from the code seems natural. Then you run plan and destroy 1 shows up. It is a database in production.

In the past, this situation was solved with a command. You type by hand, once, a command that removes that address from the state, and then you delete the code. It works, but two things are bad. First, there is no record. Who removed what from the state and when is not left in a commit. Second, if you get the order wrong, it is over. If you delete the code first and type apply, the situation ends there.

A removed block turns this job into a declaration. Because it is written in the code, it gets reviewed, stays in a commit, and there is no room for the order to be reversed.

How it works

# 1) 리소스 블록을 지운다
# 2) 그 자리에 removed 블록을 둔다
removed {
  from = local_file.legacy
}

The two Korean comments say: 1) delete the resource block, and 2) put a removed block in its place.

When you run the plan, it comes out like this.

# local_file.legacy will be removed from the OpenTofu state
# but will not be destroyed
Plan: 0 to add, 0 to change, 0 to destroy.

There are two places to read. First, the word indicating the action is not destroy. Second, so the three numbers on the Plan: line are all 0 — it means nothing is done to the real thing.

The arguments accepted differ by release. This is a habit you must take away from this module: whether a removed block accepts a sub-block such as lifecycle depends on the release you are using. The documentation site shows the latest release by default, so just because something is in the documentation, there is no guarantee that your tool accepts it. The way to check is simple — put it in and run plan, and the tool answers directly. In step 3 of the lab, you ask what this Pod's release accepts and does not accept, and write it down in a table.

Changing only the name is moved. It is easy to confuse with removed, but the purpose is the opposite. moved says "it is the same object but the address changed" and moves the state entry to the new address. Without it, the tool reads the old name as vanished and the new name as appeared and destroys and recreates. Whether it was moved or recreated is immediately clear by comparing the identifiers.

When you want to recreate just one, use -replace. You leave the configuration as it is and put "destroy and recreate only this address" into the plan. In the past the same thing was done with taint, and the difference is important. taint edits the state file first and lets the next plan read it. If you mark it and forget, the wrong person will apply that replacement at the wrong time. -replace applies only to that run, so it leaves no trace. Even so, the reason you should know taint and untaint is so that you are not flustered when you encounter a state that someone else has marked.

What it looks like in the field

The most common incident is leaving a removed block in place without clearing it. Once the job is done, that block is dead code, and left dead, it comes back to life one day when a resource at that address is needed again. If "forget it" and "create it" are at the same address at once, the plan is blocked. More ironic is that this error passes configuration validation (validate) and occurs only in the plan. Matching addresses against the state is the job of the plan stage. If CI runs only validate, it cannot catch this problem.

The second is organizational splits. When a team splits, you must hand over one side's share wholesale, and since the from of removed can also take a module address, you can let go of a whole module at once. However, the receiving side taking that real thing into its own state is a separate task, and the provider must support importing that resource — not every resource does.

The third is the period when ownership is empty. From the moment you let go until the receiving side takes it, no code manages that real thing. To make sure nobody touches it in between, you must wrap the handover task in documentation and a schedule. It is not something the tool can do for you.

The fourth is whether you can take it back. To manage again in code what you removed from the state, you must import it, and if the provider has not implemented importing for that resource, there is no way. The file resource in this lab is exactly such a case. So before letting go, it is safer to check once "is this a reversible decision?" — if it is not reversible, leave a copy, or the handing-over side and the receiving side must work at the same time.

What to do in the next lab

In /root/tfa-removed, you remove one handed-over file from the state only with removed and confirm that the real thing stays. You ask directly what this release's removed accepts and write it in a table, rename with moved and compare with the step 1 copy that the identifier is kept, receive the error when a forget declaration and a create declaration overlap, then recreate just one with -replace and taint, and finally let go of a whole module.