Terraform/OpenTofu Fundamentals
Dissecting the Dependency Graph and the State File
Goal
You confirm directly in the state file how references build the dependency graph, and learn to read the state in two ways, for people and for machines, and extract metadata.
Why it matters
After using Terraform for a few days, the question "why is the order like this?" is sure to come. The answer is always the graph. The tool does not look at the order written in the file but only at which resource uses the value of which resource. So if you reference a value, an order arises, and if you copy a value, the order disappears. depends_on is an escape hatch for expressing an order that does not show up in values, but once it becomes a habit, the graph gets thick, execution slows down, and recreation spreads sideways. Meanwhile, this relationship is recorded in the state file as dependencies, and that is the basis for deleting in the correct reverse order even after you have deleted a resource from the code. Here you can also understand why editing the state by hand is dangerous.
Steps
- Create the configuration in
/root/tf/stateand initialize. Declare a resource namedseedof typerandom_petand a resource namedchildof typelocal_file, and make the content ofchildreference the value ofrandom_pet.seed. Onchild, do not usedepends_on. After applying, forlocal_file.childin the state, itsdependenciesmust containrandom_pet.seed. - Add a resource named
markerof typelocal_file, and in/root/tf/state/main.tfwritedepends_on = [local_file.child]to pin the order. After applying, forlocal_file.markerin the state, itsdependenciesmust showlocal_file.child. - Save the list of resource addresses registered in the state to
/root/tf/state/out/state-list.txt. The three linesrandom_pet.seed,local_file.child, andlocal_file.markermust each be in there exactly as those strings. - Save the JSON representation of the state to
/root/tf/state/out/state.json. There must be 3 or more resources under.values.root_module.resources, and among them there must be an entry whoseaddressislocal_file.child. - Create
/root/tf/state/out/meta.json. It has four keys,serial,lineage,version, andresource_count; the first three must be the same as the values in/root/tf/state/terraform.tfstate, andresource_countmust be the same as the length of the state'sresourcesarray. - Delete the file that
childmade directly in the shell without going through Terraform, then produce a plan in that state and save it to/root/tf/state/out/drift-plan.txt. The output must say it will recreatelocal_file.child, and it must not be "no changes." - Read
/root/tf/state/.terraform.lock.hcl, summarize the provider name and version pinned there on one line, and save it to/root/tf/state/out/lock-note.txt. It must include both the namelocaland a three-part version number such as2.5.3. - Add three resources named
stage_a,stage_b, andstage_c, of typelocal_file, withstage_breferencing the value ofstage_aandstage_creferencing the value ofstage_b. After applying, write to/root/tf/state/out/chain.txt, in the order they are created,local_file.stage_a,local_file.stage_b, andlocal_file.stage_c, one per line, for a total of 3 lines.
Notes
terraform state listshows only addresses, andterraform show -jsonshows the whole state in a machine-readable format. The latter pairs withjq.- For step 5, if you use
jqto extract.serial,.lineage,.version, and(.resources|length)and assemble a new JSON, you avoid the mistakes of copying by hand. - The plan output in step 6 comes out on standard output. Save it to a file before applying again.
- Common mistake 1: attaching
depends_onto every resource to get the order right. If there is a reference, the dependency already exists, and a duplicatedepends_ononly thickens the graph. - Common mistake 2: leaving spaces or quotes around the address in steps 3 and 8. Grading checks whether the whole line matches exactly.
Create a dependency from a reference alone
Create the configuration in /root/tf/state and initialize. Declare a resource named seed of type random_pet and a resource named child of type local_file, and make the content of child reference the value of random_pet.seed. On child, do not use depends_on. After applying, for local_file.child in the state, its dependencies must contain random_pet.seed.
If you use another resource's attribute in an expression, that itself is the order declaration. In this step do not use depends_on, and check by whether the state's dependencies array gets filled.
Pin the order with depends_on
Add a resource named marker of type local_file, and in /root/tf/state/main.tf write depends_on = [local_file.child] to pin the order. After applying, for local_file.marker in the state, its dependencies must show local_file.child.
There is an argument you use when you do not use a value but only need an order. Its value is a list of resource addresses and is not wrapped in quotes.
Extract the list of state addresses
Save the list of resource addresses registered in the state to /root/tf/state/out/state-list.txt. The three lines random_pet.seed, local_file.child, and local_file.marker must each be in there exactly as those strings.
There is a subcommand that shows only the addresses registered in the state, one per line. The file must not contain any decoration other than the addresses.
Extract the state as JSON
Save the JSON representation of the state to /root/tf/state/out/state.json. There must be 3 or more resources under .values.root_module.resources, and among them there must be an entry whose address is local_file.child.
The output a person reads and the output a machine reads are different options. Trace with jq the path at which the resource list lies inside the JSON.
Record the state metadata
Create /root/tf/state/out/meta.json. It has four keys, serial, lineage, version, and resource_count; the first three must be the same as the values in /root/tf/state/terraform.tfstate, and resource_count must be the same as the length of the state's resources array.
Four keys are needed. Do not copy the values by eye; if you extract them directly from the state file and assemble them, you cannot go wrong. The resource count is the length of the array.
See whether a file deleted by hand is caught in the plan
Delete the file that child made directly in the shell without going through Terraform, then produce a plan in that state and save it to /root/tf/state/out/drift-plan.txt. The output must say it will recreate local_file.child, and it must not be "no changes."
If you leave the code as it is and remove only the product, the difference shows up in the query stage. Save the plan output first, before applying again.
Read the provider version from the lock file
Read /root/tf/state/.terraform.lock.hcl, summarize the provider name and version pinned there on one line, and save it to /root/tf/state/out/lock-note.txt. It must include both the name local and a three-part version number such as 2.5.3.
The lock file has the provider block, the confirmed version, and the hashes. Summarize it on one line while thinking about what the hash guarantees.
Build a 3-stage dependency chain and record the order
Add three resources named stage_a, stage_b, and stage_c, of type local_file, with stage_b referencing the value of stage_a and stage_c referencing the value of stage_b. After applying, write to /root/tf/state/out/chain.txt, in the order they are created, local_file.stage_a, local_file.stage_b, and local_file.stage_c, one per line, for a total of 3 lines.
If you link them so that the next stage references the result of the previous stage, it becomes a chain. The order to record is not the order written in the code but the order in which they are created.