Тернарный оператор ?:
Базовая условная конструкция:
condition ? value_if_true : value_if_false
Примеры:
variable "env" { default = "dev" }resource "aws_db_instance" "main" {instance_class = var.env == "prod" ? "db.m5.large" : "db.t3.micro"
multi_az = var.env == "prod"
backup_retention = var.env == "prod" ? 30 : 1
skip_final_snapshot = var.env != "prod"
}
Ветви должны быть одного типа. Нельзя var.x ? "string" : 42, упадёт «inconsistent conditional result types».
Вложенные тернарные, нечитаемо, лучше через locals
# Плохо
size = var.env == "prod" ? "large" : var.env == "staging" ? "medium" : "small"
# Лучше
locals { size_by_env = {prod = "large"
staging = "medium"
dev = "small"
}
instance_size = local.size_by_env[var.env]
}
Особенно если вариантов больше двух, map читается легче чем цепочка ? : ?.
try(expr, fallback), попытаться или забить
Часто HCL ломается на «значение отсутствует». Например, читаем поле, которого нет в map:
variable "config" {type = map(string)
default = {env = "dev"
}
}
# Если в config нет ключа region, упадёт
region = var.config["region"]
try() решает: попробовать вычислить, если упало, взять fallback:
region = try(var.config["region"], "us-east-1")
# Если ключ есть, его значение. Если нет, "us-east-1".
Можно несколько fallback'ов:
region = try(
var.config["region"], # 1-я попытка
var.default_config["region"], # 2-я
"us-east-1" # последняя
)
Берётся первый успешно вычисленный.
can(expr), проверка «получится ли»
Возвращает true/false, без значения. Полезно в условиях:
validation { condition = can(regex("^[a-z][a-z0-9-]{2,62}$", var.bucket_name))error_message = "Bucket name must start with a letter, 3-63 chars, lowercase only."
}
regex() падает если не нашёл соответствия. can(regex(...)), true если нашёл, false если упало. Это правильный паттерн для validation.
Ещё пример:
locals {has_custom_region = can(var.config["region"])
}
coalesce(...), первый не-null
Берёт первое значение, которое не null:
region = coalesce(var.custom_region, var.default_region, "us-east-1")
Если var.custom_region, null, проверяет следующий. И так далее. Если все null, упадёт.
Похоже на try(), но разница:
coalesce()смотрит на null.try()смотрит на ошибку вычисления.
Если хотите «не null И не пустая строка», coalesce() сама пропустит null, но пустую строку оставит. Нужно compact() для списков или явная проверка.
lookup(map, key, default), словарь с дефолтом
Старый стиль доступа к map с fallback'ом:
tags = {Env = "prod"
}
env = lookup(tags, "Env", "unknown") # "prod"
team = lookup(tags, "Team", "unassigned") # "unassigned", ключа нет
Эквивалент:
env = try(tags["Env"], "unknown")
Обе записи допустимы. try() современнее и универсальнее.
Подводные камни
-
Тернарный с разными типами, ошибка.
var.x ? 42 : "fallback"не пройдёт. Обе ветви, один тип. -
try()может маскировать настоящие ошибки. Если вы оборачиваете вtry()всё подряд, пропустите реальный bug в expression. Используйте точечно. -
can()тоже маскирует. Если вcan(...)ушла любая ошибка, вернётся false, даже если этоregex()с битым паттерном. Будьте осторожны. -
coalesce()и пустые строки.coalesce("", "fallback")вернёт"", не fallback."": это не null. Если нужно «не пустое»: пишите явно:var.x != "" ? var.x : "fallback". -
Условия не делают if/else блоков. В HCL вы не можете «создать ресурс только если». Для этого,
count = var.create ? 1 : 0(создаёт 0 или 1 ресурс). И аналогично для for_each:for_each = var.create ? toset(["one"]) : toset([]). -
?:в строке без обёртки.bucket = "name-${var.env == "prod" ? "p" : "d"}", валидно. Внутри${...}можно полные выражения.