linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
Cluster

← все кластеры

HCL: выражения, типы, ссылки

HCL-семантика и типы: type-coercion, splat, dynamic-блок, conditional, for, references и резолвер графа. Что значит «known after apply» и почему оно бесит. Базовые вопросы junior'а и пара ловушек для middle'а.

6 вопросов · ~20 мин чтения

Questions

На этой странице

  1. 01Какие типы есть в HCL? Что приведётся неявно, а что нет?
  2. 02Что делает splat-оператор `[*]` и чем он отличается от for?
  3. 03Что такое `dynamic`-блок и когда без него не обойтись?
  4. 04Что значит «known after apply» и почему его не получается заменить на конкретное значение?
  5. 05Чем `try`, `can`, `coalesce` отличаются? Где какое использовать?
  6. 06Откуда берётся cycle и как Terraform его находит?

#hcl-types-and-coercion

juniorчасто

Какие типы есть в HCL? Что приведётся неявно, а что нет?

Что отвечать

Примитивы: string, number, bool. Коллекции: list(T), set(T), map(T), tuple([T1,T2,...]), object({k1=T1, k2=T2}). Плюс null. Coercion: число приведётся к string в интерполяции (`"port=${var.port}"`), bool к string как `"true"`. Обратно - нет: строку `"5"` в number руками через `tonumber()`. Между list и tuple - разница: list требует одинаковый T, tuple - фиксированной длины с разными T. Set теряет порядок и дубликаты.

Что хотят услышать

Кандидат должен: - не путать list и set: set удобен для `for_each`, но порядок не гарантирован. List - порядок есть, дубликаты есть - знать про `tostring`, `tonumber`, `tobool`, `tolist`, `toset`, `tomap` - явные converters - объяснить object vs map: map(string) требует одинаковый тип значений, object позволяет разные типы по ключам - сказать что type constraint в variable - не «строгий тип», а форма. `optional()` позволяет отсутствие поля в object'е (с 1.3+)

Подводные камни

  • ✗ Объявить `variable foo { type = list }` без параметра - принимает что угодно, проверки нет. Всегда писать `list(string)`
  • ✗ Сравнивать `[1,2,3]` с `tolist([1,2,3])` - одно tuple, второе list, типы разные, `==` сравнит как разные
  • ✗ Использовать `for_each` с list - старые версии падали, новые потребуют `toset()`. Set'ом сразу

Follow-up

  • ? Чем `object({})` отличается от `map(any)` в variable?
  • ? Что вернёт `toset([1,1,2])`?
  • ? Когда нужен `optional()` в object-type?

Глубина в базе знаний

  • Типы данных в HCL: string, number, list, map, object
  • HCL: язык, на котором пишут Terraform
  • Блок variable: вход в конфигурацию
tags: hcl, types

#splat-and-for-expressions

intermediateиногда

Что делает splat-оператор `[*]` и чем он отличается от for?

Что отвечать

Splat - синтаксический сахар над for для коллекций. `aws_instance.web[*].id` возвращает list всех `id` из `aws_instance.web` (когда там `count` или `for_each`). Эквивалент: `[for x in aws_instance.web : x.id]`. Splat работает только для прямого attribute access; если нужно фильтровать, трансформировать или собрать map - используй for. For-expression строит list (`[for ...]`), set (через toset), или map/object (`{for k,v in m : k => v if cond}`).

Что хотят услышать

Senior должен: - назвать что splat `.*` (legacy) и `[*]` дают разный результат в edge-cases с null; в новом коде - только `[*]` - показать обе формы for: list-comprehension `[for]` и map-comprehension `{for}`. Map-comp требует уникальные ключи, дубликат - ошибка plan'а - объяснить когда for + `if` лучше splat'а: фильтрация (`[for i in ins : i.id if i.public]`), трансформация значений - упомянуть `for` с двумя переменными для map: `for k,v in m`

Подводные камни

  • ✗ Думать что `[*]` работает на любой коллекции - работает только на tuple-of-objects (типичный случай: ресурсы с `count`/`for_each`)
  • ✗ Сделать map-comprehension с потенциально дублирующимся ключом - ошибка только в plan, в IDE не видно
  • ✗ Использовать splat там где нужен for с фильтрацией - выходит «splat + ...» через postfix, нечитаемо

Follow-up

  • ? Когда нужно `flatten([for x in xs : x.list])` и почему не работает просто splat?
  • ? Чем отличаются `[*]` и `.*` в современном Terraform?
  • ? Как преобразовать list-of-objects в map по ключу?

Глубина в базе знаний

  • Ссылки в HCL: как читать aws_s3_bucket.demo.bucket
  • HCL: язык, на котором пишут Terraform
  • count и for_each: несколько ресурсов из одного блока
tags: hcl, expressions

#dynamic-block-when-needed

intermediateиногда

Что такое `dynamic`-блок и когда без него не обойтись?

Что отвечать

`dynamic` строит nested-блок ресурса итеративно. Нужен когда количество nested-блоков заранее неизвестно: список ingress-правил в security group, набор lifecycle_rule в S3 bucket. Без dynamic пришлось бы либо хардкодить, либо плодить отдельные resource-блоки. Синтаксис: `dynamic "ingress" { for_each = var.rules ; content { ... } }`, внутри `content` обращаешься к `ingress.value`.

Что хотят услышать

Кандидат должен: - различить dynamic-блок для nested-блоков ресурса и for_each для самого ресурса. Это разные уровни - назвать что dynamic ухудшает читаемость, поэтому хороший стиль - использовать его только когда правил действительно много или их набор приходит из variable - упомянуть `iterator` - переименование переменной в dynamic, полезно при вложенных dynamic - сказать что один dynamic заменяет только один блок, для двух разных nested-блоков нужны два dynamic

Подводные камни

  • ✗ Использовать dynamic на каждый блок «для единообразия» - код становится двойным синтаксисом без причины
  • ✗ Сделать dynamic с пустой коллекцией думая что блок не появится - появится, но без content; на некоторых ресурсах это ошибка провайдера
  • ✗ Запутаться во вложенных dynamic без `iterator` - имя `each.value` коллидирует, plan ругается невнятно

Follow-up

  • ? Что произойдёт если в `for_each` у dynamic передать пустой set?
  • ? Зачем нужен `iterator` и когда без него код становится двусмысленным?
  • ? Можно ли в dynamic сделать `content` с условным включением?

Глубина в базе знаний

  • HCL: язык, на котором пишут Terraform
  • Ссылки в HCL: как читать aws_s3_bucket.demo.bucket
  • count и for_each: несколько ресурсов из одного блока
tags: hcl, dynamic

#known-after-apply-and-graph

seniorчасто

Что значит «known after apply» и почему его не получается заменить на конкретное значение?

Что отвечать

Terraform строит граф ресурсов через references. Значение становится известно только после реального создания ресурса в провайдере - например, id EC2-инстанса AWS назначает сама. На стадии plan Terraform не может предсказать это значение и пишет «(known after apply)». Если другое выражение зависит от такого id - оно тоже становится unknown в plan'е. Это не баг, это честное «я не знаю до apply». Часто пугает на ровном месте: видишь diff на 200 строк где большинство «known after apply» - это просто references вглубь.

Что хотят услышать

Senior должен: - объяснить ссылку на граф: каждый ресурс - node, references - edges; computed-атрибуты резолвятся при apply узлов - назвать что unknown «протекает» дальше: если depends_on на unknown, то всё дерево становится unknown в plan'е - сказать что unknown в `count` или `for_each` ломает plan: Terraform требует known-keys для for_each на стадии plan. Workaround - вынести ресурс в отдельный root или жёстко передать ключи - упомянуть `terraform plan -refresh-only` для отделения «реальный drift» от «unknown из-за references»

Подводные камни

  • ✗ Сделать `for_each` по computed-атрибуту другого ресурса (`aws_subnet.x.id`)- plan упадёт с «cannot use unknown value in for_each»
  • ✗ Считать что «known after apply» - это ошибка. Это нормальный output
  • ✗ Пытаться «зафиксировать» unknown через `lifecycle.ignore_changes` - это не про то, ignore_changes только для drift'а

Follow-up

  • ? Почему `for_each` по `aws_subnet.x[*].id` падает, а `count = length(aws_subnet.x)` работает?
  • ? Что делает `-refresh-only` иначе чем обычный plan?
  • ? Как обойти `for_each` over unknown на чистом HCL?

Глубина в базе знаний

  • terraform plan: посмотреть, что Terraform собирается сделать
  • DAG в Terraform, как строится граф зависимостей
  • terraform graph: граф зависимостей ресурсов
  • Ссылки в HCL: как читать aws_s3_bucket.demo.bucket
tags: hcl, plan, graphbook: mastering.terraform.epub:ch5

#try-can-coalesce

intermediateиногда

Чем `try`, `can`, `coalesce` отличаются? Где какое использовать?

Что отвечать

`try(a, b, c)` возвращает первое выражение, которое не падает с ошибкой - нужен для опциональных полей в object'ах, чтения отсутствующих ключей в map. `can(expr)` возвращает true/false, можно ли выражение вычислить без ошибки - используется в `validation { condition = can(...) }` на variable. `coalesce(a, b, c)` возвращает первый не-null и не-пустой аргумент - для дефолтов. Не путать: `try` ловит ошибки вычисления, `coalesce` фильтрует null/пустоту.

Что хотят услышать

Кандидат должен: - дать каждой функции свой кейс: `try` для отсутствующих полей, `can` для validation, `coalesce` для default-значений - назвать `coalescelist` как аналог для list - вернёт первый непустой list - сказать что `try` оборачивает «возможную ошибку», но не «возможный null» - для null'а нужен `coalesce` или явная проверка - упомянуть что злоупотребление `try` маскирует баги в HCL - если try возвращает default слишком часто, твоя структура данных не такая, как ты думаешь

Подводные камни

  • ✗ Использовать `try` вместо проверки `var.x != null` - try ловит любую ошибку, в том числе typo в имени поля; ошибка тихо замазывается дефолтом
  • ✗ Сделать `coalesce("", "default")` думая что вернётся «default» - coalesce НЕ считает `""` пустым; пустая строка для него валидна. Нужна `coalesce(var.x != "" ? var.x : null, "default")`
  • ✗ Использовать `can` вне validation-блока - в нормальном выражении можно, но обычно это symptom: ловишь ошибку вместо того чтобы написать корректную типизацию

Follow-up

  • ? Почему `coalesce("", "def")` не возвращает «def»? Что вернёт?
  • ? Когда `can` лучше чем `try` с обработкой результата?
  • ? Как написать validation, который проверяет что variable не пустой и не содержит whitespace?

Глубина в базе знаний

  • Функции коллекций HCL: length, lookup, merge, concat, flatten
  • HCL: язык, на котором пишут Terraform
  • Блок variable: вход в конфигурацию
tags: hcl, expressions, functions

#references-and-cycle

seniorредко

Откуда берётся cycle и как Terraform его находит?

Что отвечать

Terraform строит DAG из ресурсов и references. Cycle - когда A зависит от B, B зависит от C, C ссылается на A. На plan получаешь ошибку «Cycle: A, B, C». Чаще всего источник - неявная зависимость через `lifecycle.replace_triggered_by` или `depends_on` в обе стороны. Реже - честная циклическая ссылка в HCL: `resource "a" { x = b.y }` и `resource "b" { y = a.z }`. Лечение: `terraform graph` визуализирует граф, видно где замкнулось; обычно ломают через data source или выносят общее значение в local.

Что хотят услышать

Senior должен: - назвать `terraform graph | dot -Tsvg > graph.svg` как штатный способ найти cycle глазами - объяснить откуда чаще всего берётся cycle: `depends_on` руками там где Terraform уже видит implicit dep через атрибут; или неявная зависимость через `lifecycle.replace_triggered_by` - сказать что разрыв через local - частое решение: оба ресурса ссылаются на `local.shared_id` вместо друг на друга - упомянуть что `module`-границы не помогают: cycle через границу модуля Terraform тоже находит

Подводные камни

  • ✗ Добавить `depends_on` «для надёжности» там, где уже есть implicit зависимость через атрибут - вместо помощи получаешь cycle
  • ✗ Запутаться в `replace_triggered_by` - частая причина внезапных cycle'ов после рефакторинга lifecycle
  • ✗ Думать что cycle ловится в IDE - нет, только на `terraform plan` после resolve графа

Follow-up

  • ? Как `terraform graph` помогает в поиске cycle? Что в его выводе?
  • ? Чем `depends_on` отличается от implicit-зависимости через атрибут?
  • ? Что делает `replace_triggered_by` и почему он часто причина цикла?

Глубина в базе знаний

  • Ссылки в HCL: как читать aws_s3_bucket.demo.bucket
  • DAG в Terraform, как строится граф зависимостей
  • terraform graph: граф зависимостей ресурсов
  • Зависимости ресурсов: явные и неявные
tags: hcl, graph, referencesbook: mastering.terraform.epub:ch5
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки