Files
tf_provider/HISTORY/2026-09-30_yaml_pipeline_hardening.md
T
Repinoid 89bfb46b1c docs(history): этап 4 — перепроверка после перегенерации + подводные камни
Перенесено в файл репозитория (а не только в служебную память VS Code):
- результаты полной перегенерации и перепроверки всех трёх стендов;
- подводные камни: случайные default от API (детектор дрейфа по побайтовому
  сравнению не работает); случайный default вшивается в Go-код → drift 15 файлов
  сразу после перегенерации; generated/<стенд>/go|docs стареют после шага 01;
  403 без браузерного User-Agent (DDoS-Guard);
- актуальная карта пайплайна, токены, что удалено и что оставлено осознанно.
2026-09-30 10:30:41 +03:00

18 KiB
Raw Blame History

2026-09-30 — Ужесточение пайплайна генерации YAML (безопасная замена, чистка легаси)

Связанные материалы: TOOLS/README.md (канонический пайплайн, порядок шагов), TOOLS/ARCHITECTURE.md (спецификация), память репозитория: pipeline-legacy.md.

Задача

  1. Разобрать, что в генерации YAML устарело.
  2. Перегенерировать YAML по всем стендам так, чтобы старое удалялось безопасно, а новое создавалось атомарно.
  3. Задокументировать всё, чтобы история изменений прослеживалась.

Команда пользователя: «делай как ПОЛОЖЕНО, как в best practices».

Что было не так (до правок)

TOOLS/scripts/01_generate_yamls.sh

Место (до) Проблема
стр. 230 rm -f "$output_glob" старый YAML удалялся до генерации → при сбое API файл исчезал, новый не создавался (неатомарно per-service)
стр. 122 «Полной очистки нет» YAML исключённого сервиса оставался в каталоге и попадал в сборку
стр. 91 ls -t "${ROOT_DIR}"/*.token легаси-фолбэк токена: в корне токенов нет; поиск «последнего» мог подхватить чужой токен
стр. 109 API_ENDPOINT="${NUBES_API_ENDPOINT:-https://lk-api-gateway.ngcloud.ru/...}" молчаливый уход в PROD, если в профиле нет endpoint
стр. 167 svc_name="" имя всегда пустое, хотя шапка обещала парсинг из списка → лишний HTTP-запрос на каждый сервис (2 запроса вместо 1)
стр. 46–50 дубль SERVICES_FILE_DEFAULT (одинаковое присваивание в if)
шапка vs код «REQUEST_DELAY по умолчанию 0.2», в коде 0.5; путь вывода указан как provider/resources_yaml (устарел)

TOOLS/yaml-generator/internal/config/config.go

Место (до) Проблема
Load() стр. 33 свой дефолт endpoint = PROD gateway
loadToken() + findLatestToken() легаси-фолбэк «последний *.token в корне репо»
Load() стр. 60 путь filepath.Join(repoRoot, "devops", "config", "services_list.txt"), причём FindRepoRoot() возвращает каталог provider/ → путь заведомо не существовал

Легаси-скрипты

10_yaml_stability_run.sh, 11_yaml_stability_run_latest.sh, 12_generate_yamls_latest.sh, 13_generate_yamls_clean.sh, 02_generate_resources_and_docs_template_v2.sh — мертвы: зовут 01 без --profile (→ exit 2), ищут *.token в корне репо, а 13 вдобавок делал rm -f provider/resources_yaml/*.yaml. Ни один рабочий скрипт их не вызывает (ссылки есть только в исторических HISTORY/, NOTES/).

Что сделано

1. 01_generate_yamls.sh — безопасная запись по принципу staging → атомарная замена

Новый алгоритм:

staging = generated/<stand>/resources_yaml.staging.<pid>
   ↓ генерация всех сервисов пачкой per-service в staging
   ↓ при пустом failures:
       mv resources_yaml → resources_yaml.bak-<UTC>     (бэкап, ротация KEEP_BACKUPS=5)
       mv staging        → resources_yaml               (атомарный rename в том же FS)
   ↓ при непустом failures:
       замена ОТМЕНЯЕТСЯ, рабочий каталог не тронут, staging оставлен для разбора, exit 1

Прочие изменения:

  • каталог помечается маркером .stand; генерация в каталог чужого стенда запрещена (exit 2);
  • embed.go создаётся теперь в staging (обязательный go:embed *.yaml);
  • NUBES_API_ENDPOINT обязателен, иначе exit 2;
  • токен берётся только из NUBES_API_TOKEN/TOKEN_FILE; легаси-поиск удалён;
  • имя сервиса — из 2-го поля services_list.txt; лишний python-запрос удалён;
  • удалён дубль SERVICES_FILE_DEFAULT; синхронизированы комментарии.

2. yaml-generator/internal/config/config.go

  • NUBES_API_ENDPOINT обязателен (нет PROD-дефолта);
  • loadToken() больше не ищет *.token в корне репо; findLatestToken и getenvDefault удалены как мёртвые;
  • при отсутствии NUBES_SERVICE_ID требуется явный NUBES_SERVICES_FILE (угадывание пути удалено).

3. Легаси-скрипты отключены (fail-fast)

В начало каждого добавлен guard: сообщение DEPRECATED + exit 2. Файлы не удалены (удаление — отдельное решение владельца), но теперь они не могут сделать ничего вредного.

4. Документация

  • TOOLS/README.md — добавлен раздел «Канонический пайплайн (порядок шагов)» и описание безопасной генерации;
  • TOOLS/ARCHITECTURE.md — ссылки devops/… заменены на TOOLS/config/<stand>/…;
  • память репозитория — pipeline-legacy.md уточнена.

Прогон по всем стендам (результат)

Токены проверены прямыми запросами к API (с браузерным User-Agent, иначе DDoS-Guard отдаёт 403):

Стенд Endpoint HTTP YAML после генерации Stale Failures
dev lk-api-gateway-dev.ngcloud.ru 200 40 нет нет
test lk-api-gateway-test.ngcloud.ru 200 36 нет нет
prod lk-api-gateway.ngcloud.ru 200 35 нет нет

Команды:

./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/dev
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/test
./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/prod

Проверка целостности: diff -rq нового каталога dev с бэкапом даёт различия только в случайных default-суффиксах, которые API генерирует при каждом запросе (db-ievgpdvu → db-ujama5rb, flask-seqtiq3t → flask-xwfdxdqh и т.п.) — структурной регрессии нет. Это же объясняет, почему побайтовое сравнение двух прогонов не может быть использовано как «детектор дрейфа».

Коммиты

  • 12b3932 — fix(tools): безопасная генерация YAML — staging + атомарная замена, без легаси-фолбэков
  • ad4daab — chore(tools): легаси-скрипты генерации отключены (fail-fast DEPRECATED)

Проверки

  • bash -n для 01_generate_yamls.sh и всех guard-скриптов — OK;
  • go vet ./... + go build для yaml-generator — OK;
  • guard отдаёт exit 2;
  • прогон dev/test/prod — 0 failures, stale отсутствует, staging не остаётся;
  • бэкапы создаются: generated/dev/resources_yaml.bak-20260930T063152Z и т.д.

Этап 2 — удаление мёртвого (по команде «удаляй всё старое, аккуратно»)

Удалено (1e796c8) — 100% мёртвый код/данные, ничего их не вызывает:

Файл Почему удалён
TOOLS/scripts/10_yaml_stability_run.sh зовёт 01 без --profile (exit 2), ищет *.token в корне
TOOLS/scripts/11_yaml_stability_run_latest.sh цепочка на 10, та же поломка
TOOLS/scripts/12_generate_yamls_latest.sh зовёт 01 без --profile, ищет *.token в корне
TOOLS/scripts/13_generate_yamls_clean.sh цепочка на 12 + делал rm -f provider/resources_yaml/*.yaml
TOOLS/scripts/02_generate_resources_and_docs_template_v2.sh легаси-дубль канонического 02_generate_resources_and_docs_v2.sh
TOOLS/config/services_list.txt (общий) код его не читает; как «объединение» устарел: активный 27 (в test/prod — «нет в UI»), нет 87/88/97/153, которые есть в dev

Проверка «ничего не вызывает»: grep по всему репо находил ссылки только в исторических HISTORY/, NOTES/, docs/ (не исполняются).

Правки ссылок (c822ae2): README.md, HOW_TO/README.md, HOW_TO/DEVOPS_BUILD_PIPELINE.md, HOW_TO/HOWTO_ADD_NEW_SERVICE.md (включая переписанный блок «Быстрый старт» с devops/ на ./TOOLS/scripts/*), DOCS_PIPELINE/README.md, scripts/publish-doc-page.sh, .gitignore.

Проверка после удаления: bash -n для всех TOOLS/scripts/*.sh и scripts/publish-doc-page.sh — OK; smoke-прогон 01 --profile TOOLS/config/dev — 40 YAML, замена атомарная, бэкап создан.

Не удалено (осознанно):

  • поддержка легаси-прокси index.cfm в 01 и yaml-generator — это совместимость с работающими пользователями старого API (провайдер v5.0.75, secrets/stands.md);
  • DOCS_PIPELINE/publish-docs.sh — сам файл помечен «справочная копия, не подменяет пайплайн»;
  • HISTORY/, NOTES/, docs/ — исторические документы (в них devops/ и легаси-скрипты упоминаются как история, это нормально);
  • scripts/*.py и s3_notification_example.sh — ручные утилиты, вызываются вручную.

Этап 3 — сверка списков стендов с облачным каталогом (источник истины)

Принято: истина — то, что перечислено в облаке. Определяется эндпоинтом каталога:

# «перечислено в облаке» (продакшен-готовые сервисы стенда)
GET {NUBES_API_ENDPOINT}/services?limit=200&isProductionReady=true
# для сравнения: без фильтра отдаются ВСЕ сервисы платформы, включая
# DEPRECATED и не заявленные в каталоге (48 у prod, 49 у test, 60 у dev)

Требуется браузерный User-Agent (иначе DDoS-Guard отдаёт 403) и Referer.

Результат на 2026-09-30:

Стенд Облако (isProductionReady=true) Активных в services_list.txt Лишние в файле Не хватало
dev 40 40 нет нет
test 36 36 нет нет
prod 36 35 → 36 нет 151 k8sOpenbao (Vault)

У остальных 12 закомментированных prod-сервисов, присутствующих в API, isProductionReady=false — они закомментированы обоснованно. Четыре id в файле отсутствуют в каталоге prod вовсе (32 vmpostgre, 87 k8svalkey, 153 nifi, 175 k8sGo).

Исправлено коммитом a68a36a: 151 k8sOpenbao раскомментирован (комментарий «нет в PROD UI» устарел), prod перегенерирован — 36 YAML, ровно как в облаке.

Этап 4 — перепроверка после полной перегенерации + подводные камни

Команда: «сгенери YAML для всех стендов, проследи чтобы старого ничего не осталось, перепроверь после генерации всё». Выполнено три прогона 01:

for s in dev test prod; do ./TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/$s; done
# все три: exit=0

Результат перепроверки (2026-09-30)

Стенд YAML = активных в списке = облако (isProductionReady=true) Stale Дубли id Failures
dev 40 ✅ 40 ✅ 40 нет нет пусто
test 36 ✅ 36 ✅ 36 нет нет пусто
prod 36 ✅ 36 ✅ 36 нет нет пусто

Дополнительно проверено:

  • staging-каталоги (resources_yaml.staging.*) — не осталось ни одного;
  • в resources_yaml/ только *.yaml, .stand, embed.go — посторонних файлов нет;
  • .stand в каждом каталоге совпадает с профилем (dev/test/prod);
  • бэкапы прошлых версий: dev 3, test 2, prod 2 (ротация KEEP_BACKUPS=5);
  • generated/<стенд>/tmp/yaml_gen_failures.txt — пусты;
  • git status — чисто.

⛔ Подводные камни, найденные при перепроверке (важно на будущее)

  1. API отдаёт случайные default. Часть параметров приходит со случайным суффиксом (db-ievgpdvu → db-ujama5rb, kvname-grzjes7l → kvname-g3s0uof2, flask-seqtiq3t → flask-xwfdxdqh). Поэтому побайтовое сравнение двух прогонов не является детектором дрейфа — различия в этих строках не регрессия.
  2. Случайный default вшивается в сгенерированный Go-код. Пример: generated/dev/go/151_k8s_openbao_kv_resource.go содержит Default: stringdefault.StaticString("kvname-XXXX"). Следствие: check_generated_drift.sh dev показывает дрейф 15 файлов сразу после любой перегенерации YAML — это не ошибка оператора.
  3. Производные артефакты стареют молча. generated/<стенд>/go и generated/<стенд>/docs создаются шагом 02 и после нового 01 становятся старше своих источников (на момент проверки: go/docs dev — 08:46, YAML dev — 10:18). Отдельно живёт эфемерная копия provider/internal/resources_gen + provider/resources_yaml (её кладёт dev-materialize.sh, маркер .stand = стенд). Их нужно обновлять шагом 02 после каждого 01.
  4. Прямые HTTP-запросы к API без браузерного User-Agent получают 403 (DDoS-Guard). С User-Agent + Referer — 200.

Актуальная карта пайплайна на 2026-09-30

  • Единственный путь генерации YAML: TOOLS/scripts/01_generate_yamls.sh --profile TOOLS/config/<стенд> (--profile обязателен, без него exit 2).
  • Один универсальный движок на все стенды: TOOLS/bin/yaml-generator, стенд задаётся переменными окружения (NUBES_API_ENDPOINT, NUBES_API_TOKEN, NUBES_SERVICE_ID, NUBES_SERVICE_NAME, NUBES_OUTPUT_DIR); список сервисов — свой у каждого стенда.
  • Стенд-специфичных хардкодов в коде нет — контролируется check_hardcoded_service_ids.sh.
  • Токены: secrets/{dev,test,prod}.token (валидны на 2026-09-30, срок до 2026-12-27); обновление — TOOLS/scripts/00_token_manager.sh (keycloak refresh, THRESHOLD_MIN=10).
  • Удалены как мёртвые (1e796c8): 10/11/12/13_yaml_*.sh, 02_generate_resources_and_docs_template_v2.sh, общий TOOLS/config/services_list.txt.
  • Оставлены осознанно: поддержка легаси-прокси index.cfm, справочная копия DOCS_PIPELINE/publish-docs.sh, исторические HISTORY//NOTES//docs/, ручные утилиты scripts/*.py.

Открытые вопросы (на решение владельца)

  1. Поддержка легаси-прокси index.cfm: оставляем или выпиливаем (README уже помечает закрытые API как «не использовать»)?
  2. Прочие .gitignore-паттерны мёртвых каталогов (universal_rebuild/*, provider/generated/) — чистить?