Terraform/OpenTofu Fundamentals
A Read-Only Block Ended Up in the State File
Goal
You confirm directly, in three places — state, plan, and destruction — how a data block differs from a resource. You cause, one by one with OpenTofu, when the read happens, what it does not own, and what happens when the original is missing or changes.
Why it matters
Data blocks appear in configuration files as often as resources, and if you read the two as the same thing, accidents happen quietly. Data neither creates nor deletes its target — even if you run destroy, the original stays as it is, which is what makes a configuration that reads and uses something another team made safe. In exchange, data does not trust the value stored in the state and reads again on every plan. If the original changes, that is not drift but a change of input, and the downstream can be replaced entirely. If the name of what is to be read is not yet decided or depends_on is attached, the read is pushed back to apply time, and then the plan shows the value wholesale as "unknown," which makes review difficult. If you experience these four things by hand, your eyes will stop at the data block when reading other people's configurations.
Steps
- Create
/root/tfb-data/base/seed.txtby hand with the single linealpha, and inmain.tfin the same directory, putdata "local_file" "seed", which reads that file, and a block that writes its content tocopy.txt, namelyresource "local_file" "copy". Run init and apply, and the plan must be clean. - Open
/root/tfb-data/base/terraform.tfstateand find the address of the entry whose mode is data and the entry whose mode is managed, and write two lines to/root/tfb-data/shape.txt:data=<그 주소>andmanaged=<그 주소>(the placeholders are that address). The address includes the mode prefix, likedata.local_file.seed. - In
/root/tfb-data/own/, put a configuration of the same shape, but makeseed.txtthe single lineowned. After creatingcopy.txtwith init and apply, runtofu destroy -auto-approveand save its output to/root/tfb-data/own-destroy.txt. Do not apply again. - In
/root/tfb-data/deferred/main.tf, putrandom_pet.name(length 2); a block that writesmade-<이름>.txtwith that name in it (the placeholder is the name), namelylocal_file.made; a block that reads that file, namelydata.local_file.back; and a block that writes the read value toecho.txt, namelylocal_file.echo. After init, save the plan output to/root/tfb-data/deferred-plan.txtand apply. - First create
/root/tfb-data/gate/ready.txtwith the single lineready. Inmain.tfin the same directory, putnull_resource.prepare(triggers v = "1"); withdepends_on = [null_resource.prepare]attached,data.local_file.ready; and a block that writes the read value tomirror.txt, namelylocal_file.mirror; then run init. Save the plan output to/root/tfb-data/gate-plan.txtand then apply. - In
/root/tfb-data/missing/main.tf, put, reading the nonexistent fileabsent.txt,data.local_file.absent, and the outputabsentthat exports its content, and run init. Run the plan and save the error output to/root/tfb-data/missing-plan.txt. It is fine if the command fails. Do not createabsent.txt. - In
/root/tfb-data/tfdata/main.tf, putterraform_data.note(input "v1") and the outputnotethat exports its output, and run init and apply. Then change the input to "v2", save the plan output to/root/tfb-data/tfdata-plan.txt, and apply. Finally, write two lines to/root/tfb-data/mode-compare.txt:terraform_data=<tfdata 상태에서 읽은 mode>andlocal_file_seed=<base 상태에서 읽은 mode>(the placeholders are the mode read from the tfdata state and the mode read from the base state). - Change
/root/tfb-data/base/seed.txtto the single linebeta, run plan in/root/tfb-data/base, save the output to/root/tfb-data/data-change.txt, and apply. Confirm why the plan comes out as a replacement (replace), and at the end the plan must be clean again.
Notes
- The Pod has OpenTofu 1.9.0 and local, random, null, and tls provider mirrors, so it runs without internet. Data sources that need the internet cannot be used in this Pod, so all of them are reproduced with local files.
- Save plan output with
-no-colorso the grader can read it easily. If color codes get mixed in, it is messy to human eyes too. - Common mistake: creating absent.txt in step 6 to get rid of the error. The task of this step is to leave a failing plan.
- Common mistake: applying again after the destroy in step 3. It is a step where you leave the deleted spot as it is and check whether the original remains.
- Data Sources · depends_on · terraform_data · local_file data source · tofu plan
Read an existing file with data
Create /root/tfb-data/base/seed.txt by hand with the single line alpha, and in main.tf in the same directory, put data "local_file" "seed", which reads that file, and a block that writes its content to copy.txt, namely resource "local_file" "copy". Run init and apply, and the plan must be clean.
A data block does not create but only reads. Reference the value you read as data.<타입>.<이름>.<속성> (type, name, attribute). The file data source of the local provider puts the file content in the content attribute.
Separate what is read from what is made, in the state
Open /root/tfb-data/base/terraform.tfstate and find the address of the entry whose mode is data and the entry whose mode is managed, and write two lines to /root/tfb-data/shape.txt: data=<그 주소> and managed=<그 주소> (the placeholders are that address). The address includes the mode prefix, like data.local_file.seed.
Each entry of the state JSON has mode, type, and name. If the mode is data, data. is attached in front of the address, and if managed, it is not. Compare with the output of tofu state list.
Even after destroy, an original that was only read remains
In /root/tfb-data/own/, put a configuration of the same shape, but make seed.txt the single line owned. After creating copy.txt with init and apply, run tofu destroy -auto-approve and save its output to /root/tfb-data/own-destroy.txt. Do not apply again.
destroy deletes only what the state owns. A file that was only read with data leaves a record in the state but is not owned. The state just before the destroy remains as terraform.tfstate.backup.
If the target to read does not exist yet, the read is pushed back to apply
In /root/tfb-data/deferred/main.tf, put random_pet.name (length 2); a block that writes made-<이름>.txt with that name in it (the placeholder is the name), namely local_file.made; a block that reads that file, namely data.local_file.back; and a block that writes the read value to echo.txt, namely local_file.echo. After init, save the plan output to /root/tfb-data/deferred-plan.txt and apply.
If an argument of the data source is a value that cannot be known at plan time, the read is deferred. Read as it is how the line for that data source is written in the plan output.
If you attach depends_on, even an existing file cannot be read at plan time
First create /root/tfb-data/gate/ready.txt with the single line ready. In main.tf in the same directory, put null_resource.prepare (triggers v = "1"); with depends_on = [null_resource.prepare] attached, data.local_file.ready; and a block that writes the read value to mirror.txt, namely local_file.mirror; then run init. Save the plan output to /root/tfb-data/gate-plan.txt and then apply.
Even if the original already exists, if there is a depends_on, the tool does not read before that dependency is done. Compare it with the plan output from step 1 and the difference shows up in a single line.
If the original is missing, the plan itself fails
In /root/tfb-data/missing/main.tf, put, reading the nonexistent file absent.txt, data.local_file.absent, and the output absent that exports its content, and run init. Run the plan and save the error output to /root/tfb-data/missing-plan.txt. It is fine if the command fails. Do not create absent.txt.
The read of a data source happens at the plan stage. So if it cannot be read, it cannot even get to apply and the plan stops. Save the title and the cause line of the error message as they are.
terraform_data is managed, not data
In /root/tfb-data/tfdata/main.tf, put terraform_data.note (input "v1") and the output note that exports its output, and run init and apply. Then change the input to "v2", save the plan output to /root/tfb-data/tfdata-plan.txt, and apply. Finally, write two lines to /root/tfb-data/mode-compare.txt: terraform_data=<tfdata 상태에서 읽은 mode> and local_file_seed=<base 상태에서 읽은 mode> (the placeholders are the mode read from the tfdata state and the mode read from the base state).
By name alone it looks like a data source, but the answer comes out when you look at the mode in the state. The action word in the plan output also differs from a data source — a data source is read, and this is a resource with a lifecycle.
If the original changes, it is not drift but a change of input
Change /root/tfb-data/base/seed.txt to the single line beta, run plan in /root/tfb-data/base, save the output to /root/tfb-data/data-change.txt, and apply. Confirm why the plan comes out as a replacement (replace), and at the end the plan must be clean again.
A data source does not trust the value stored in the state and reads again on every plan. So when the original changes, it is caught not as the state having diverged but as the input of the downstream resource having changed. The content of a local_file is an attribute that cannot be modified in place.