TT Lab
Get started
Learn Learning paths Courses

Infrastructure as Code

Monday's apply tried to delete the rule fixed by hand on Friday night

Continue in TT Lab

Goal

Read changes made outside the code separately with plan -refresh-only, find in the plan JSON what would disappear if applied as is, then carry out the three resolutions of absorbing, reverting, and excluding yourself, and build a detection script to run every day.

Why it matters

A declarative tool takes the code as the truth, so even a correct fix made in the console is reverted on the next apply. So when you find drift, you first read what will disappear, and a person must decide whether to absorb or revert it. ignore_changes is not a switch that hides all drift, and a change to an attribute the provider does not read does not appear in the plan at all. Only by knowing this limit can you tell "it matches" from "it isn't visible" when the detection result is empty.

Steps

  1. In /root/iac-drift/main.tf, put a rules variable (default ["allow 443"]); a resource that writes one line per rule to out/firewall.rules, namely local_file.fw; and a resource that writes to out/motd.txt the single line maintenance window: sun 02:00, namely local_file.motd; then run init and apply.
  2. During incident response, someone appended by hand to the end of /root/iac-drift/out/firewall.rules the line allow 8443 (add it yourself). Without touching the code, run tofu plan -refresh-only, which shows only the changes that arose from outside, and save the output to /root/iac-drift/drift.txt.
  3. Without changing the code, save a plan with tofu plan -out=/root/iac-drift/monday.tfplan (do not apply). Compare what that plan will write to firewall.rules with the current file, and write to /root/iac-drift/lost.txt only the lines that would disappear if applied.
  4. You judged the emergency fix to be correct. Change the default of rules to ["allow 443", "allow 8443"] and apply. After that, firewall.rules must have two lines and the plan must be clean.
  5. This time someone changed out/motd.txt to maintenance window: none (change it yourself). This is a wrong change. Before reverting, save the output of tofu plan -refresh-only to /root/iac-drift/revert-drift.txt, and with the code left as it is, apply to revert the notice to the code's content.
  6. To main.tf, add a resource that writes to out/cache.conf the content cache v1 and carries lifecycle { ignore_changes = [content] }, namely local_file.cache, and apply. (a) After changing the file by hand to cache tampered, save the output of tofu plan to /root/iac-drift/ignore.txt and revert with apply. (b) Change the code's content to cache v2 and see whether the plan is clean (do not apply). (c) After chmod 600 out/firewall.rules, write the exit code of tofu plan -detailed-exitcode to /root/iac-drift/perm.txt as perm=<코드> (the placeholder is the code).
  7. Create /root/iac-drift/detect.sh <작업디렉터리> (the placeholder is the working directory). It saves plan -refresh-only as a plan file and collects the addresses of resources changed from outside, and finishes with OK and 0 if there is no drift, a single line DRIFT <주소들(공백 구분, 정렬)> and 2 if there is (the placeholder is the addresses, space-separated and sorted), and ERROR and 1 if it fails. Leave no files in the working directory. The grader checks by changing files in a copy of /root/iac-drift.

Notes

Baseline: apply the firewall rules and the notice from code

In /root/iac-drift/main.tf, put a rules variable (default ["allow 443"]); a resource that writes one line per rule to out/firewall.rules, namely local_file.fw; and a resource that writes to out/motd.txt the single line maintenance window: sun 02:00, namely local_file.motd; then run init and apply.

Use a for expression to attach a newline to each rule and join them together with join. The plan must be empty after applying for this to be the baseline.

See Friday night's emergency fix in the plan

During incident response, someone appended by hand to the end of /root/iac-drift/out/firewall.rules the line allow 8443 (add it yourself). Without touching the code, run tofu plan -refresh-only, which shows only the changes that arose from outside, and save the output to /root/iac-drift/drift.txt.

-refresh-only does not plan changes to the code and shows only the difference between the actual and the state. The local_file provider reports a file as "deleted" if its content differs from the record — confirm in the output that it does not look like one attribute changed.

What disappears if you just apply on Monday

Without changing the code, save a plan with tofu plan -out=/root/iac-drift/monday.tfplan (do not apply). Compare what that plan will write to firewall.rules with the current file, and write to /root/iac-drift/lost.txt only the lines that would disappear if applied.

In resource_changes of the plan JSON, the change.after.content of local_file.fw is the file content after applying. The lines that exist only in the current file are the lines that will disappear. You can get a line-by-line difference with comm or grep -vxF -f.

It was a correct fix — absorb it into the code

You judged the emergency fix to be correct. Change the default of rules to ["allow 443", "allow 8443"] and apply. After that, firewall.rules must have two lines and the plan must be clean.

Absorbing is making the code match reality. Because this provider saw the file as "deleted," even the plan after fixing the code comes out as a creation, not an update — the content to be written is the same as the current file, so the result is the same.

It was a wrong fix — revert it according to the code

This time someone changed out/motd.txt to maintenance window: none (change it yourself). This is a wrong change. Before reverting, save the output of tofu plan -refresh-only to /root/iac-drift/revert-drift.txt, and with the code left as it is, apply to revert the notice to the code's content.

Reverting is making reality match the code. Checking why such a change was made before reverting is part of the procedure — here, you leave the trace of that by saving the drift output.

What ignore_changes blocks, what it cannot block, and what is not seen at all

To main.tf, add a resource that writes to out/cache.conf the content cache v1 and carries lifecycle { ignore_changes = [content] }, namely local_file.cache, and apply. (a) After changing the file by hand to cache tampered, save the output of tofu plan to /root/iac-drift/ignore.txt and revert with apply. (b) Change the code's content to cache v2 and see whether the plan is clean (do not apply). (c) After chmod 600 out/firewall.rules, write the exit code of tofu plan -detailed-exitcode to /root/iac-drift/perm.txt as perm=<코드> (the placeholder is the code).

ignore_changes means "do not plan an update even if the value written in the configuration changes." It cannot prevent the provider from reading the real object and judging that it is gone. And a change to an attribute the provider does not read is not caught as drift.

Drift detection that runs every morning

Create /root/iac-drift/detect.sh <작업디렉터리> (the placeholder is the working directory). It saves plan -refresh-only as a plan file and collects the addresses of resources changed from outside, and finishes with OK and 0 if there is no drift, a single line DRIFT <주소들(공백 구분, 정렬)> and 2 if there is (the placeholder is the addresses, space-separated and sorted), and ERROR and 1 if it fails. Leave no files in the working directory. The grader checks by changing files in a copy of /root/iac-drift.

The plan JSON has resource_drift as well as resource_changes — the changes that arose from outside are held there separately. -detailed-exitcode can be used together with -refresh-only.