Terraform/OpenTofu Fundamentals
No Type Declared, So the Error Landed on the Wrong Line
Goal
You declare primitive, collection, and structural types one by one and confirm what is blocked at the variable boundary and what is quietly converted. You also look at optional attributes and defaults, the declaration that null is not accepted, and even where the error arises when you did not write the type.
Why it matters
A type is not documentation but a checkpoint. If you write the type, a wrong value is blocked at the variable boundary along with the tfvars line number, and if you do not, the value flows deep inside and blows up with an unrelated message. It is the same mistake, yet the time to fix it differs tenfold. But type checking does not only block — it also converts. A number written as a string becomes a number, and a number put into a map with an element type becomes a string. This conversion happens without an error, so if the value will later be used in a comparison or arithmetic, you must know it in advance. Conversely, a structural type silently discards an attribute that is not in the declaration — if you make a typo in an attribute name, it looks as if the default was applied. What is blocked, what is converted, and what disappears — telling these three apart is the whole of this module.
Steps
- In
/root/tfb-types/prim/main.tf, put astringvariablename, anumbervariablereplicas, aboolvariabledebug, and three outputs. Inok.tfvars, writename = "api",replicas = "3",debug = "true", apply, and savetofu output -jsonto/root/tfb-types/prim.json. Inbad.tfvars, put a list intoreplicas, run plan, and save the error output to/root/tfb-types/prim-bad.txt. - In
/root/tfb-types/quiet/main.tf, put amap(string)variabletags, alist(string)variableids, and two outputs. Inv.tfvars, givetagsthe valuesteam = "core",rev = 3,tls = true, and giveidsthe value[1, 2, 10], apply, and savetofu output -jsonto/root/tfb-types/quiet.json. - In
/root/tfb-types/coll/main.tf, put alist(string)variableas_list, aset(string)variableas_set, atuple([string, number, bool])variableas_tuple, and five outputs (the three values and the lengths of the list and the set). Inv.tfvars, give the first two variables the same["b", "a", "b"]and give the tuple["web", 3, true], apply, and savetofu output -jsonto/root/tfb-types/coll.json. Then run plan with abad.tfvarsthat gives only two tuple elements and save the error to/root/tfb-types/tuple-bad.txt. - In
/root/tfb-types/obj/main.tf, put anobject({ name = string, port = number })variablesvcand one output. Inok.tfvars, in addition tonameandport, also writeowner = "team-a", which is not in the declaration, apply, and savetofu output -jsonto/root/tfb-types/obj.json. Inbad.tfvars, leave outname, run plan, and save the error to/root/tfb-types/obj-bad.txt. - In
/root/tfb-types/opt/main.tf, put thesvcvariable as an object, making onlynamerequired and declaringport(default 8080),tls(default false),tags(default empty map), andaliases(default empty list) as optional. Inv.tfvars, give onlyname, apply, and savetofu output -jsonto/root/tfb-types/opt.json. - In
/root/tfb-types/nul/main.tf, put two variables withnullable = false— one with the default"a",zone, and one with no default,must. Also put two outputs. Inok.tfvars, givezone = nullandmust = "x", apply, and savetofu output -jsonto/root/tfb-types/nul.json. Inbad.tfvars, give onlymust = null, run plan, and save the error to/root/tfb-types/nul-bad.txt. - In
/root/tfb-types/late/, put a variable of typeany,loose, and an output that exports its length, and in/root/tfb-types/tight/, put the same shape but with a variable of typelist(string),tight. In both places, inv.tfvars, give the value7, run plan, and save the outputs to/root/tfb-types/late.txtand/root/tfb-types/tight.txtrespectively. Then pull out only the first line of each of the two errors and write two lines to/root/tfb-types/blame.txt:late=<첫 오류 줄>andtight=<첫 오류 줄>(first error line; only the text afterError:). - In
/root/tfb-types/schema/main.tf, put theservicevariable in the same shape as step 5 (requiredname, optionalport,tls,tags,aliases), and makeservice.confwith its values. The five lines arename=,port=,tls=,tags=(encoded as JSON), andaliases=(joined with commas). Inv.tfvars, givename = "api",port = "9090",tags = { team = "core", rev = 3 }, andaliases = ["api-1", "api-2"], and apply. The grader checks by putting two kinds of wrong input into a copy of this configuration to see that they are rejected.
Notes
- A type looks at the 'shape', and a validation block looks at the 'range of values'. The two cannot substitute for each other.
- The output JSON contains the type along with the value, which makes it good for checking by eye what went in.
- When you give a value on the command line, a primitive-type variable receives even the quotes as part of the value. In this lab, to avoid confusion, only tfvars files are used.
- Common mistake: fixing the bad.tfvars of step 3 to make it pass. That file must be left wrong for the error to be confirmed.
- Input Variables · Type Constraints · Types and Values · Type Constraints (HashiCorp)
Primitive types are caught at the variable boundary
In /root/tfb-types/prim/main.tf, put a string variable name, a number variable replicas, a bool variable debug, and three outputs. In ok.tfvars, write name = "api", replicas = "3", debug = "true", apply, and save tofu output -json to /root/tfb-types/prim.json. In bad.tfvars, put a list into replicas, run plan, and save the error output to /root/tfb-types/prim-bad.txt.
Even a value with quotes is converted by the tool if it can be made to fit the type. The output JSON shows the type as well as the value, so check by eye what went in.
Inside a collection, conversion happens quietly
In /root/tfb-types/quiet/main.tf, put a map(string) variable tags, a list(string) variable ids, and two outputs. In v.tfvars, give tags the values team = "core", rev = 3, tls = true, and give ids the value [1, 2, 10], apply, and save tofu output -json to /root/tfb-types/quiet.json.
A collection with a specified element type converts the values put in to that type and holds them. Even if numbers and booleans become strings, no error occurs, so if the value will later be used in a comparison or arithmetic, you must know about this conversion.
The same input is kept differently as a list, a set, and a tuple
In /root/tfb-types/coll/main.tf, put a list(string) variable as_list, a set(string) variable as_set, a tuple([string, number, bool]) variable as_tuple, and five outputs (the three values and the lengths of the list and the set). In v.tfvars, give the first two variables the same ["b", "a", "b"] and give the tuple ["web", 3, true], apply, and save tofu output -json to /root/tfb-types/coll.json. Then run plan with a bad.tfvars that gives only two tuple elements and save the error to /root/tfb-types/tuple-bad.txt.
A list keeps the order written and the duplicates, and a set discards duplicates and sorts by its own rules. A tuple has a fixed length with a type decided for each position, so if the count differs, it is blocked right there.
An object blocks when something is missing and quietly discards what is extra
In /root/tfb-types/obj/main.tf, put an object({ name = string, port = number }) variable svc and one output. In ok.tfvars, in addition to name and port, also write owner = "team-a", which is not in the declaration, apply, and save tofu output -json to /root/tfb-types/obj.json. In bad.tfvars, leave out name, run plan, and save the error to /root/tfb-types/obj-bad.txt.
An object type accepts only the attributes you declared, and an attribute not in the declaration is not an error but a discard. So a mistyped attribute name vanishes without a word, and it looks as if the default was applied.
Accept short inputs with optional attributes and defaults
In /root/tfb-types/opt/main.tf, put the svc variable as an object, making only name required and declaring port (default 8080), tls (default false), tags (default empty map), and aliases (default empty list) as optional. In v.tfvars, give only name, apply, and save tofu output -json to /root/tfb-types/opt.json.
An optional attribute takes a default as its second argument. If you do not give a default, that attribute becomes null and has to be checked again downstream, so writing the default along with it makes things easier for the caller.
The declaration that null is not accepted works in two branches
In /root/tfb-types/nul/main.tf, put two variables with nullable = false — one with the default "a", zone, and one with no default, must. Also put two outputs. In ok.tfvars, give zone = null and must = "x", apply, and save tofu output -json to /root/tfb-types/nul.json. In bad.tfvars, give only must = null, run plan, and save the error to /root/tfb-types/nul-bad.txt.
When you declare that null is not accepted, if there is a default, null is replaced by the default, and if there is no default, it is an error. It is a device that pins down in one place what happens when there is no value.
If you accept it as any, the error arises deep inside
In /root/tfb-types/late/, put a variable of type any, loose, and an output that exports its length, and in /root/tfb-types/tight/, put the same shape but with a variable of type list(string), tight. In both places, in v.tfvars, give the value 7, run plan, and save the outputs to /root/tfb-types/late.txt and /root/tfb-types/tight.txt respectively. Then pull out only the first line of each of the two errors and write two lines to /root/tfb-types/blame.txt: late=<첫 오류 줄> and tight=<첫 오류 줄> (first error line; only the text after Error: ).
If you do not write the type, a wrong value passes the variable boundary as it is. Then the error arises where that value is used, and the message talks about a function, not the variable. If you narrow the type, the same mistake is blocked up front along with the tfvars line number.
Pin down the input specification with nested types
In /root/tfb-types/schema/main.tf, put the service variable in the same shape as step 5 (required name, optional port, tls, tags, aliases), and make service.conf with its values. The five lines are name=, port=, tls=, tags= (encoded as JSON), and aliases= (joined with commas). In v.tfvars, give name = "api", port = "9090", tags = { team = "core", rev = 3 }, and aliases = ["api-1", "api-2"], and apply. The grader checks by putting two kinds of wrong input into a copy of this configuration to see that they are rejected.
If you write the type properly, a wrong input is blocked right there along with the tfvars line number. The port written as a string going in as a number and the number in the map becoming a string — two conversions happen together in one input, so check the result by eye.