Terraform Associate: type constraints optional() attributes and nullable
Converts before it checks. Unknown attributes drop. Null keeps null unless nullable = false.
<!-- hal:authoritative:yaml -->
A type constraint converts before it checks. optional() fills gaps. nullable decides what null means. Each one can accept an input you meant to reject.
§I. Frame: Associate objectives for tonight
09-23 covered data sources and depends_on. 09-20 covered dynamic blocks, for_each, count and splat. 09-17 covered ephemeral and write-only values. 09-14 covered import and moved. 09-11 covered backends. Leave all of that.
Tonight's stems test how a variable block treats the value it is given: the type constraint, the optional() modifier inside object(...), and the nullable argument. Ops tonight declares ECR repositories. A module wrapping them would take map(object({...})) input, and that is the shape below.
Every output here is real, from Terraform 1.14.3 on the lab Mac, in a root module with variables and outputs only. No provider, no cloud calls.
§II. The test module
variable "repos" {
type = map(object({
mutability = optional(string, "IMMUTABLE")
scan_on_push = optional(bool, true)
kms_key_arn = optional(string)
keep_images = optional(number, 50)
}))
}
variable "region" {
type = string
default = "us-east-1"
nullable = false
}
variable "extra_tags" {
type = map(string)
default = { team = "platform" }
}
Input file:
repos = {
api = {}
worker = { mutability = "MUTABLE", keep_images = "20" }
legacy = { scan_on_push = "false", kms_key_arn = null }
}
region = null
extra_tags = null
§III. Five claims, each from a real run
Claim one. optional(type, default) fills a missing attribute; optional(type) fills it with null. api = {} came back as keep_images = 50, mutability = "IMMUTABLE", scan_on_push = true, kms_key_arn = tostring(null). The null is typed: it is a string-typed null, and the output shows it that way.
Claim two. Conversion runs before any check. keep_images = "20" became the number 20. scan_on_push = "false" became the bool false. Terraform converts a string to a number or bool whenever the text parses. Brikman lists the constraint types (string, number, bool, list, map, set, object, tuple, any) and advises always declaring one (PDF p. 115). Declaring a type is not the same as refusing strings. Only a value that cannot convert fails:
Error: Invalid value for input variable
on bad1.tfvars line 1:
1: repos = { api = { keep_images = "fifty" } }
The given value is not suitable for var.repos declared at main.tf:1,1-17: a
number is required.
Claim three. An object type drops attributes it does not declare, without a warning. Input repos = { api = { mutable = "MUTABLE" } } has a typo: mutable, not mutability. The plan succeeded with no error and no warning. The result:
> var.repos.api
{
"keep_images" = 50
"kms_key_arn" = tostring(null)
"mutability" = "IMMUTABLE"
"scan_on_push" = true
}
The typo vanished and the default applied. Here that default happens to be the safe value. Invert the default and the same typo ships a mutable registry.
Claim four. A required attribute turns that typo into an error. Same input, with mutability = string (no optional):
Unsuitable value for var.repos set using -var="repos=...": element "api":
attribute "mutability" is required.
Typo Tripwire (named technique): make the attributes that carry a security decision required, and leave optional() for tuning knobs. Bootcamp Lab 16 already has this split: versioning = bool is required, while lifecycle_days = optional(number) and tags = optional(map(string), {}) are optional.
Claim five. nullable decides whether null means "use the default". region has nullable = false, so passing null produced the default: output region = "us-east-1". extra_tags leaves nullable at its default of true, so passing null set it to null and ignored the default. terraform console confirmed var.extra_tags == null is true, and after apply the output was simply missing: terraform output extra_tags returned Error: Output "extra_tags" not found. Terraform does not store an output whose value is null.
§IV. Exam traps
- **"A
numbertype rejects the string\"20\"."** False. It converts. Only non-numeric text fails. - "Unknown object attributes cause a validation error." False for input variables in Terraform 1.14. They are discarded.
- **"Passing
nullto a variable with a default uses the default."** Only withnullable = false. The default isnullable = true, which keeps the null. - **"
optional(string)means the attribute defaults to an empty string."** False. It defaults to null. - **"A
validationblock will catch the typo."** Not after conversion. On the lab Mac,condition = !can(var.x.mutable)passed withmutablein the input, because the key was already gone when the condition ran.
§V. Practice
type = object({ size = optional(number, 10) }) and receives { size = "25" }. What does var.x.size hold?size is absent.default = "eu-west-1" and no nullable argument. A caller passes null. What is the value inside the module?nullable defaults to true, so null is a valid value and the default is not used. Set nullable = false to make null fall back to the default.map(object({ encrypted = optional(bool, false) })). A caller writes { db = { encrypt = true } }. What happens at plan?encrypt is not a declared attribute, so it is dropped, and encrypted takes its default false. Making encrypted a required bool would have failed the plan instead.tags?object({ name = string, tags = optional(map(string), {}) }). name is required, and tags becomes an empty map when omitted.§VI. Drill (Lab 16 primary)
- Open
the study notes/tfpro-labs/labs/16-filtered-foreach-outputs-broken/. Read thebucketsvariable and explain whyversioningis required whilelifecycle_daysis optional. - In a scratch root module, reproduce claim three: misspell an optional attribute and confirm the plan passes. Then make it required and confirm the plan fails.
- Add
nullable = falseto a variable with a default, pass-var 'x=null', and check the value withterraform console.
Success: you can predict, before running, whether a given input converts, drops, defaults, or errors.
§VII. Close instruction
File three flash lines: converts before it checks; unknown attributes drop silently; null keeps null unless nullable = false. Maghrib owns quiz.html. Pair: Ops ECR registry scanning and census; Dev Rust serde over plan JSON.
Related
- Ops: ECR registry scanning + Rust census (same trio)
- Dev: Rust serde over plan JSON (same trio)
- Prior Cert: data sources + depends_on (09-23)
- Prior Cert: dynamic blocks (09-20)
- Bootcamp Lab 16