TT Lab
Get started
Learn Learning paths Courses

Terraform/OpenTofu Fundamentals

A Read-Only Block Ended Up in the State File

Continue in TT Lab

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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).
  8. 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.

Notes

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.