Terraform Conditionals and for Expressions: Ternary, count, splat and Examples

· 1 min read · Terraform & OpenTofu Tutorials

Logic in HCL #

HCL is declarative, but it has expressions to choose values and transform collections. These are the ones you use every day. The full list of built-in functions is in Terraform functions.

Conditional expression (ternary) #

conditional.tf
variable "environment" {
  type = string
}

locals {
  instance_type = var.environment == "pro" ? "m6i.large" : "t3.micro"
  min_size      = var.environment == "pro" ? 3 : 1
}

The format is condition ? value_if_true : value_if_false. Both values must be the same type. Combine conditions with &&, || and !, and compare with ==, !=, <, >.

Create a resource only if... #

optional.tf
variable "create_bastion" {
  type    = bool
  default = false
}

resource "aws_instance" "bastion" {
  count         = var.create_bastion ? 1 : 0
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
}

output "bastion_ip" {
  value = one(aws_instance.bastion[*].public_ip)   # null when not created
}

count = condition ? 1 : 0 is the standard switch. one() returns the single element or null if the list is empty, avoiding an index error. See for_each vs count.

Optional values and defaults #

defaults.tf
locals {
  # first non-null, non-empty value
  name = coalesce(var.name, "default-name")

  # fall back if an expression fails or is null
  port = try(var.config.port, 8080)

  # value from a map with a default
  size = lookup(var.sizes, var.environment, "small")
}

Use optional() in object types for attributes that may be missing:

variables.tf
variable "rule" {
  type = object({
    port     = number
    protocol = optional(string, "tcp")
    cidrs    = optional(list(string), ["10.0.0.0/8"])
  })
}

for expressions #

Transform a list into another list:

for.tf
locals {
  names     = ["web", "db", "cache"]
  upper     = [for n in local.names : upper(n)]                  # ["WEB", "DB", "CACHE"]
  prefixed  = [for n in local.names : "ditwl-${n}"]
  with_idx  = [for i, n in local.names : "${i}-${n}"]
  filtered  = [for n in local.names : n if n != "cache"]          # ["web", "db"]
}

Build a map (use braces and =>):

for-map.tf
locals {
  subnet_cidrs = {
    a = "10.0.1.0/24"
    b = "10.0.2.0/24"
  }

  name_to_cidr = { for k, v in local.subnet_cidrs : "private-${k}" => v }
  only_a       = { for k, v in local.subnet_cidrs : k => v if k == "a" }
}

Using the result with for_each creates one resource per item.

Splat expressions #

splat.tf
output "instance_ids" {
  value = aws_instance.web[*].id         # same as [for i in aws_instance.web : i.id]
}

With for_each resources, use values(): [for i in aws_instance.web : i.id] or values(aws_instance.web)[*].id.

Merge, flatten and setproduct #

collections.tf
locals {
  tags = merge(var.common_tags, { Name = "web" }, var.extra_tags)

  # flatten nested lists
  all_subnets = flatten([for vpc in var.vpcs : vpc.subnets])

  # every combination
  pairs = setproduct(["web", "db"], ["a", "b"])   # [["web","a"],["web","b"],["db","a"],["db","b"]]
}

A common pattern is to flatten nested structures into a map for for_each:

flatten.tf
locals {
  rules = flatten([
    for sg, ports in var.sg_ports : [
      for port in ports : {
        key  = "${sg}-${port}"
        sg   = sg
        port = port
      }
    ]
  ])
}

resource "aws_vpc_security_group_ingress_rule" "this" {
  for_each = { for r in local.rules : r.key => r }

  security_group_id = aws_security_group.this[each.value.sg].id
  ip_protocol       = "tcp"
  from_port         = each.value.port
  to_port           = each.value.port
  cidr_ipv4         = "10.0.0.0/8"
}

Dynamic blocks and templates #

Conditions can generate nested blocks (dynamic blocks) and templatefile supports %{ if } and %{ for } directives to build text files such as user data.

Test expressions #

$ terraform console
> [for n in ["a","b"] : upper(n)]
[
  "A",
  "B",
]
> var.environment == "pro" ? 3 : 1

terraform console loads your configuration and evaluates any expression, the fastest way to debug. See also common errors and the cheat sheet.

#Terraform #OpenTofu #Hcl