Функции, выражения и условная логика

HCL — не Turing-полный язык, но в нём есть всё для гибкой конфигурации: встроенные функции, тернарный оператор, выражения-циклы for и динамические блоки.

В HCL нет переменных-присваиваний и циклов в привычном смысле. Вместо них — выражения, которые вычисляются в значение. Это сознательное ограничение: конфигурация должна оставаться предсказуемой.

Terraform даёт сотни встроенных функций: строковые (upper, replace, format), коллекций (length, merge, concat, lookup), числовые, сетевые (cidrsubnet), кодирующие (jsonencode, base64encode). Своих функций писать нельзя — только использовать готовые. Это держит язык простым.

Условия и циклы-выражения

# тернарный оператор
instance_type = var.env == "prod" ? "t3.large" : "t2.micro"

# for-выражение: преобразовать список
locals {
  upper_names = [for n in var.names : upper(n)]
  # мапа из списка
  by_id = { for s in var.servers : s.id => s.name }
}

# dynamic-блок: генерировать вложенные блоки
dynamic "ingress" {
  for_each = var.ports
  content {
    from_port = ingress.value
    to_port   = ingress.value
    protocol  = "tcp"
  }
}

Тернарный оператор условие ? a : b заменяет if. for-выражение преобразует одну коллекцию в другую (список → список или список → мапа). dynamic-блок генерирует повторяющиеся вложенные блоки — например, набор правил файрвола из списка портов.

Как работает под капотом

for-выражение в HCL — это, по сути, list/dict comprehension из Python. Смоделируем три типичных преобразования, которые вы будете писать постоянно:

servers = [
    {"name": "web", "env": "prod"},
    {"name": "api", "env": "prod"},
    {"name": "test", "env": "dev"},
]

# [for s in servers : upper(s.name)]
names_upper = [s["name"].upper() for s in servers]
print("Список имён:", names_upper)

# { for s in servers : s.name => s.env }
env_by_name = {s["name"]: s["env"] for s in servers}
print("Мапа имя->env:", env_by_name)

# фильтрация: [for s in servers : s.name if s.env == "prod"]
prod_only = [s["name"] for s in servers if s["env"] == "prod"]
print("Только prod:", prod_only)

«Попробуй сам ▶» — синтаксис HCL почти дословно повторяет эти три строки. for-выражение с if умеет и фильтровать.

Частые ошибки

  • Вложенные тернарники. a ? b : c ? d : e нечитаемы; вынесите логику в locals или мапу-словарь.
  • Путать [] и {} в for-выражении. Квадратные скобки дают список, фигурные с => — мапу.
  • Логика вместо данных. Если конфиг превращается в дерево условий — вынесите варианты в переменные-мапы (lookup(var.sizes, var.env)).

Best practices

  • Сложные выражения выносите в блок locals — это именованные промежуточные значения, читаемые и переиспользуемые.
  • Используйте lookup и мапы вместо длинных цепочек тернарников.
  • dynamic-блоки применяйте умеренно: они мощные, но затрудняют чтение. Иногда явные блоки понятнее.

Разбор глубже

Среди функций есть категория, которую используют чаще всего, — функции для работы со значениями по умолчанию и проверками. Функция try(expr1, expr2, ...) возвращает первое выражение, которое вычислилось без ошибки, — это удобно, когда структура данных может не содержать какого-то поля. Функция coalesce(a, b, c) возвращает первое непустое значение, а lookup(map, key, default) достаёт значение из мапы с запасным вариантом. Вместе они избавляют от громоздких проверок на null и делают конфигурацию устойчивой к отсутствующим данным.

Важно понимать философию ограничений HCL. Отсутствие циклов while и рекурсии — это не недоработка, а защита: конфигурация инфраструктуры должна вычисляться за конечное предсказуемое число шагов, без риска зависнуть. Всё, что нужно для генерации повторяющихся структур, дают for-выражения и dynamic-блоки, а вся «настоящая» логика выносится либо в значения переменных, либо во внешние data-источники. Этот аскетизм — сознательная цена за то, что план всегда детерминирован и его результат можно безопасно показать в ревью.

На практике мощнее всего оказывается связка for-выражений с функциями преобразования коллекций. Типичный приём — превратить список объектов в мапу по ключу через { for x in list : x.name => x }, чтобы потом удобно скармливать её в for_each. Другой частый паттерн — функция flatten, разворачивающая вложенные списки в плоский, что незаменимо, когда из конфигурации с группами и подгруппами нужно получить единый список правил. Освоив десяток таких идиом, вы перестанете писать громоздкие конструкции и начнёте выражать намерение коротко и читаемо — именно так выглядит зрелый код на HCL.

Итог: HCL даёт функции, тернарники, for-выражения и dynamic-блоки — этого хватает для гибкости без полноценного программирования. Выносите сложное в locals. Дальше — как параметризовать конфигурацию переменными.

Проверьте себя
1. Можно ли в HCL написать свою функцию?
AДа, как в Python
BНет, доступны только встроенные функции
CТолько на платной версии
DТолько в модулях
2. Чем for-выражение { for s in list : s.id => s.name } отличается от [for ...]?
AНичем
BФигурные скобки с => создают мапу, квадратные — список
CФигурные работают быстрее
DКвадратные создают мапу