Конфиг, который умеет считать сам себя

02 окт 2026
5,810
667
331
6 дней

Однажды я полдня вылавливал опечатку в YAML-файле. Два пробела вместо четырёх, и весь деплой развалился с сообщением в духе «неверный формат на строке 214». Строка 214 была абсолютно пустой. Знакомая боль, правда?

Именно из этой категории проблем вырос проект, о котором хочу рассказать. HCL — HashiCorp Configuration Language. Это не ещё один сериализатор данных, а инструмент для построения собственных языков конфигурации. Разница принципиальная, и сейчас объясню, в чём она.

Что это вообще такое

HCL — это библиотека на Go. Точнее, набор инструментов, с помощью которых ваше приложение объявляет: «вот такие атрибуты и блоки я ожидаю в конфиге». HCL читает файл, проверяет, что структура совпадает с заявленной, и отдаёт готовые объекты. Всё остальное берёт на себя вызывающая сторона.

Здесь и кроется главный трюк. JSON и YAML отвечают на вопрос «как записать данные». HCL отвечает на вопрос «как записать конфигурацию конкретного приложения так, чтобы и человеку было приятно, и машине понятно». Это разные задачи, хотя на первый взгляд кажутся похожими.

Проекту уже больше десяти лет — репозиторий живёт с 2014 года. И это не игрушка на выходные: HCL лежит в основе Terraform, а через него — в инфраструктуре огромного числа компаний. Так что если вы когда-нибудь писали resource "aws_instance" "web" { ... }, вы уже работали с HCL, возможно, даже не подозревая об этом.

Реклама

Два синтаксиса на один и тот же конфиг

Самая интересная идея проекта — один и тот же конфиг можно описать двумя способами. Первый — родной синтаксис, рассчитанный на людей:

io_mode = "async"

service "http" "web_proxy" {
  listen_addr = "127.0.0.1:8080"

  process "main" {
    command = ["/usr/local/bin/awesome-app", "server"]
  }

  process "mgmt" {
    command = ["/usr/local/bin/awesome-app", "mgmt"]
  }
}

Второй — эквивалентный JSON, который проще генерировать кодом:

{
  "io_mode": "async",
  "service": {
    "http": {
      "web_proxy": {
        "listen_addr": "127.0.0.1:8080",
        "process": {
          "main": {
            "command": ["/usr/local/bin/awesome-app", "server"]
          }
        }
      }
    }
  }
}

Зачем это нужно на практике? Часть конфигов пишут руками в читаемом виде, часть генерируют скрипты и пайплайны. HCL позволяет смешивать оба источника, не переписывая систему под каждый формат. Приложение читает и то, и другое одинаково.

Выражения прямо в конфиге

Вот здесь становится по-настоящему любопытно. Значения атрибутов в HCL — это не просто строки и числа, а выражения:

# Арифметика с литералами и переменными приложения
sum = 1 + addend

# Интерполяция строк и шаблоны
message = "Hello, ${name}!"

# Функции, которые предоставляет приложение
shouty_message = upper(message)

Простая арифметика, подстановка переменных, вызовы функций, доступные из внешнего кода. В JSON напрямую так не напишешь, но HCL даёт обходной путь: выражения можно встроить в строки JSON через синтаксис ${...}. Получается компромисс между строгим форматом данных и полноценным языком программирования.

В документации проекта это описано как попытка занять середину между «просто JSON» и «конфиг на Ruby, где можно случайно написать бизнес-логику». Мне кажется, они близки к балансу.

Как это выглядит в коде

Для типовых случаев есть высокоуровневый API, и он приятно короткий. Опишите структуру через Go-теги, и всё:

package main

import (
	"log"

	"github.com/hashicorp/hcl/v2/hclsimple"
)

type Config struct {
	IOMode  string        `hcl:"io_mode"`
	Service ServiceConfig `hcl:"service,block"`
}

type ServiceConfig struct {
	Protocol   string          `hcl:"protocol,label"`
	Type       string          `hcl:"type,label"`
	ListenAddr string          `hcl:"listen_addr"`
	Processes  []ProcessConfig `hcl:"process,block"`
}

type ProcessConfig struct {
	Type    string   `hcl:"type,label"`
	Command []string `hcl:"command"`
}

func main() {
	var config Config
	err := hclsimple.DecodeFile("config.hcl", nil, &config)
	if err != nil {
		log.Fatalf("Failed to load configuration: %s", err)
	}
	log.Printf("Configuration is %#v", config)
}

Всё содержимое конфига разбирается в структуру Config за один вызов. Если файл не совпадает с ожидаемой схемой, вы получите внятную ошибку с указанием места, а не загадочное «unexpected token». Это, пожалуй, главная причина, по которой люди выбирают HCL: качество сообщений об ошибках заложено в саму архитектуру.

Если нужен контроль над разбором, вычислением выражений или собственная логика обработки, есть низкоуровневый API. Он заметно сложнее, но иногда без него не обойтись.

Второй мажор и почему он не совместим с первым

HCL 2.0 объединил в себе два проекта: собственно HCL первого поколения и язык интерполяции HIL. Раньше это были отдельные библиотеки, теперь — единый язык с произвольными выражениями. Парсер и Go-API переписали с нуля, поэтому прямого пути миграции с первой версии нет. Синтаксис похож, а реализация внутри совсем другая.

Если у вас в проекте оказались обе версии, Go спасает механизм семантического версионирования импортов:

import (
    hcl1 "github.com/hashicorp/hcl"
    hcl2 "github.com/hashicorp/hcl/v2"
)

Две версии могут спокойно жить в одной программе под разными именами. Ещё один нюанс: HCL 2 нельзя импортировать в проект, который не использует Go Modules. Для старых GOPATH-проектов это может стать неприятным сюрпризом.

Кому это нужно

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

Вторая — вы работаете с Terraform и хотите понимать, что происходит под капотом. Знание HCL тут окупается быстро.

А вот если вам просто нужно сохранить и прочитать пару словарей в файл, HCL избыточен. Берите JSON или YAML, не мучайте себя.

Что стоит учесть

Проект живой: около 5,8 тысячи звёзд, регулярные коммиты, лицензия MPL-2.0. Но документация довольно сухая — по сути это набор спецификаций и примеров, а не учебник. Новичку придётся вчитываться в описания синтаксиса. Открытых issue тоже хватает, их больше двух сотен, так что на мгновенный ответ мейнтейнеров рассчитывать не всегда получится.

Начать проще всего с пакета hclsimple и примера из README. Если поймёте, что нужен более тонкий контроль, переходите к низкоуровневому API. А если хочется увидеть HCL в реальном деле, посмотрите на конфиги Terraform — это та же библиотека в боевых условиях.

Что мне здесь нравится больше всего: проект честно признаёт, что JSON незаменим как универсальный обменный формат, и не пытается его вытеснить. Вместо этого HCL предлагает там, где нужен именно конфиг с выражениями и проверяемой структурой, свой синтаксис, совместимый с JSON. Редкий случай прагматичного дизайна без попытки «победить» всех конкурентов.

🍪 Мы используем файлы cookie и сервис аналитики Яндекс.Метрика, чтобы сайт работал лучше. Продолжая пользоваться devtrends.ru, вы соглашаетесь с обработкой данных согласно Политике конфиденциальности.