TT Lab
Get started
Learn Learning paths Courses

Infrastructure as Code

Two repositories kept reverting the same config file

Continue in TT Lab

Goal

Reproduce two states fighting over one resource and settle ownership with a removed block, then bring in existing values without deleting them, through an import block, configuration generation, and the CLI import, and start managing them with an empty plan.

Why it matters

When you adopt IaC, you do not start from a blank slate. Resources made by hand already exist, and if, while moving them into code, you merely copy their shape, the tool creates new ones and breaks the places that were using the existing values. So you must first register them in the state with import and polish the code until the plan is empty before you are done. Conversely, if two codebases manage one resource, they revert each other without a single error, so when handing over ownership you must use the method of removing it only from the state, rather than deleting the resource block (= deletion).

Steps

  1. In each of /root/iac-own/team-a/main.tf and /root/iac-own/team-b/main.tf, put a local_file.conf. Both manage /root/iac-own/shared/app.conf, and the content is the single line owner=team-a and owner=team-b respectively. After running init in each directory, apply in the order team-a, team-b, team-a, team-b, and each time you apply, append the file content as one line to /root/iac-own/flip.txt.
  2. team-b has decided to hand over ownership. Delete team-b's resource block and put in a removed { from = local_file.conf } block. In team-b, save the output of tofu plan to /root/iac-own/forget.txt and then apply, and then apply team-a. The file must remain (with team-a's content), there must be no local_file in team-b's state, and the plans of both directories must be clean.
  3. Write a record of the hand-made resources in /root/iac-own/legacy.env: DB_PASSWORD=Lb7qK2mZx9Wc4Rt8Yp3N, PORT=8123, PORT_RANGE=8000,8999, INSTANCE_ID=6f1c2a4e-3b7d-4c9a-8e2f-1a2b3c4d5e6f, TOKEN=abcDEF123 (one per line). Then in /root/iac-own/naive/main.tf, without import, write random_password.db (length 20), random_integer.port (8000 to 8999), and random_uuid.instance, and only save with tofu plan -out=/root/iac-own/naive/naive.tfplan (do not apply).
  4. In /root/iac-own/adopt/main.tf, put only the random provider requirement and three import blocks: for random_password.db, the password from legacy.env; for random_integer.port, the format <값>,<최소>,<최대> (value, minimum, maximum); and for random_uuid.instance, the UUID is the identifier. Do not write resource blocks, and extract the configuration with tofu plan -generate-config-out=generated.tf. The plan must be 3 to import, 0 to add, 0 to change, 0 to destroy (do not apply).
  5. Move the three resources in generated.tf to adopt/main.tf, dropping lines whose value is null and also arguments equal to the defaults, to trim it down (keep password length 20 and port range 8000 and 8999). Delete generated.tf. Confirm that the plan is 3 to import, 0 to add, 0 to change, 0 to destroy and then apply. The grader checks that the values in the state are the same as in legacy.env and that the plan afterward is clean.
  6. Bring the TOKEN in legacy.env in as random_string.token. This time do not use an import block; first put a resource block (length 9) in /root/iac-own/adopt/token.tf, and then register it in the state with the tofu import command. The plan after that must be clean.
  7. Create /root/iac-own/owners.sh <작업디렉터리>... (the placeholder is the working directories). From each directory's state, collect the file paths managed by local_file and local_sensitive_file as absolute paths, and if the same path is in two or more directories, print one line per path, CONFLICT <경로> <디렉터리들(절대 경로, 정렬, 공백 구분)>, and finish with 3; if there are none, finish with OK and 0 (the placeholders are the path and the directories, as absolute paths, sorted, space-separated). A relative-path filename is resolved relative to that working directory. The grader checks with team-a and team-b (now OK) and a conflicting directory it builds itself.

Notes

Two teams keep reverting the same file in turn

In each of /root/iac-own/team-a/main.tf and /root/iac-own/team-b/main.tf, put a local_file.conf. Both manage /root/iac-own/shared/app.conf, and the content is the single line owner=team-a and owner=team-b respectively. After running init in each directory, apply in the order team-a, team-b, team-a, team-b, and each time you apply, append the file content as one line to /root/iac-own/flip.txt.

The two states do not know of each other's existence. When one side writes, the other side's next plan sees that file as "changed from outside" (for local_file, "deleted"), and applying reverts it to its own content. The reason to end with team-b is so that in the next step team-b gives up ownership while its own record is still alive. A fight like this continues quietly, without any error.

One side gives up ownership — remove only from the state, without deleting

team-b has decided to hand over ownership. Delete team-b's resource block and put in a removed { from = local_file.conf } block. In team-b, save the output of tofu plan to /root/iac-own/forget.txt and then apply, and then apply team-a. The file must remain (with team-a's content), there must be no local_file in team-b's state, and the plans of both directories must be clean.

If you delete only the resource block, the tool plans it as a deletion — then team-b deletes the file team-a uses. A removed block means "just stop managing it," so the real object is left as it is. Read the sentences in the plan output to confirm the difference.

If you only copy the shape of an existing value, a new one is created

Write a record of the hand-made resources in /root/iac-own/legacy.env: DB_PASSWORD=Lb7qK2mZx9Wc4Rt8Yp3N, PORT=8123, PORT_RANGE=8000,8999, INSTANCE_ID=6f1c2a4e-3b7d-4c9a-8e2f-1a2b3c4d5e6f, TOKEN=abcDEF123 (one per line). Then in /root/iac-own/naive/main.tf, without import, write random_password.db (length 20), random_integer.port (8000 to 8999), and random_uuid.instance, and only save with tofu plan -out=/root/iac-own/naive/naive.tfplan (do not apply).

Even if the shape of the configuration is the same, if there is no record in the state, it is "not there yet" to the tool. Applying creates a new value different from the existing password, and everywhere that used that value breaks. In the plan's Plan line, see that import is 0.

Use import blocks and have the tool extract the configuration

In /root/iac-own/adopt/main.tf, put only the random provider requirement and three import blocks: for random_password.db, the password from legacy.env; for random_integer.port, the format <값>,<최소>,<최대> (value, minimum, maximum); and for random_uuid.instance, the UUID is the identifier. Do not write resource blocks, and extract the configuration with tofu plan -generate-config-out=generated.tf. The plan must be 3 to import, 0 to add, 0 to change, 0 to destroy (do not apply).

The import identifier format differs for each resource — see the Import section of the provider documentation. Configuration generation is an experimental feature, and if the file already exists, it does not overwrite it and raises an error.

Trim the extracted configuration and bring it in with an empty plan

Move the three resources in generated.tf to adopt/main.tf, dropping lines whose value is null and also arguments equal to the defaults, to trim it down (keep password length 20 and port range 8000 and 8999). Delete generated.tf. Confirm that the plan is 3 to import, 0 to add, 0 to change, 0 to destroy and then apply. The grader checks that the values in the state are the same as in legacy.env and that the plan afterward is clean.

If, while trimming, you change an argument that forces replacement (for example, the maximum of a range), the plan turns into "replace after import" — if you apply it as is, the imported value disappears immediately. The criterion is whether add and destroy in the Plan line are 0.

Bring in one more with a command instead of a block

Bring the TOKEN in legacy.env in as random_string.token. This time do not use an import block; first put a resource block (length 9) in /root/iac-own/adopt/token.tf, and then register it in the state with the tofu import command. The plan after that must be clean.

The imperative import changes the state immediately without a plan. So the configuration must exist first, and no plan is left to review — this is why the import block was made to go through code review and a plan.

A check that finds files with two owners

Create /root/iac-own/owners.sh <작업디렉터리>... (the placeholder is the working directories). From each directory's state, collect the file paths managed by local_file and local_sensitive_file as absolute paths, and if the same path is in two or more directories, print one line per path, CONFLICT <경로> <디렉터리들(절대 경로, 정렬, 공백 구분)>, and finish with 3; if there are none, finish with OK and 0 (the placeholders are the path and the directories, as absolute paths, sorted, space-separated). A relative-path filename is resolved relative to that working directory. The grader checks with team-a and team-b (now OK) and a conflicting directory it builds itself.

Get the filename from the values of tofu show -json. Even a relative path with the same name is a different file if the directory differs. To pick out only the duplicates, sort | uniq -d is convenient.