indent и nindent: укрощение отступов

Если чарт падает с «error converting YAML to JSON» — почти всегда виноваты отступы. Учимся управлять ими.

indent N добавляет N пробелов в начало каждой строки текста. nindent N делает то же, но сначала добавляет перенос строки — отсюда буква n (newline).

YAML значим по отступам: сдвиг на пробел меняет структуру. Когда шаблон вставляет многострочный блок, его строки нужно сдвинуть на правильную глубину. Этим занимаются indent и nindent.

Корень проблемы в том, что шаблонизатор Go и YAML живут в разных мирах и не знают друг о друге. Для движка шаблонов манифест — это просто поток текста: он не понимает, что строка начинается на восьмом пробеле и что вставляемый блок должен лежать «внутри» ключа выше. Он слепо подставляет туда, где стоит выражение {{ ... }}, ровно тот текст, который вернула функция. А функция вроде toYaml отдаёт блок, выровненный по левому краю, с нулевым отступом. Совместить эти два мира — задача программиста, и единственный инструмент для этого — ручное добавление пробелов перед каждой строкой вставляемого блока. Отсюда и берутся indent/nindent: они не «понимают» YAML, они механически двигают текст, а ответственность за правильное число пробелов лежит на вас.

Именно поэтому отступы — самая частая причина «странных» падений чартов у новичков. Ошибка не в логике, не в значениях, а в одной неверной цифре после nindent. И сообщение об ошибке указывает не на шаблон, а на отрендеренный YAML, который вы даже не писали руками. Привыкнуть к этому стоит сразу: при любой ошибке вида «error converting YAML to JSON» или «did not find expected key» первым делом смотрите на отступы во вставленных блоках.

Проблема: многострочная вставка

Допустим, toYaml вернул блок:

requests:
  cpu: 250m
limits:
  cpu: "1"

Этот текст начинается с нулевого отступа. Но в манифесте он должен лежать под ключом resources: на глубине 8 пробелов. Просто подставить нельзя — поедет.

indent: сдвинуть каждую строку

        resources:
{{ toYaml .Values.resources | indent 8 }}

Каждая строка блока получит +8 пробелов. Но есть подвох: строка с самим {{ ... }} уже стоит на нулевом отступе, и между resources: и вставкой не должно быть лишнего. Управлять этим вручную неудобно — поэтому чаще берут nindent.

nindent: перенос + отступ в одном

nindent 8 сам ставит перевод строки перед блоком и сдвигает на 8. Это позволяет писать вставку на той же строке, что и ключ:

      resources: {{- toYaml .Values.resources | nindent 8 }}

Результат:

      resources:
        requests:
          cpu: 250m
        limits:
          cpu: "1"

Здесь {{- срезает пробел после resources:, а nindent сам делает новую строку с нужным отступом. Это каноничный паттерн Helm — запомните его, он встречается в каждом втором чарте.

Разберём, почему именно nindent избавляет от мороки. Когда вставка стоит на отдельной строке (вариант с indent), нужно вручную следить, чтобы между ключом resources: и блоком не вклинилась пустая строка и чтобы сам блок начинался ровно там, где надо. nindent переносит контроль над переводом строки внутрь себя: он гарантированно ставит один перенос и затем отступ. Поэтому вставку можно писать прямо после двоеточия ключа — глаз видит «ключ: значение», а движок аккуратно разворачивает многострочный блок ниже. Меньше движущихся частей — меньше способов ошибиться.

Зачем дефис в {{- и -}}

Дефис у границ выражения — это управление пробелами и переносами, и в контексте отступов он критичен. {{- съедает весь пробельный «мусор» (пробелы и переносы) слева от выражения, а -}} — справа. Без {{- перед nindent на строке осталась бы и собственная разметка строки шаблона, и перенос от nindent — получилась бы лишняя пустая строка. Простое правило: {{- ставят, когда выражение занимает строку целиком и эту строку надо «убрать» из вывода (типичный случай для if/range/end), а также перед nindent после ключа, чтобы срезать унаследованный пробел. Злоупотреблять -}} справа не стоит: он умеет «склеить» вашу строку со следующей и неожиданно сломать YAML.

indent против nindent: когда какой

СлучайФункция
Вставка на отдельной строке, ключ вышеindent
Вставка после ключа на той же строкеnindent (+ {{-)

Типичный лейбл-блок

metadata:
  labels: {{- include "webapp.labels" . | nindent 4 }}

Тот же приём с include (именованным шаблоном) — об этом следующий урок.

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

Обе функции — чистые строковые операции из Sprig. indent N s разбивает строку s по переносам и приклеивает N пробелов к каждой части. nindent N s = "\n" + indent N s. Никакой магии про YAML тут нет — это слепое добавление пробелов. Вот почему ошибка в числе отступа всплывает только на этапе YAML-парсинга после рендера, и сообщение указывает не на шаблон, а на номер строки в готовом манифесте. Отлаживать удобно через helm template, глядя на отрендеренный текст.

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

  • Перепутать indent и nindent. С indent после ключа на той же строке нет переноса — блок прилипнет к ключу. С nindent на отдельной строке появится лишняя пустая строка.
  • Неверное число отступа. Считайте глубину ключа: если ключ на 6 пробелах, содержимое — на 8 (обычно +2 за уровень для карты).
  • Забыть {{- перед nindent. Останется пробел после двоеточия — иногда безобидно, иногда ломает.

Итог

  • indent N добавляет N пробелов к каждой строке; nindent N — то же плюс ведущий перенос.
  • Каноничный паттерн: ключ: {{- toYaml ... | nindent N }}.
  • Это слепые строковые операции; ошибки отступа всплывают при YAML-парсинге — отлаживайте через helm template.
Проверьте себя
1. Чем nindent отличается от indent?
AНичем
Bnindent дополнительно добавляет перенос строки перед блоком
Cindent работает только с числами
Dnindent удаляет отступы
2. Почему ошибка отступа всплывает не в шаблоне, а в готовом манифесте?
AHelm прячет шаблоны
Bindent/nindent — слепые строковые операции; YAML парсится только после полного рендера
CЭто баг
DИз-за кеширования
3. Какой паттерн каноничен для вставки блока после ключа?
Aключ: {{ toYaml ... }}
Bключ: {{- toYaml ... | nindent N }}
C{{ indent }} без ключа
DtoYaml без функций отступа