Terraform/OpenTofu Fundamentals
Three Things That Filter Before Apply
In one line
fmt looks at formatting, and validate looks at structure. Neither can see values, and console fills that gap.
Why these checks were needed separately
Accidents you learn about only by applying are expensive. To make a plan you have to download providers, read the state, and, if it is remote, even take the lock. Yet a good share of the mistakes that actually happen can be found just by reading the configuration file. Things like referencing a variable that was not declared, leaving out a required argument, or putting a list where a string belongs. Going all the way to a plan for these is a waste.
Formatting is different in nature. The problem is not that something is wrong but that everything is different. Once a team using the configuration language starts deciding indentation rules in meetings, that time never comes back, and the resulting review diffs fill up not with content but with whitespace. So the tool fixed one right answer. Removing room for debate is the design intent of this command.
How it works
tofu fmt fixes files by default. If you want only a verdict, use check mode, and then the exit code splits three ways.
tofu fmt -check -diff -recursive # 고치지 않고 판정만
# 0 : 고칠 것이 없다
# 3 : 고칠 것이 있다
# 2 : 파일을 읽을 수조차 없다(문법 오류)
When you use it in a gate, distinguishing 2 from 3 makes the message much friendlier. 3 is "run the format command once," and 2 is "this file cannot be parsed at all." To look into subdirectories you need the recursive option — without it, it looks only at the files in the root.
tofu validate needs provider schemas, so you have to run init first. If you run it in a directory that has not been initialized, it says there is no provider. It catches three kinds.
Reference to undeclared input variable 선언 안 한 var.xxx 참조
Missing required argument 필수 인자 누락
Incorrect attribute value type 스키마와 타입 불일치
There is also a machine-readable format. If you give -json, it outputs valid, error_count, warning_count, and a list of diagnostics, so CI can judge with it as is or attach it to a review.
What matters is what it cannot catch. validate sees neither the state nor the real object, and it does not know variable values. So the following configuration passes without any problem.
variable "seed_path" { type = string }
resource "local_file" "out" {
filename = "${path.module}/out.txt"
content = file(var.seed_path)
}
The structure is perfect. But when you try to make a plan, it gets blocked in two places. If you give no value, it says "the variable is not set," and if you give a nonexistent path, it says "there is no file at that path." Both are facts outside the configuration file, so static checks cannot know them. Passing validate does not mean it is okay to apply.
The third tool, tofu console, reads the variables and locals of the current directory and evaluates expressions one line at a time. If you pipe a file into it, you can use it non-interactively too, and it stops at the first error and ends with a non-zero code. It is good for checking confusing conversions.
"5" + 5 -> 10 산술에서는 문자열이 수로 변환된다
1 == "1" -> false 같다 비교에서는 변환되지 않는다
What you see in the field
The most common case is a CI with the gate order built backward. If you run the structure check first, one file with broken syntax makes a flood of unexplained errors pour out, and the actual cause, "a bracket was not closed," gets buried. If you put the formatting verdict first, it points at that file directly.
The second is when the gate fixes the repository. If you put a fixing mode into CI, instead of the check passing, the working tree quietly changes, and a person later discovers changes they never committed. Use a verdict-only mode in the gate.
The third is the misunderstanding mentioned earlier. Half of the question "all the checks passed, so why did the apply blow up?" is a value problem. Static checks only vouch for the syntax and structure of the configuration file, and say nothing about the world those values point to. That is why plan review remains even after the checks pass.
The fourth is a team that does not use the console. Every time an expression is confusing, they repeat the round trip of changing the configuration, applying, and looking at the result, and that one round takes minutes and, if it fails, leaves the state half-done. If you check on one line in the console, the round trip disappears. Especially at the places where conversion happens — arithmetic and comparison, between strings and numbers — it is faster to print it on the spot rather than rely on memory.
What you will do in the next lab
You go through eight steps. You make a tree with misaligned formatting and check the verdict exit code, actually fix it and judge again, and see the code change once more on a file with broken syntax. Then you make machine-readable output that catches two structural errors at once, and conversely record the case where the structure is fine but the plan is blocked twice because of values. In the last two steps, you check four expressions with the console and move one of them into a real configuration and apply it, and build by hand a pre-commit gate that chains the formatting verdict and the structure check in order.