# Условия и null-safety: ?:, try, can, coalesce _Основы Terraform · TerraformLab Knowledge Base_ **TL;DR:** Тернарный оператор a ? b : c, простой if/else. try(expr, fallback) пытается вычислить или подставляет запасное. can(expr) возвращает true/false. coalesce(...): первый не-null. Все спасают от падений. ## Тернарный оператор `?:` Базовая условная конструкция: ```hcl condition ? value_if_true : value_if_false ``` Примеры: ```hcl 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 ```hcl # Плохо 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: ```hcl variable "config" { type = map(string) default = { env = "dev" } } # Если в config нет ключа region, упадёт region = var.config["region"] ``` `try()` решает: попробовать вычислить, если упало, взять fallback: ```hcl region = try(var.config["region"], "us-east-1") # Если ключ есть, его значение. Если нет, "us-east-1". ``` Можно несколько fallback'ов: ```hcl region = try( var.config["region"], # 1-я попытка var.default_config["region"], # 2-я "us-east-1" # последняя ) ``` Берётся первый успешно вычисленный. ## `can(expr)`, проверка «получится ли» Возвращает `true`/`false`, без значения. Полезно в условиях: ```hcl 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. Ещё пример: ```hcl locals { has_custom_region = can(var.config["region"]) } ``` ## `coalesce(...)`, первый не-null Берёт первое значение, которое **не null**: ```hcl 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'ом: ```hcl tags = { Env = "prod" } env = lookup(tags, "Env", "unknown") # "prod" team = lookup(tags, "Team", "unassigned") # "unassigned", ключа нет ``` Эквивалент: ```hcl 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"}"`, валидно. Внутри `${...}` можно полные выражения. ## Команды ```bash terraform console ``` Лучшее место для проверки сложных условий. Введите выражение: увидите результат. ```bash echo 'try(var.x["key"], "fallback")' | terraform console ``` Можно прогнать try() прямо из терминала через stdin. ## См. также - [HCL: язык, на котором пишут Terraform](/terraform/kb/hcl-syntax.md) - [locals: вычисляемые внутренние имена](/terraform/kb/tf-locals.md) - [Функции коллекций HCL: length, lookup, merge, concat, flatten](/terraform/kb/tf-functions-collection.md)