TT Lab
Get started
Learn Learning paths Courses

Terraform/OpenTofu Fundamentals

Almost Rebuilt a Resource When Only the State Was Wrong

Continue in TT Lab

Goal

You confirm one by one with OpenTofu the refresh-only plan and apply, the illusion of a plan that does not read real objects, a saved destroy plan, the reverse order of destruction, the warning of a target-narrowed destruction, and what remains in the state after everything is deleted.

Why it matters

A plan compares two things — code versus state, and state versus real objects. Being able to handle these two separately is the core of the refresh family of commands. If the state has merely diverged from the real objects, you can bring only the state in line without touching the code, and then you do not have to recreate resources. Conversely, if you make a plan without reading real objects, the tool trusts the values written in the state as fact. It is a choice that gives up accuracy in exchange for speed in a large repository, and if you get approval with a plan made that way, the apply goes in diverged. On the destruction side too, order is everything. You must delete in the reverse of the creation order so that what remains does not point at what is already gone. If you delete with the target narrowed, that guarantee breaks, so the tool attaches a warning. Finally, you need to know that the state file itself remains even after everything is deleted, so that you do not confuse "deleting the state" with "deleting resources."

Steps

  1. In /root/tfb-refresh/base/main.tf, put a resource that writes to app.conf the single line mode=managed, namely local_file.conf, and a resource that writes that resource's id as the single line conf=<id> to audit.txt, namely local_file.audit. Run init and apply, and check that the plan is clean.
  2. Without going through the tool, change /root/tfb-refresh/base/app.conf to the single line HANDEDIT. Then save the output of tofu plan -refresh-only to /root/tfb-refresh/refresh-plan.txt. Do not apply anything yet.
  3. In the same directory, save the output of tofu plan -refresh=false to /root/tfb-refresh/norefresh.txt and the output of a plain tofu plan to /root/tfb-refresh/withrefresh.txt. Check how the two outputs differ. Still do not apply.
  4. Run tofu apply -refresh-only -auto-approve. Then write four lines to /root/tfb-refresh/refresh-report.txt — serial_before=<새로고침 직전 상태의 serial>, serial_after=<지금 serial>, resources_after=<지금 상태의 리소스 수>, conf_first_line=<지금 디스크의 app.conf 첫 줄> (the serial of the state just before the refresh, the current serial, the number of resources in the current state, and the first line of app.conf currently on disk). The state just before remains in the backup file.
  5. In /root/tfb-refresh/chain/main.tf, put three terraform_data as a chain — net, db referencing its output, and app referencing its output. All three have a provisioner that, at destroy time, appends its own name to /root/tfb-refresh/chain/order.log, and also put an output that exports app.output, namely chain. After init and apply, save a plan with tofu plan -destroy -out=destroy.tfplan, and with tofu show -json, write the addresses to be deleted in dictionary order, one per line, to /root/tfb-refresh/destroy-targets.txt.
  6. Apply the saved destroy.tfplan as it is to delete the chain. Check the order left in /root/tfb-refresh/chain/order.log, and write the same order, one per line, to /root/tfb-refresh/order.txt.
  7. In /root/tfb-refresh/tgt/main.tf, put a chain of the same shape (net, db, app) without provisioners, and run init and apply. Then delete only the very last one with tofu destroy -target=terraform_data.app -auto-approve and save the output to /root/tfb-refresh/target-destroy.txt. Write the addresses of the remaining state in dictionary order to /root/tfb-refresh/target-left.txt.
  8. Compare the state of /root/tfb-refresh/chain after destruction with the backup file and write four lines to /root/tfb-refresh/leftover.txt — resources=<지금 상태의 리소스 수>, outputs=<지금 상태의 출력 수>, lineage_changed=<yes 또는 no>, serial_up=<yes 또는 no> (the number of resources in the current state, the number of outputs in the current state, yes or no, and yes or no). And run tofu plan once more and save the output to /root/tfb-refresh/after-destroy-plan.txt.

Notes

Build a baseline

In /root/tfb-refresh/base/main.tf, put a resource that writes to app.conf the single line mode=managed, namely local_file.conf, and a resource that writes that resource's id as the single line conf=<id> to audit.txt, namely local_file.audit. Run init and apply, and check that the plan is clean.

The file resource of the local provider uses the hash of the content as its id. So when the content changes, the id changes, and resources that reference that id shake along with it.

See what changed outside with a refresh-only plan

Without going through the tool, change /root/tfb-refresh/base/app.conf to the single line HANDEDIT. Then save the output of tofu plan -refresh-only to /root/tfb-refresh/refresh-plan.txt. Do not apply anything yet.

A refresh-only plan is not a plan that "brings the code in line with reality" but one that "brings the state in line with reality." So the output shows what vanished from the state, not create or destroy.

A plan that did not read real objects says nothing happened

In the same directory, save the output of tofu plan -refresh=false to /root/tfb-refresh/norefresh.txt and the output of a plain tofu plan to /root/tfb-refresh/withrefresh.txt. Check how the two outputs differ. Still do not apply.

If it does not read real objects, the tool trusts the value written in the state as fact. It is an option used to get a plan quickly in a large repository, but if you get approval with that plan, the apply goes in diverged from the real objects.

Fix only the state and do not touch the real objects

Run tofu apply -refresh-only -auto-approve. Then write four lines to /root/tfb-refresh/refresh-report.txt — serial_before=<새로고침 직전 상태의 serial>, serial_after=<지금 serial>, resources_after=<지금 상태의 리소스 수>, conf_first_line=<지금 디스크의 app.conf 첫 줄> (the serial of the state just before the refresh, the current serial, the number of resources in the current state, and the first line of app.conf currently on disk). The state just before remains in the backup file.

This command changes no real object at all. Only the state changes, and the tool leaves the state just before as a backup file. The file fixed by hand must stay as it is.

Save the destroy plan to a file and review it

In /root/tfb-refresh/chain/main.tf, put three terraform_data as a chain — net, db referencing its output, and app referencing its output. All three have a provisioner that, at destroy time, appends its own name to /root/tfb-refresh/chain/order.log, and also put an output that exports app.output, namely chain. After init and apply, save a plan with tofu plan -destroy -out=destroy.tfplan, and with tofu show -json, write the addresses to be deleted in dictionary order, one per line, to /root/tfb-refresh/destroy-targets.txt.

A destroy plan can also be saved to a file, like a normal plan, and the saved file can be viewed in both a human-readable format and a machine-readable format. The more irreversible the operation, as with destruction, the safer it is to save the plan, review it, and then apply only that file.

The deletion order is the reverse of the creation order

Apply the saved destroy.tfplan as it is to delete the chain. Check the order left in /root/tfb-refresh/chain/order.log, and write the same order, one per line, to /root/tfb-refresh/order.txt.

When creating, the thing depended on must be created first, and when deleting, it must be the opposite. Otherwise what still remains points at something already gone. Do not make a new plan; apply the saved file.

A target-narrowed destruction comes with a warning

In /root/tfb-refresh/tgt/main.tf, put a chain of the same shape (net, db, app) without provisioners, and run init and apply. Then delete only the very last one with tofu destroy -target=terraform_data.app -auto-approve and save the output to /root/tfb-refresh/target-destroy.txt. Write the addresses of the remaining state in dictionary order to /root/tfb-refresh/target-left.txt.

The option that narrows the target is a declaration that a person takes responsibility for the consistency of the whole graph, which the tool normally guards. That is why a warning is attached to the output. Use it only in emergencies, and after using it, run one un-narrowed plan to check.

What remains in the state after everything is deleted

Compare the state of /root/tfb-refresh/chain after destruction with the backup file and write four lines to /root/tfb-refresh/leftover.txt — resources=<지금 상태의 리소스 수>, outputs=<지금 상태의 출력 수>, lineage_changed=<yes 또는 no>, serial_up=<yes 또는 no> (the number of resources in the current state, the number of outputs in the current state, yes or no, and yes or no). And run tofu plan once more and save the output to /root/tfb-refresh/after-destroy-plan.txt.

Even after everything is deleted, the state file itself remains. You need to know what becomes empty and what remains so that you do not confuse "deleting the state" with "deleting resources." The state just before the destruction is in the backup file.