Almost every non-trivial configuration transforms lists and maps; HCL expressions are how.
HCL values have types: string, number, bool, and collections list(...), set(...), map(...), plus structural object({...}) and tuple([...]). Terraform converts between them automatically where safe ("5" to 5), and errors where not.
Expressions you'll use constantly:
- Interpolation:
"web-${var.env}". - Conditional:
var.env == "prod" ? 3 : 1. Both branches must have compatible types. - Operators:
==,!=,&&,||,!, arithmetic, comparisons. - Indexing:
var.subnets[0],var.tags["team"],var.cfg.size. - Splat:
aws_instance.web[*].idgives the list of IDs from a counted resource. - for expressions:
[for s in var.names : upper(s)]builds a list;{for k, v in var.sizes : k => v * 2}builds a map; addifto filter:[for u in var.users : u.name if u.admin]. nullmeans "unset", so an argument set tonullbehaves as if omitted, handy in conditionals.
Experiment interactively with terraform console, which evaluates expressions against your configuration and state: the fastest way to debug a tricky for expression.
Example
variable "env" {
type = string
default = "prod"
}
variable "users" {
type = list(object({ name = string, admin = bool }))
default = [
{ name = "ana", admin = true },
{ name = "bo", admin = false },
]
}
locals {
replicas = var.env == "prod" ? 3 : 1
admins = [for u in var.users : u.name if u.admin]
by_name = { for u in var.users : u.name => u.admin }
}
output "replicas" { value = local.replicas } # 3
output "admins" { value = local.admins } # ["ana"]
output "by_name" { value = local.by_name } # { ana = true, bo = false }Remember
- Conditionals: cond ? a : b with compatible types
- for expressions build lists [...] and maps {...}, with optional if
- Splat [*] collects an attribute; terraform console for experiments
Watch out for
- Mixing types in a conditional (a list on one side, a string on the other), which fails to evaluate.