I changed one attribute and the file was recreated
Goal
Read the plan produced by a real declarative tool (OpenTofu), build automatic verdicts from exit codes, saved plans, and plan JSON, and confirm for yourself what prevent_destroy blocks and what it cannot block.
Why it matters
The symbols of the plan you imitated with a shell in the previous lab follow the provider's rules in the real tool. It is common for a resource to be replaced when you have changed only one line of a file's content, and if you cannot read that from the plan, you find out only after applying. Automation has to read the plan in place of a person, so it must handle exit codes and JSON according to the agreed conventions, and the guarantee that what was reviewed and what was applied are the same comes from the saved plan. You also need to know the limitation that deletion protection works only while it is in the configuration, so that you can decide what to look for in review.
Steps
- In
/root/iac-plan/main.tf, require the local and random providers and declare three resources.local_file.motdwrites to${path.module}/out/motd.txtthe one linehello v1(with a trailing newline),terraform_data.releasehas inputv1, andrandom_pet.suffixhas keepersteam = "blue". Apply withtofu initandtofu apply -auto-approve. - Create
/root/iac-plan/gate.sh <작업디렉터리>(the placeholder is the working directory). In that directory, it runstofu plan -detailed-exitcode; if there are no changes it printscleanand exits with 0, if there are changes it printschangesand exits with 2, and on an error it printserrorand exits with 1. The output is only that one word on one line. The grader creates three temporary directories — no changes, changes, and a configuration error — and runs this script on them. - In
main.tf, change three values: the motd content tohello v2, the release input tov2, and the suffix keepers team togreen. Do not apply. Save the plan withtofu plan -out=/root/iac-plan/change.tfplan, then in/root/iac-plan/actions.txtwrite one line per changing resource in the form<주소> <create|update|replace|delete>(address and action). Judge which is an in-place update and which is a replacement from the symbols in the plan output and the actions intofu show -json. - First apply the saved plan as it is with
tofu apply change.tfplan. Then change the motd content tohello v3and save withtofu plan -out=/root/iac-plan/late.tfplan. Before applying this plan, create a situation in which someone else stepped in: runtofu apply -auto-approve -replace=random_pet.suffix -target=random_pet.suffixfirst (it replaces only the suffix and does not touch motd). Now runtofu apply late.tfplanand save the standard output and errors together to/root/iac-plan/stale.txt. - Create
/root/iac-plan/summary.sh <계획JSON>(the placeholder is the plan JSON). It takes thetofu show -jsonresult file and prints one line,create=<수> update=<수> replace=<수> delete=<수>(the placeholders are counts). A replacement (a resource that has both a delete and a create) is counted only as one replace. Then save the plan of the current working directory to/root/iac-plan/now.json(tofu plan -out, thentofu show -json), and save its summary to/root/iac-plan/now-summary.txt. - To
local_file.motd, addlifecycle { prevent_destroy = true }. With the content stillhello v3, runtofu planand save the output (including the error) to/root/iac-plan/guard.txt. Then change the motd content back tohello v2so that the plan is clean again (the plan of the working directory must show no changes). The grader builds a plan on a copy with the motd content changed to check that the protection actually blocks it. - Copy
/root/iac-plan, including the state file, to/root/iac-plan-bypass(cp -a). In the copy'smain.tf, delete thelocal_file.motdresource block entirely, including its lifecycle, and savetofu plan -out=/root/iac-plan-bypass/bypass.tfplanin the copy. Do not apply it. Leave the original as it is. - Create
/root/iac-plan/review.sh <작업디렉터리>(the placeholder is the working directory). It saves that directory's plan to a temporary file, counts withsummary.sh, and finishes withCLEANand 0 if there are no changes, a line starting withAUTOand 2 if there are only additions and updates, a line starting withREVIEWand 3 if there is even one replacement or deletion, andERRORand 1 if the plan fails. Leave no plan file in the working directory. The grader checks with the original (CLEAN), the bypass copy (REVIEW), and temporary directories it builds itself.
Notes
- The Pod contains OpenTofu 1.9.0 as
tofu(the same binary can also be calledterraform), and the local, random, null, and tls providers are fetched from a mirror inside the Pod. No internet is needed. - Plan JSON:
tofu show -json <계획파일>(the placeholder is the plan file) — the plan file must be read from the working directory that created it, so that it can find the provider schemas. - Common mistake: putting
set -ein the script so that it dies early at the 2 of -detailed-exitcode. - Common mistake: counting a replacement twice, as one addition and one deletion.
- tofu plan (-detailed-exitcode) · tofu apply (Saved Plan Mode) · JSON Output Format · lifecycle · Terraform plan
Declare three resources and apply once
In /root/iac-plan/main.tf, require the local and random providers and declare three resources. local_file.motd writes to ${path.module}/out/motd.txt the one line hello v1 (with a trailing newline), terraform_data.release has input v1, and random_pet.suffix has keepers team = "blue". Apply with tofu init and tofu apply -auto-approve.
The local and random providers are fetched from the mirror inside the Pod, so init works even without internet. terraform_data is a built-in resource that is used without a provider. After applying, check the addresses the state knows with tofu state list.
A gate that turns three exit codes into words
Create /root/iac-plan/gate.sh <작업디렉터리> (the placeholder is the working directory). In that directory, it runs tofu plan -detailed-exitcode; if there are no changes it prints clean and exits with 0, if there are changes it prints changes and exits with 2, and on an error it prints error and exits with 1. The output is only that one word on one line. The grader creates three temporary directories — no changes, changes, and a configuration error — and runs this script on them.
-detailed-exitcode gives three values: 0, 1, and 2. If you put set -e, the script ends early at 2. For a command that only makes a plan, you can add -lock=false so that it does not block someone else's work.
You fixed one attribute and it gets created anew
In main.tf, change three values: the motd content to hello v2, the release input to v2, and the suffix keepers team to green. Do not apply. Save the plan with tofu plan -out=/root/iac-plan/change.tfplan, then in /root/iac-plan/actions.txt write one line per changing resource in the form <주소> <create|update|replace|delete> (address and action). Judge which is an in-place update and which is a replacement from the symbols in the plan output and the actions in tofu show -json.
~ is an in-place update, and -/+ is a replacement that deletes and recreates. In JSON, you can tell by whether actions is ["update"] or ["delete","create"]. Which attributes force a replacement is decided by the provider — look in the plan output for the # forces replacement marker.
Only the reviewed plan is applied, and a stale plan is rejected
First apply the saved plan as it is with tofu apply change.tfplan. Then change the motd content to hello v3 and save with tofu plan -out=/root/iac-plan/late.tfplan. Before applying this plan, create a situation in which someone else stepped in: run tofu apply -auto-approve -replace=random_pet.suffix -target=random_pet.suffix first (it replaces only the suffix and does not touch motd). Now run tofu apply late.tfplan and save the standard output and errors together to /root/iac-plan/stale.txt.
A saved plan contains the state at the time the plan was made. If the state has changed in between, OpenTofu does not apply the plan. -replace is an option that makes it replace that resource without changing the configuration, and -target narrows the scope of the plan to that resource (without -target, the v3 change in main.tf would be applied along with it). If you add -no-color, the saved file is easier to read.
Count the actions in the plan JSON
Create /root/iac-plan/summary.sh <계획JSON> (the placeholder is the plan JSON). It takes the tofu show -json result file and prints one line, create=<수> update=<수> replace=<수> delete=<수> (the placeholders are counts). A replacement (a resource that has both a delete and a create) is counted only as one replace. Then save the plan of the current working directory to /root/iac-plan/now.json (tofu plan -out, then tofu show -json), and save its summary to /root/iac-plan/now-summary.txt.
Each element of resource_changes has a change.actions array. A resource with no change is ["no-op"], so it is counted in none of the columns. In jq you can compare arrays with ==.
We added protection, and the replacement was blocked too
To local_file.motd, add lifecycle { prevent_destroy = true }. With the content still hello v3, run tofu plan and save the output (including the error) to /root/iac-plan/guard.txt. Then change the motd content back to hello v2 so that the plan is clean again (the plan of the working directory must show no changes). The grader builds a plan on a copy with the motd content changed to check that the protection actually blocks it.
Changing the content of a local_file is a replacement. A replacement includes a deletion, so for a resource with prevent_destroy the plan itself ends in an error. Read the resource address and the reason from the error message.
If you delete the block, the protection disappears with it
Copy /root/iac-plan, including the state file, to /root/iac-plan-bypass (cp -a). In the copy's main.tf, delete the local_file.motd resource block entirely, including its lifecycle, and save tofu plan -out=/root/iac-plan-bypass/bypass.tfplan in the copy. Do not apply it. Leave the original as it is.
prevent_destroy is effective only while it is in the configuration. If you delete the block, that setting disappears too, so the plan proposes the deletion without an error. This difference tells you what to look for in code review.
Hand plans that mix in destruction to a person
Create /root/iac-plan/review.sh <작업디렉터리> (the placeholder is the working directory). It saves that directory's plan to a temporary file, counts with summary.sh, and finishes with CLEAN and 0 if there are no changes, a line starting with AUTO and 2 if there are only additions and updates, a line starting with REVIEW and 3 if there is even one replacement or deletion, and ERROR and 1 if the plan fails. Leave no plan file in the working directory. The grader checks with the original (CLEAN), the bypass copy (REVIEW), and temporary directories it builds itself.
This is a problem of chaining the gate.sh and summary.sh from the earlier steps. Create the temporary file with mktemp and remove it with trap. You cannot get the JSON without a plan file — save with -out and then show.