Язык HCL: блоки, аргументы, выражения
HCL (HashiCorp Configuration Language) — язык, на котором пишут Terraform. Вся его грамматика держится на двух конструкциях: аргументы (имя = значение) и блоки (тип "метка" { ... }).
HCL спроектирован так, чтобы его одинаково легко читали и человек, и машина. Это не язык программирования в привычном смысле — это структурированный способ описать желаемое состояние.
Откройте любой .tf файл — вы увидите только две вещи. Аргумент присваивает значение имени: instance_type = "t2.micro". Блок — это контейнер с типом, опциональными метками и телом в фигурных скобках. Всё остальное — комбинации этих двух элементов.
Анатомия блока
# resource — тип блока
# "aws_instance" — тип ресурса (первая метка)
# "web" — локальное имя (вторая метка)
resource "aws_instance" "web" {
ami = "ami-0abcdef" # аргумент-строка
instance_type = var.size # ссылка на переменную
count = 3 # аргумент-число
monitoring = true # аргумент-bool
tags = { # аргумент-объект (map)
Name = "web-${count.index}" # интерполяция ${}
}
}
HCL знает основные типы: строки, числа, булевы, списки ["a", "b"], мапы { k = "v" }. Внутри строк работает интерполяция через ${...} — туда можно подставить переменную, результат функции или ссылку на другой ресурс. Комментарии: # и // для строки, /* */ для блока.
Как работает под капотом
Когда Terraform читает файл, он не выполняет код сверху вниз, как Python. Он парсит всё дерево блоков, а потом строит граф зависимостей по ссылкам вида var.size или aws_instance.web.id. Поэтому порядок блоков в файле не важен — важны только ссылки. Смоделируем простейший парсер аргументов, превращающий текст HCL в словарь:
hcl = '''
ami = "ami-0abcdef"
instance_type = "t2.micro"
count = 3
monitoring = true
'''
def parse_args(text):
result = {}
for line in text.strip().splitlines():
if "=" not in line:
continue
key, raw = line.split("=", 1)
raw = raw.strip()
if raw.startswith('"'):
val = raw.strip('"')
elif raw in ("true", "false"):
val = raw == "true"
else:
val = int(raw)
result[key.strip()] = val
return result
cfg = parse_args(hcl)
print(cfg)
print("тип count:", type(cfg["count"]).__name__)
«Попробуй сам ▶» — Terraform делает то же самое, только умнее: распознаёт строки, числа, булевы и ссылки, сохраняя их типы.
Частые ошибки
- Кавычки вокруг ссылок:
"${var.x}"там, где можно простоvar.x. Лишняя интерполяция ухудшает читаемость. - Путать список и мапу.
[...]— упорядоченный список,{...}— мапа ключ-значение. Их нельзя смешивать. - Забыть, что порядок блоков не важен. Новички расставляют ресурсы «по порядку выполнения» — это бессмысленно, граф строится по ссылкам.
Best practices
- Запускайте
terraform fmt— он выравнивает=и приводит код к единому стилю. - Используйте «голые» ссылки (
var.x), а интерполяцию${}— только когда строка действительно собирается из частей. - Группируйте логически связанные ресурсы в один файл, а не «как пришло в голову».
Разбор глубже
У HCL есть младший брат — JSON-синтаксис. Любую конфигурацию Terraform можно записать в файле .tf.json вместо .tf, и движок её поймёт одинаково. Зачем это нужно? Человек пишет на HCL, потому что он читаемее, а машины (генераторы конфигов, скрипты) иногда генерируют JSON-вариант, потому что его проще собрать программно. Под капотом HCL и его JSON-представление — это одна и та же модель данных, просто два способа записи.
Отдельного внимания заслуживают heredoc-строки и многострочные значения. Когда нужно вписать в аргумент целый скрипт или YAML-манифест, используют синтаксис <<-EOT ... EOT, который сохраняет переносы строк. А блок locals позволяет завести именованные промежуточные значения: вместо того чтобы повторять длинное выражение в десяти местах, вы вычисляете его один раз в locals и ссылаетесь как local.имя. Это не переменные в смысле ввода — это вычисляемые константы конфигурации, и они сильно улучшают читаемость по мере роста проекта.
Ещё одна деталь, экономящая часы отладки: HCL чувствителен к разнице между значением null и отсутствием аргумента. Передать аргументу null — значит явно сказать «используй значение по умолчанию», что иногда отличается от того, чтобы не указывать аргумент вовсе. На практике это всплывает, когда вы условно задаёте аргумент через тернарник: ветка с null корректно откатывает поле к дефолту провайдера, тогда как пустая строка или ноль были бы реальными значениями. Понимание этой тонкости помогает писать аккуратные опциональные конфигурации без сюрпризов на плане.
Итог: весь HCL — это аргументы и блоки. Terraform парсит дерево целиком и строит граф по ссылкам, поэтому порядок не важен. Дальше — главная сущность языка: ресурс.