CI считает terraform plan ошибкой при найденных изменениях: detailed-exitcode

Terraform plan напечатал корректный diff, но job стала красной. В команду добавили -detailed-exitcode, и найденные изменения теперь возвращают код 2. Для shell с автоматическим выходом по ошибке это ненулевой статус, хотя планирование завершилось успешно. CI нужно научить различать результат сравнения и сбой выполнения.

У Terraform и OpenTofu базовый контракт одинаков: 0 — успешный план без изменений, 1 — ошибка, 2 — успешный план с изменениями. Без detailed-exitcode успешные планы обычно не дают такого различия. Поэтому сначала убедитесь, что флаг действительно передан, а не изменяйте обработку любого ненулевого кода.

Сохраним код сразу после команды

Следующий POSIX shell-скрипт принимает имя используемого CLI как первый аргумент. Он не выполняет apply. Каталог должен быть инициализирован, входные переменные и credentials — предоставлены обычным для проекта способом.

#!/bin/sh
set -eu

iac_tool=${1:-terraform}
case "$iac_tool" in
  terraform|tofu) ;;
  *) printf '%s\n' 'Expected terraform or tofu' >&2; exit 64 ;;
esac

plan_status=0
"$iac_tool" plan -input=false -detailed-exitcode -out=review.tfplan || plan_status=$?

case "$plan_status" in
  0)
    printf '%s\n' 'PLAN_HAS_CHANGES=false' > plan.env
    ;;
  2)
    printf '%s\n' 'PLAN_HAS_CHANGES=true' > plan.env
    ;;
  *)
    printf 'Plan failed with exit code %s\n' "$plan_status" >&2
    exit "$plan_status"
    ;;
esac

Сохраните файл как ci/plan.sh и вызывайте sh ci/plan.sh terraform либо sh ci/plan.sh tofu. Конструкция справа от || получает исходный код сразу, поэтому set -e не обрывает скрипт до классификации. Код 1 и любой неожиданный ненулевой код остаются ошибкой. Нельзя заменять всё выражение на plan || true: тогда различие исчезнет.

Выходной файл — только после успешного плана

При коде 0 записывается false, при 2 — true. В случае ошибки скрипт не создаёт новый успешный флаг. В CI лучше использовать чистый рабочий каталог, чтобы plan.env или review.tfplan не остались от прежней попытки. Потребитель должен брать артефакты именно текущей producer job, а не искать одноимённые файлы в общей папке.

Для GitLab можно опубликовать plan.env как dotenv report и review.tfplan как artifact с ограниченным доступом. Это отдельная настройка передачи данных. Сохранённый plan способен содержать чувствительные значения; не публикуйте его в общедоступный журнал ради диагностики кода возврата.

Почему allow_failure:exit_codes не всегда достаточно

Можно разрешить job код 2 на уровне CI, но тогда она будет отмечена как допустимо неуспешная, а последующие действия с artifacts и условиями потребуют отдельного внимания. Явная классификация внутри wrapper проще отделяет «успешно найдены изменения» от «план не построен». Выбор зависит от того, как команда хочет показывать результат в интерфейсе.

Если вывод пропускают через tee, проверьте, чей код сохраняется. В POSIX shell pipeline обычно возвращает статус последней команды. Тогда вместо 2 или 1 от plan вы получите 0 от tee. В показанном wrapper конвейера нет: журнал команды остаётся в stdout, а статус фиксируется напрямую.

Проверим три результата без инфраструктуры

Для проверки логики wrapper можно подставить тестовый executable terraform или tofu в начало PATH, который возвращает заданный код. Сценарии 0 и 2 должны завершить wrapper успешно и создать разные plan.env;1 должен завершить его ошибкой. Такая проверка валидирует shell-классификацию, но не доказывает, что реальный provider способен построить план.

Затем на отдельном учебном state проверьте настоящий plan без изменений и с изменённым входом.

Найденный diff не означает разрешение на apply. Потребитель true может открыть этап ревью, а false — закончить проверку. Ошибка планирования должна остановить процесс отдельно. Тогда CI сообщает три разных факта понятным образом и не объявляет нормальное предложение изменений аварией.

Ещё по теме

Продолжить изучение

Хотите управлять инфраструктурой через код и понимать последствия изменений? Plan, state, модули и работа с существующими ресурсами — темы программы «Terraform и OpenTofu: инфраструктура как код».

Продолжить на практике

  • Terraform и OpenTofu: инфраструктура как код ↗

    Описывайте инфраструктуру в коде, проверяйте изменения до применения и работайте со state без сюрпризов. HCL, модули, remote state, импорт, CI и различия Terraform/OpenTofu — на воспроизводимом учебном окружении.