# 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//resources_yaml.staging. ↓ генерация всех сервисов пачкой per-service в staging ↓ при пустом failures: mv resources_yaml → resources_yaml.bak- (бэкап, ротация 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//…`; - память репозитория — `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 | нет | нет | Команды: ```bash ./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 — сверка списков стендов с облачным каталогом (источник истины) Принято: **истина — то, что перечислено в облаке**. Определяется эндпоинтом каталога: ```bash # «перечислено в облаке» (продакшен-готовые сервисы стенда) 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`: ```bash 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/`) — чистить?