Two repositories kept reverting the same config file
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
- In each of
/root/iac-own/team-a/main.tfand/root/iac-own/team-b/main.tf, put alocal_file.conf. Both manage/root/iac-own/shared/app.conf, and the content is the single lineowner=team-aandowner=team-brespectively. 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. - 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 oftofu planto/root/iac-own/forget.txtand 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. - 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, writerandom_password.db(length 20),random_integer.port(8000 to 8999), andrandom_uuid.instance, and only save withtofu plan -out=/root/iac-own/naive/naive.tfplan(do not apply). - 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 withtofu plan -generate-config-out=generated.tf. The plan must be3 to import, 0 to add, 0 to change, 0 to destroy(do not apply). - Move the three resources in
generated.tftoadopt/main.tf, dropping lines whose value isnulland 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 is3 to import, 0 to add, 0 to change, 0 to destroyand 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. - 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 thetofu importcommand. The plan after that must be clean. - 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 withOKand 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
- The Pod has OpenTofu 1.9.0 and local and random provider mirrors, so it runs without internet.
- The import identifier format differs per resource: for random_password it is the password value, for random_integer it is
<값>,<최소>,<최대>(value, minimum, maximum), for random_uuid the UUID, and for random_string the string value (the Import section of the provider documentation). - In 1.9.0 in this Pod, the removed block accepts only from and always behaves as "remove only from the state." The latest OpenTofu documentation recommends stating the same meaning explicitly with
lifecycle { destroy = false }, but 1.9.0 rejects that block (measured). - Common mistake: handing over ownership by deleting only the resource block, so that deletion of the real object is planned. Common mistake: after configuration generation, putting the same resource twice in generated.tf and main.tf.
- Import · Generating Configuration · tofu import · Removing Resources · random_id (example of the import format)
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.