002 · Domain 1: Scalable Terraform Configuration Design · 20% of the exam
Task 1.3: Trace input variables, local values, and outputs to identify the source of a configuration issue
Following a value from where it is set to where it breaks: variable types and conversion, locals, conditional and splat expressions, count instances, module outputs, and the error each mistake produces.
Study it
Tracing values: variables, locals, conditionals, splats and module outputs
Lesson coming
Sample questions
Try each one before opening the answer. Every option is explained, with the HashiCorp documentation page that proves it.
Question 1 · choose 2
A root module contains:
module "db" {
source = "./db"
count = var.create_db ? 1 : 0
}
output "db_endpoint" {
value = module.db.endpoint
}
terraform plan fails with an error about module.db being a list. The output
must return the endpoint when create_db is true and null when it is false.
Which two values work? (Choose TWO.)
Aone(module.db[*].endpoint)
Bmodule.db[0].endpoint
Clookup(module.db, "endpoint", null)
Dtry(module.db[0].endpoint, null)
Emodule.db.endpoint[0]
Show the answer and why
Aone(module.db[*].endpoint)
Correct
The splat gives a list of zero or one endpoints, and one returns null for an empty list or the single element otherwise.
Bmodule.db[0].endpoint
Incorrect
It works when the instance exists, but with count = 0 the index is out of range and the plan fails.
Clookup(module.db, "endpoint", null)
Incorrect
lookup works on maps; module.db is a list of module instances here, so the call fails.
Dtry(module.db[0].endpoint, null)
Correct
try catches the dynamic out-of-range error when no instance exists and falls back to null.
Emodule.db.endpoint[0]
Incorrect
module.db is a list, so it has no endpoint attribute to index; the index must come before the attribute.
A module call with count is a list of instance objects. one() with a splat and try() with an index both cover the zero-instance case; a bare index or attribute access does not.
A module creates a NAT gateway only when the caller passes no private
subnets of its own:
variable "private_subnet_ids" {
type = list(string)
default = []
}
resource "aws_nat_gateway" "this" {
count = var.private_subnet_ids == [] ? 1 : 0
# ...
}
Callers that leave the variable unset get no NAT gateway. What is the cause?
A[] is an empty tuple, which never equals a list(string); test length(var.private_subnet_ids) == 0
BThe default is applied after count is evaluated, leaving var.private_subnet_ids null at comparison
Ccount accepts only a bool variable and converts the conditional to 0 whatever the condition returns
DAn empty list(string) default is stored as an empty set, and sets cannot be compared with == in Terraform
Show the answer and why
A[] is an empty tuple, which never equals a list(string); test length(var.private_subnet_ids) == 0
Correct
The equality operators need both values to have the same type, so the comparison is always false and count is 0. length avoids the type mismatch.
BThe default is applied after count is evaluated, leaving var.private_subnet_ids null at comparison
Incorrect
Defaults are part of the variable's value before any expression that uses it is evaluated; count sees the empty list.
Ccount accepts only a bool variable and converts the conditional to 0 whatever the condition returns
Incorrect
count accepts any whole number known at plan time; a conditional that returns 1 or 0 is valid.
DAn empty list(string) default is stored as an empty set, and sets cannot be compared with == in Terraform
Incorrect
The type constraint is list(string), so the default is an empty list; the problem is the tuple literal on the other side of ==.
Comparing collections with == depends on exact types. The empty brackets literal is a tuple, so a list never matches it. length() or a type conversion states the intent without the trap.
A CI step pipes three newline-separated expressions into terraform console: var.environment, local.subnet_map and local.tags["owner"]. What appears in the job log?
AAll three results, one after another
BNothing, as console works only interactively
CThe first result only, after which console exits
DOnly the result of local.tags["owner"], unless an earlier line errors
Show the answer and why
AAll three results, one after another
Incorrect
When commands are piped in, console prints only the output of the final command, unless an earlier one errors.
BNothing, as console works only interactively
Incorrect
console can be used in non-interactive scripts by piping newline-separated commands to it.
CThe first result only, after which console exits
Incorrect
console evaluates every piped line and prints the last result.
DOnly the result of local.tags["owner"], unless an earlier line errors
Correct
In scripted use, only the output of the final command is printed, unless an error occurs earlier.
To see several values from one scripted console call, pipe a single expression that combines them, for example an object with one attribute per value.
local.port = var.use_default ? 12 : "8080". With var.use_default = true, what is local.port?
AThe number 12
BThe string "12"
CAn error about inconsistent conditional result types
Dnull, the result of arms with different types
Show the answer and why
AThe number 12
Incorrect
The two arms must share a type, and Terraform converts both to string.
BThe string "12"
Correct
A number converts to a string automatically, so the conditional always returns a string.
CAn error about inconsistent conditional result types
Incorrect
Terraform first tries to find a type both arms convert to; number and string can both become string.
Dnull, the result of arms with different types
Incorrect
Mismatched types are converted or rejected; they never produce null.
Both results of a conditional need a common type. Terraform converts where it can; HashiCorp recommends explicit conversion with tostring() or tonumber() when the result type matters.
A module declares variable "website" with an object type and default =
null, and a bucket resource contains:
dynamic "website" {
for_each = var.website[*]
content { index_document = website.value.index_document }
}
A caller leaves website unset. How many website blocks does the bucket get?
AOne block with null attributes
BAn error: for_each cannot be null
CZero blocks
DOne block; a splat always returns at least one element
Show the answer and why
AOne block with null attributes
Incorrect
A null value splats to an empty tuple, so there is nothing to iterate.
BAn error: for_each cannot be null
Incorrect
for_each never sees null here; the splat turns null into an empty tuple.
CZero blocks
Correct
A splat on null returns an empty tuple, so the dynamic block generates no blocks; a non-null object becomes a one-element tuple.
DOne block; a splat always returns at least one element
Incorrect
Only a non-null single value becomes a one-element tuple.
The splat operator turns an optional single object into a zero-or-one collection, which suits dynamic blocks and for_each.
local.log_prefix joins var.env with aws_s3_bucket.logs.bucket, a bucket name set in the configuration. The bucket is new in this change, so in terraform console local.log_prefix shows (known after apply). Which command evaluates the local against the values the next plan expects?
Aterraform console -plan
Bterraform output -json
Cterraform state show aws_s3_bucket.logs
Dterraform validate -json
Show the answer and why
Aterraform console -plan
Correct
With -plan, the console first creates a plan, as terraform plan would, and evaluates expressions against the planned state, so values from the configuration are available.
Bterraform output -json
Incorrect
terraform output prints declared outputs, not local values, and nothing has been applied yet.
Cterraform state show aws_s3_bucket.logs
Incorrect
State has no object for a bucket that has not been created yet.
Dterraform validate -json
Incorrect
validate checks the configuration; it does not evaluate expressions against state or a plan.
The console normally evaluates against the current state, which says little about objects that do not exist yet. -plan takes longer because it plans first, and a configuration whose data sources take actions while planning will take them in the console too.