002 · Domain 1: Scalable Terraform Configuration Design · 20% of the exam
Task 1.4: Trace dynamic resources created with for_each
What for_each creates and how to read it back: instance keys from maps and sets, each.key and each.value, the addresses that terraform state list and state show print, and values that are not known until apply.
Study it
for_each instances: keys, each.value and the addresses state prints
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 1
A new environment is applied from scratch with:
variable "public_subnets" {
type = map(string) # zone => CIDR
}
resource "aws_subnet" "public" {
for_each = var.public_subnets
cidr_block = each.value
# ...
}
resource "aws_nat_gateway" "this" {
for_each = toset([for s in aws_subnet.public : s.id])
subnet_id = each.value
}
The plan fails with "Invalid for_each argument". The fix must work in a
single plan and apply for every new environment. What should the team do?
ARun a targeted apply of aws_subnet.public first, then apply the rest of the configuration
BAdd depends_on = [aws_subnet.public] to the NAT gateways to make each subnet_id known in time
CWrap the expression in tomap() to give for_each a map like var.public_subnets instead of a set
DUse for_each = var.public_subnets and set subnet_id = aws_subnet.public[each.key].id instead
Show the answer and why
ARun a targeted apply of aws_subnet.public first, then apply the rest of the configuration
Incorrect
It works once, but every new environment then needs two runs, which the requirement rules out.
BAdd depends_on = [aws_subnet.public] to the NAT gateways to make each subnet_id known in time
Incorrect
depends_on orders operations; it cannot make an ID that the cloud assigns at creation known during planning.
CWrap the expression in tomap() to give for_each a map like var.public_subnets instead of a set
Incorrect
The problem is that the values used as keys are unknown, not the collection type; converting does not make them known.
DUse for_each = var.public_subnets and set subnet_id = aws_subnet.public[each.key].id instead
Correct
The zone names are known at plan time, so they can be instance keys, and the unknown subnet ID is only an argument value, which may be unknown.
for_each keys must be known before any remote operation. Subnet IDs are assigned at creation, so keying by them fails on a first apply; keying by the zone names from the variable and referencing the ID as a value works in one run.
A module receives var.ports = [80, 443] with type list(number) and sets for_each = toset(var.ports) on a security group rule. The plan rejects the for_each value. Which for_each works?
Afor_each = var.ports
Bfor_each = toset([for p in var.ports : tostring(p)])
Cfor_each = length(var.ports)
Dfor_each = toset(var.ports), with the variable type changed to list(any)
Show the answer and why
Afor_each = var.ports
Incorrect
for_each accepts a map or a set of strings, not a list.
Bfor_each = toset([for p in var.ports : tostring(p)])
Correct
Converting each number to a string gives a set of strings, with instance keys "80" and "443".
Cfor_each = length(var.ports)
Incorrect
A number is a count; for_each needs a map or a set of strings.
Dfor_each = toset(var.ports), with the variable type changed to list(any)
Incorrect
The elements are still numbers, so toset still returns a set of numbers rather than a set of strings.
Derive string keys for for_each, and convert back with tonumber() wherever a resource argument needs the number, such as from_port = tonumber(each.key).
aws_lb.app uses for_each over three environments. An author writes output "dns" { for_each = aws_lb.app, value = each.value.dns_name }, and terraform validate rejects the output. What is the reason?
Afor_each is not supported in output blocks
Bfor_each in outputs needs a set, not map
CThe value must use each.key, not each.value
DThe output first needs depends_on = [aws_lb.app]
Show the answer and why
Afor_each is not supported in output blocks
Correct
The for_each reference lists data, ephemeral, module and resource blocks as the configuration blocks that support it; output blocks are not among them.
Bfor_each in outputs needs a set, not map
Incorrect
Outputs do not take for_each at all, whatever the collection type.
CThe value must use each.key, not each.value
Incorrect
The problem is the for_each argument itself, which no output block supports.
DThe output first needs depends_on = [aws_lb.app]
Incorrect
No other argument makes for_each valid in an output block.
A resource with for_each already appears as a map of instance objects in expressions, so one output can expose the whole resource or a single value computed from it.
Three databases come from resource "aws_db_instance" "db" with for_each = local.environments. Each must get its own generated password through the write-only password_wo argument (other arguments omitted), and no password may be stored in state or plan files. Which configuration meets both rules?
AOne ephemeral "random_password" "db" without for_each, shared through password_wo = ephemeral.random_password.db.result
Bresource "random_password" "db" with for_each, and password_wo = random_password.db[each.key].result
Cephemeral "random_password" "db" with for_each, and password_wo = ephemeral.random_password.db.result
Dephemeral "random_password" "db" with for_each, and password_wo = ephemeral.random_password.db[each.key].result
Show the answer and why
AOne ephemeral "random_password" "db" without for_each, shared through password_wo = ephemeral.random_password.db.result
Incorrect
One ephemeral resource instance gives all three databases the same password, which breaks the first rule.
Bresource "random_password" "db" with for_each, and password_wo = random_password.db[each.key].result
Incorrect
A managed random_password resource keeps its result in state, which breaks the second rule.
Cephemeral "random_password" "db" with for_each, and password_wo = ephemeral.random_password.db.result
Incorrect
With for_each the ephemeral block has one instance per key, so the reference needs the instance key.
Dephemeral "random_password" "db" with for_each, and password_wo = ephemeral.random_password.db[each.key].result
Correct
ephemeral blocks support for_each, each database reads the instance with its own key, and Terraform stores neither ephemeral values nor write-only arguments.
Ephemeral resources use the same instance addressing as managed resources: ephemeral.<TYPE>.<NAME>[<KEY>]. Write-only arguments then pass each value to the provider without persisting it.