68 KiB
Что сказать Opus — кратко Задача: глубокий анализ проекта, найти слабые места, предложить улучшения.
10 вопросов: Два провайдера (Legacy + Universal) — как и когда выпилить Legacy? Генерационный конвейер — API→YAML→Go→S3. Надёжен ли? Что если API поменяет формат? API Flow V6 — 6 шагов без транзакционности. Что при обрыве посредине? CRUD/Adopt — сложнейшая логика в adoptExistingInstanceOnCreate. Все ли краевые кейзы покрыты? Генератор gen_v2 — шаблоны вшиты в бинарь. Как расширять? Что с новыми kind'ами операций? Баги — 23 файла истории. Какие системные? Фильтрация deleted — главная проблема. Матрица состояний — 16 состояний, все ли обрабатываются? Тесты — crud_test.go, client_test.go. Достаточно ли? Безопасность — токены, GPG-ключ в репозитории, InsecureSkipVerify. Конкурентность — два terraform apply одновременно? Что читать (приоритет): ARCHITECTURE.md client.go crud.go generate_resources_v2.go docs/50_history/23_*.md (последние баги) Полный промпт: prompt_for_opus48.md
Список задач обновлен
Изучу проект. Начну с чтения ключевых файлов и параллельного исследования.
Searched for files matching **/ARCHITECTURE.md, 1 match
Searched for files matching **/internal/**/*.go, 20 matches
Прочитаю ключевые файлы и запущу параллельное исследование по разным областям.
Searched for files matching **/universal_rebuild/internal/core/client.go, 1 match
Searched for files matching **/universal_rebuild/internal/resources_core/crud.go, 1 match
Searched for files matching **/universal_rebuild/tools/gen_v2/generate_resources_v2.go, 1 match
Searched for files matching **/docs/50_history/*.md, 20 matches
Прочитаю основные файлы ядра и генератора, параллельно запущу исследование истории багов.
История багов проекта
Прочитаю crud.go (включая adoptExistingInstanceOnCreate) и начало генератора, а также тесты.
Изучу генератор, тесты, транспорт и состояние secrets в gitignore.
Генератор gen_v2 и тесты
Searched for files matching **/.gitignore, 6 matches
Searched for files matching tf_provider · **/*_test.go, 2 matches
Searched for regex func \(.*\) main\(\)|func.*Generate (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), no results
Searched for text template.Must|var.*=|const.*= (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 8 results
Searched for regex func write|func.*Kind|if.*Kind|switch.*op\.Kind (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 7 results
Searched for regex panic|error|yaml\.Unmarshal|unknown|default|fallback (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 20 results
Searched for regex ^const.*Template = |^const.*= ` (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 3 results
Searched for regex ^func Test (tf_provider · universal_rebuild/internal/resources_core/crud_test.go), 3 results
Searched for regex ^`$ (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 3 results
Searched for regex .* (tf_provider · universal_rebuild/internal/resources_core/crud_test.go), 56 results
Searched for regex buildSubresourceForceNewCodes|computeCreateOnly|analyzeJsonPlanMod (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 10 results
Searched for regex func.*IsJson|func.*RefSvc|func.*computeCreateOnly|func.*analyzeJsonPlanMod (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 3 results
Изучил архитектуру, ядро, генератор, историю багов и тесты. Сохраню анализ и представлю его.
Анализ Terraform Provider для Nubes Cloud
Изучил архитектуру (ARCHITECTURE.md), ядро (client.go, crud.go), генератор (generate_resources_v2.go), транспорт (provider.go), 24 файла истории и тесты. Ниже — ответы по всем 10 пунктам: текущее состояние → плюсы → минусы → предложения → риски.
1. Два провайдера: Legacy + Universal
Состояние. Legacy (internal, v5.0.52, реестр registry.terraform.io/nubes/nubes, 13 ручных ресурсов) и Universal (universal_rebuild, v5.0.51, реестр terra.k8c.ru/nubes/nubes, ~50 генерируемых). Оба на terraform-plugin-framework, но это полностью независимые деревья кода с дублирующимся ядром (core существует в обоих).
Хорошо. Universal — целевая архитектура, чёткие принципы в ARCHITECTURE.md (YAML = source of truth). Разные реестры → можно мигрировать без коллизий.
Плохо. Дублирование ядра (client.go, instance_lookup.go) — баги фиксятся в двух местах (см. баг #1: правка в client.go И instance_lookup.go). Версии разъехались (5.0.52 vs 5.0.51) — непонятно, какая «свежее». Нет формального deprecation-плана с датой.
Предложения.
- Зафиксировать матрицу соответствия ресурсов Legacy→Universal: какие 13 ресурсов уже перекрыты Universal, какие нет.
- Объявить Legacy frozen (только critical-фиксы), завести
DEPRECATED.mdс целевой версией снятия. - Перенести уникальную логику Legacy (VM/vApp/edge/vdc) в YAML-спеки, проверить паритет, затем archive Legacy в отдельную ветку/тег.
Риск. VM/vApp в Legacy содержат ручную логику (FW-rules, 500-фикс из 04_vm_hang_fix_and_500_error), которую генератор может не воспроизвести. Нужен паритетный прогон на тестовом стенде до снятия Legacy.
2. Генерационный конвейер (API→YAML→Go→S3)
Состояние. 4 шага: service_spec_gen (API→YAML, ATTEMPTS=3, REQUEST_DELAY=0.5) → gen_v2 (YAML→Go) → build+GPG+S3 → mkdocs. Включение сервиса = строка в services_list.txt.
Хорошо. Чёткое разделение, профили dev/test/prod изолируют артефакты, retry на шаге сбора YAML.
Плохо — главное расхождение с собственными принципами. ARCHITECTURE.md декларирует «The generator must enforce these rules and fail fast on drift», но фактически:
- Неизвестный
kindоперации тихо игнорируется (generate_resources_v2.go — триif op.Kind != "..." { continue }). Если API введёт новый kind — ресурс молча пропадёт из провайдера, без ошибки. - YAML почти не валидируется: проверяется только синтаксис (
yaml.Unmarshal). Отсутствиеop.Kind/op.Action→ zero-value → тихое игнорирование. Нет проверки уникальности param ID, наличия required-полей, валидностиRefSvcId. - При ошибке
format.Sourceгенератор пишет неформатированный (возможно битый) код как fallback вместо остановки. - Исключение сервиса только комментированием в
services_list.txt→ риск рассинхрона (закомментировали в test, забыли в prod).
Предложения.
- Добавить фазу
validateSpec()перед генерацией: required-поля, уникальность ID, известностьkind/action, ссылочная целостностьRefSvcId. Fail fast на неизвестном kind. - Убрать fallback на неформатированный код — при
format.Sourceerror → паника с понятным сообщением. - CI-шаг «генерация без diff»: прогон генератора →
git diff --exit-code(детект дрейфа, как требует ARCHITECTURE.md). - Go для генераторов оправдан (один язык со сгенерированным кодом,
text/template,format.Source); bash-обёртки — лишь оркестрация. Менять не нужно.
Риск. Изменение формата ответа API на шаге 1 не обнаружится до runtime у пользователя. Сейчас единственная защита — ATTEMPTS=3, что не ловит семантический дрейф (поле переименовали, а не пропало).
3. API Flow V6 — отсутствие транзакционности
Состояние. 6 шагов в CreateGenericInstanceUniversalV6: POST /instances → POST /instanceOperations → GET cfsParams → resolve refs → POST каждого param → validate-cfs → run → waitForOperationFinish.
Хорошо. Завершение по dtFinish — надёжный контракт (выстрадан в 02, 05). Нормализация пустых map/json/array есть.
Плохо.
- Orphan при обрыве. Если процесс упал/таймаут после
POST /instances, но доrun— в облаке остаётся инстанс в состоянииnot_created/creating, не попавший в Terraform state. Следующий apply найдёт его черезFindInstanceByDisplayNameи упрётся в ошибку «найден в состоянии Not Created; удалите вручную». То есть пользователь обязан чистить руками. doRequestбез retry (client.go) — нет обработки 429/503/сетевых сбоев. Любой transient-сбой на шаге 4–5 рвёт create.req.Close = true— новое TCP+TLS соединение на каждый запрос (на длинном поллинге дорого).
Предложения.
- Retry с экспоненциальным backoff в
doRequestдля идемпотентных GET и для 429/503/сетевых ошибок (с уважениемRetry-After). - Идемпотентность create: перед
POST /instancesделатьFindInstanceByDisplayName(уже есть вCreateResource) — но также обрабатывать «недосозданный» инстанс: предлагать авто-cleanupnot_created-инстанса приadopt_existing_on_create=true, а не только ручное удаление. - Рассмотреть keep-alive (убрать
req.Close=true) для поллинга — меньше TLS-handshake.
Риск. Авто-cleanup not_created — операция удаления, требует явного флага и подтверждения семантики (нельзя удалять то, что пользователь мог создавать вручную параллельно).
4. CRUD / Adopt — adoptExistingInstanceOnCreate
Состояние. Покрытые ветки в crud.go:
operation_in_progress/pending→ ошибка «дождитесь».!adopt_existing_on_create→ ошибка с подсказкой про import.not_created→ ошибка.running→ ref-валидация → adopt (возврат UUID).suspended→ required-params check → resume → проверка статуса после resume → ref-валидация → adopt.- Иначе (
creating/failed/error) → общая ошибка «статус не подходит для авто-усыновления».
Хорошо. Логика соответствует decision-matrix из ARCHITECTURE.md. Ref-валидация при adopt (баг 22) закрыта. Диагностики подробные.
Плохо.
- Конкурентность не покрыта (см. п.10): между
FindInstanceByDisplayNameиCreateGenericInstanceUniversalV6нет блокировки. creating/failedпадают в общую ветку с менее информативным сообщением, чем требует ARCHITECTURE.md (там дляcreating/pending/failedпредписана отдельная диагностика).- Функция ~100 строк, глубокая вложенность, ref-валидация дублируется в двух ветках (running и после resume) — риск рассинхрона при правках.
state_only/detachdestroy →return nilбез API-вызова: инстанс остаётся в облаке (это by design из 20, но «orphaned» с т.з. биллинга — пользователь должен понимать).
Предложения.
- Вынести классификацию статуса в один
switchс явными ветками для каждого из 16 состояний (см. п.7), убрать дублирование ref-валидации в helper. - Для
creating/failed— отдельные сообщения по контракту ARCHITECTURE.md. - Покрыть adopt-матрицу таблично-управляемыми тестами (сейчас 0 тестов на adopt, п.8).
Риск. Рефакторинг самой сложной функции без тестов опасен — сначала тесты, потом рефакторинг.
5. Генератор gen_v2 — шаблоны вшиты в бинарь
Состояние. 3 inline-шаблона text/template: instanceTemplate (569 строк), subresourceTemplate (443), actionTemplate (174). computeCreateOnly = эвристика (param в create, но не в modify). Identity подресурса: нет modify → все params ForceNew; есть modify → createOnly+delete params ForceNew.
Хорошо. Эвристика createOnly опирается на данные API (не хардкод), format.Source гарантирует валидный Go при успехе, восстановление регистра UUID решает «inconsistent result».
Плохо.
- Шаблоны как строковые константы внутри
.go(1186 строк шаблонов) — тяжело поддерживать, нет подсветки/линтинга шаблонов, любая правка = пересборка генератора. - Новый kind → тихое выпадение ресурса (см. п.2).
data_type: json→IsJson+JsonNormalizeplan-modifier — но 0 тестов на это, а нормализация JSON исторически проблемная (V2→V6 цикл, баг 13).- Immutable определяется только через отсутствие в modify — если API временно не отдаёт modify-параметр (сбой/неполный YAML), параметр ошибочно станет ForceNew → пересоздание ресурса.
Предложения.
- Вынести шаблоны в
embed.FS(//go:embed templates/*.tmpl) — поддерживаемость без потери single-binary. - Fail fast на неизвестном kind + лог числа сгенерированных ресурсов на сервис (детект «пропал ресурс»).
- Снапшот-тесты генератора: эталонный YAML → ожидаемый
.go(golden files). - Защита от «исчезнувшего modify-параметра»: предупреждать, если у сервиса есть create-params, но 0 modify-params (подозрительно).
Риск. Если gen_v2 сломается — ломается весь Universal-провайдер. Сейчас единственная страховка — format.Source, который при ошибке всё равно пишет битый код.
6. Баги из истории — что системное, что осталось
Системные классы (порождали серии багов):
- Фильтрация deleted (23, 22, 21) — API возвращает deleted-инстансы, провайдер их не отсеивал. Закрыт 5.0.50 (
isDeleted=false+isInstanceDeleted()+ ошибка при >1 совпадении). - Определение конца операции (05, 02, 04) — зависание поллинга. Закрыт (критерий
dtFinish). - Динамические param ID (create ID ≠ modify ID, 06) — закрыт runtime-discovery.
- Create-only параметры (16) — закрыт 5.0.38.
Не до конца решённые / ограничения:
- Нормализация map/json/list — потребовала 5 итераций (V2→V6), помечена как частично; новые типы параметров могут снова всплыть.
- Realm-валидация отключена (21) из-за бага бэкенда
/resourceRealms/available— валидации realm до деплоя нет. - FW-rules 500 (04) — platform-side bug, воспроизводится и в Cloud Console; провайдер не может починить.
- Disk shrink (06) — ограничение платформы (только увеличение), провайдер корректно прокидывает ошибку.
Главная системная проблема — именно фильтрация deleted была корнем 3+ багов. Сейчас закрыта, но отсутствие тестов означает, что регрессия не будет поймана автоматически.
Предложения. Regression-тесты на deleted-фильтрацию и multi-match; превратить known-limitations в явные диагностики (например, предупреждать про realm «валидация недоступна»).
7. Матрица состояний — 16 состояний
Состояние. INSTANCE_STATES.md / STATE_TRANSITIONS.md описывают полную матрицу. В коде adopt обрабатывает: not_created, running, suspended, in_progress/pending; остальные → общая ошибка.
Плохо. Хелперы isStatusSuspended/isStatusNonAdoptable/isStatusNotCreated работают через strings.Contains по тексту explainedStatus — хрупко: изменение формулировки статуса в API сломает классификацию молча. Промежуточные (creating, failed, error) сваливаются в одну ветку без индивидуальных подсказок, хотя ARCHITECTURE.md требует разные диагностики.
Предложения. Завести enum состояний и единую функцию classifyStatus(raw) → State, маппинг raw→enum в одном месте, exhaustive switch по всем 16 (с default → явная ошибка «неизвестный статус X»). Тесты на каждый статус.
Риск. Строковое сопоставление — самое уязвимое место к молчаливому дрейфу API.
8. Тесты — достаточно ли
Состояние. 8 unit-тестов всего: crud_test.go (3: isStatusSuspended, isStatusNonAdoptable, delete-default) и client_test.go (5: нормализация значений). Без моков, без integration.
Не покрыто (критично): adopt-логика, ref-валидация, polling/waitForOperationFinish, FindInstanceByDisplayName (deleted+multi-match), генератор целиком, JSON plan-modifier, suspend/resume, required-params compare.
Предложения.
httptest.Serverмок Nubes API → тесты Flow V6, поллинга поdtFinish, обрыва на шаге N, 429/503.- Табличные тесты adopt-матрицы (все 16 состояний ×
adopt_existing_on_createtrue/false). - Golden-тесты генератора (YAML→Go).
- Контракт-тесты на основе HAR (см. п. ниже).
Риск. Все закрытые системные баги (deleted, polling, param-ID) не защищены от регрессии. Любой рефакторинг ядра/генератора — рулетка.
9. Безопасность
Состояние.
- Токены:
*.tokenв .gitignore ✅; private_key.asc в .gitignore ✅; id_ed25519.txt ✅. InsecureSkipVerifydefault = false ✅, включается только явно/NUBES_INSECURE=true. TLS 1.2 min ✅.api_tokenпомеченSensitive: true✅.
Хорошо. Базовая гигиена соблюдена — ключи и токены не коммитятся, TLS-проверка по умолчанию включена.
Плохо / проверить.
- GPG-ключ физически лежит в secrets — да, в .gitignore, но стоит проверить
git log --all -- secrets/private_key.asc, что он не попал в историю ранее. .gitignore не вычищает уже закоммиченное. - public_key.asc, id_ed25519.pub — публичные, ок; но prod.token/
dev.token/test.tokenсуществуют локально — убедиться, что покрыты*.token(да) и не было коммита до добавления правила. - Утечка токена в логи:
formatAPIErrorформатирует тело ответа API в ошибку — если API эхает заголовки/токен в body ошибки, он попадёт в диагностику Terraform.doRequestсам токен не логирует. Стоит маскироватьBearer ...в любых сообщениях. ttyOut()пишет напрямую в tty минуя Terraform — в debug-режимеStageMsgможет содержать чувствительные данные; они идут в терминал в обход TF-логирования.
Предложения. git log аудит секретов; явная маскировка токена в formatAPIError/диагностиках; политика ротации *.token; вынести секреты из репо в внешний secret-store (для CI).
Риск. Если ключ/токен попал в git-историю до .gitignore — он уже скомпрометирован, .gitignore не поможет. Это надо проверить первым делом.
10. Конкурентность — два terraform apply
Состояние. Никаких блокировок. FindInstanceByDisplayName + CreateGenericInstanceUniversalV6 — не атомарны. waitForInstanceIdle ждёт operationIsPending/InProgress, но это не защищает от гонки create.
Плохо.
- Два apply с одинаковым
displayNameодновременно: оба проходятFindInstanceByDisplayName(никого нет) → обаPOST /instances→ два инстанса с одним именем. После этогоFindInstanceByDisplayNameначнёт возвращать ошибку «найдено 2 инстанса» (баг 22 только детектирует это, но не предотвращает). - Между modify из двух окружений — гонка на
instanceOperations; частично гаситсяwaitForInstanceIdle, но окно остаётся.
Предложения.
- Полагаться на Terraform state locking (backend lock) как первичную защиту — это ответственность пользователя, задокументировать.
- На стороне API — проверить, есть ли уникальность
displayNameна бэкенде; если нет, провайдер не может гарантировать атомарность. - Минимально: после
POST /instancesсразу повторныйFindInstanceByDisplayNameи, если найдено >1, откатить свой (требует delete — осторожно).
Риск. Полноценная защита возможна только при поддержке со стороны API (уникальность имени или conditional create). Провайдер в одиночку гонку не закрывает.
Сводный план улучшений (по приоритету)
Steps
P0 — Надёжность runtime (блокеры для прода)
- Retry + backoff в
doRequestдля 429/503/сетевых сбоев (уважатьRetry-After). - Обработка orphan-инстанса
not_created: авто-cleanup при явном флаге вместо «удалите вручную». - Документировать и протестировать поведение при конкурентном apply (state-lock + повторная проверка после create).
P1 — Защита от дрейфа и регрессий (parallel с P0)
4. validateSpec() в генераторе + fail fast на неизвестном kind; убрать fallback на неформатированный код.
5. CI-шаг «генерация без diff» (детект дрейфа per ARCHITECTURE.md).
6. Тесты: httptest мок API (Flow V6, polling, обрыв, 429), табличные тесты adopt-матрицы, golden-тесты генератора, regression на deleted-фильтрацию.
P2 — Снижение тех-долга
7. Единый classifyStatus → enum для 16 состояний, exhaustive switch, убрать strings.Contains-классификацию.
8. Рефакторинг adoptExistingInstanceOnCreate (после тестов): вынести ref-валидацию в helper, отдельные диагностики для creating/failed.
9. Шаблоны генератора в embed.FS.
10. План вывода Legacy: матрица паритета ресурсов, DEPRECATED.md, целевая версия снятия.
P3 — Безопасность и наблюдаемость
11. git log аудит secrets; маскировка Bearer-токена в formatAPIError/диагностиках.
12. Realm-валидация — включить, когда бэкенд починит /resourceRealms/available.
13. HAR-based контракт-тесты (верификация соответствия провайдера реальному API).
Relevant files
- client.go —
doRequest(retry),CreateGenericInstanceUniversalV6(orphan/идемпотентность),FindInstanceByDisplayName(конкурентность). - crud.go —
adoptExistingInstanceOnCreate(рефакторинг + тесты), статус-хелперы. - generate_resources_v2.go — валидация YAML, fail-fast на kind, вынос шаблонов.
- provider.go — транспорт (keep-alive, маскировка токена).
- Тесты: рядом с crud.go и client.go.
Decisions / scope
- Это аналитический отчёт + план; код не менялся (режим Plan, и правила проекта запрещают изменения без «делай»).
- Большинство критических багов из истории уже закрыты (5.0.50). Главный системный риск сейчас — отсутствие тестов (регрессии не ловятся) и дрейф API (генератор/статусы молча ломаются).
Further Considerations
- С чего начать реализацию? A) P0 (надёжность runtime) / B) P1 (тесты+защита от дрейфа — фундамент для безопасного рефакторинга) / C) аудит безопасности секретов (быстро, но критично, если ключ в истории git).
- Вывод Legacy — делать сейчас параллельно или после стабилизации Universal тестами? Рекомендую после (нужен паритет VM/vApp, который без тестов рискован).
- Нужен ли вам этот анализ как отдельный markdown-файл в docs (например
docs/50_history/25_opus_deep_analysis.md), или достаточно ответа в чате?
Какой блок развернуть детальнее или с какого приоритета начинать?
Создано задач: 5
Список задач обновлен
Общее мнение
Архитектурно — сильное, зрелое решение. Идея «YAML как source of truth → генерация Go-кода и доков» правильная и редко встречается в self-made провайдерах. Универсальное ядро без сервис-специфичной логики + декларативные спеки = можно добавить сервис без программиста. Это уровень выше типичного «руками написанного» провайдера.
Что реально хорошо:
- Выстраданный контракт с API (
dtFinishдля конца операции, динамические param-ID, фильтрация deleted) — видно, что прошли через боль и закрыли системные баги. - Чистое разделение pipeline (API→YAML→Go→S3), профили dev/test/prod.
- Базовая безопасность в порядке (токены/ключи в .gitignore, TLS-проверка по умолчанию).
Главные слабости (по убыванию):
- Тесты почти отсутствуют (8 unit-тестов, без моков, без integration). Все закрытые баги не защищены от регрессии — это самый большой риск.
- Генератор молча ломается при дрейфе API (неизвестный kind тихо игнорируется, YAML не валидируется) — при этом сам же ARCHITECTURE.md требует «fail fast on drift». Декларация ≠ реализация.
- Нет транзакционности create и retry — обрыв посередине оставляет orphan, transient-сбой рвёт apply.
- Два провайдера с дублированным ядром — тех-долг, баги фиксятся в двух местах.
Вердикт: концепция и инженерная зрелость ядра — выше среднего, видно опытную руку. Но проект держится на «героике» (ручное тестирование на реальных стендах, комментарии «НЕ МЕНЯТЬ»), а не на автоматических гарантиях. Один слой тестов + fail-fast в генераторе превратили бы его из «работает, потому что мы знаем где не трогать» в по-настоящему надёжный. Сейчас это крепкий прототип на пути к продакшн-grade, а не законченный продукт.
Готово: 3006_1_questions.md
8 дополнительных вопросов
| # | Тема | Что нового (после его анализа) |
|---|---|---|
| 1 | Расхождение версий | Регистр отдаёт 5.0.55, код — 5.0.51. Откуда 53-55? |
| 2 | Полный аудит isDeleted | Все ли GET /instances фильтруют deleted? Особенно findInstanceUidByDisplayNameRefSvc |
| 3 | Gitea 413 | Где nginx-конфиг Gitea в репо? Можно ли увеличить лимит через чарты? |
| 4 | Suspend-сервисы | Полный список кто поддерживает suspend/resume. Есть ли баги (suspend без resume)? |
| 5 | Terraform Operator | Почему перезапустился 2 дня назад? Хватает ли компонентов? Где GPG/S3 конфиг? |
| 6 | Тестовые стенды | Какие реально используются? Legacy или Universal? Дубликаты? |
| 7 | auth.k8s.ngcloud.ru | OIDC-конфиг, обновление токенов, Keycloak HAR |
| 8 | Orphan PVC/STS | Откуда storage-check-* PVC? PostgreSQL 0/0 28 дней — почему? |
Все вопросы — только чтение файлов в репо, никаких правок. Opus может ответить на большинство из них. Вопросы 3 и 8 частично требуют данных из кластера (не из репо) — он это честно отметит.
Внесены изменения.
Список задач обновлен
Исследую все 8 вопросов через чтение файлов репозитория. Запущу параллельный сбор данных.
Searched for regex /instances\?|/instances"|GET.*instances|page=|isDeleted (**/universal_rebuild/internal/core/*.go), 20 results
Searched for regex findInstanceUidByDisplayNameRefSvc|ListRefServiceInstances, 20 results
Searched for files matching **/devops/profiles/**/profile.env, 3 matches
Нашёл ключевое для вопроса 2 — прочитаю функцию findInstanceUidByDisplayNameRefSvc и файлы версий.
Searched for regex VERSION|version|5\.0\. (**/devops/03_build_and_upload_provider.sh), 13 results
Вопросы 1 и 2 почти закрыты. Соберу данные по остальным: инфраструктура, suspend-аудит, стенды, auth, orphan-ресурсы.
Searched for regex auth\.k8s\.ngcloud\.ru|keycloak|shturval|client_max_body_size|gitea|storage-check, 20 results
Searched for regex getInstanceDisplayNameByUidRefSvc|findInstanceDisplayNameByUidRefSvc (**/universal_rebuild/internal/core/*.go), 6 results
Searched for regex storage-check|terraform-operator|registry-server|operator, 20 results
Searched for files matching **/{operator,registry-server,registry-server-build,k8s}/**, 3 matches
Аудит suspend/resume сервисов
Аудит тестовых стендов
Searched for regex kubeconfig|OIDC|oidc|kube-apiserver|kubectl|HOW_TO_SSH|auth\.k8s, 20 results
Searched for files matching **/TEST_STAND/**/*.tf, 11 matches
Searched for files matching **/PROD_STAND/**/*.tf, 10 matches
Searched for files matching **/RABBIT/**/*.tf, 6 matches
Searched for files matching tf_provider · universal_rebuild/resources_yaml/**/*.yaml, no matches
Searched for files matching tf_provider · devops/profiles/prod/generated/resources_yaml/**/*.yaml, no matches
Searched for files matching tf_provider · devops/profiles/test/generated/**/*.yaml, no matches
Searched for files matching tf_provider · universal_rebuild/resources_yaml/embed.go, 1 match
Searched for regex suspend|resume (tf_provider · devops/profiles/prod/generated/resources_yaml/**/*.yaml), 20 results
Searched for regex suspend|resume (tf_provider · devops/profiles/prod/generated/resources_yaml/**/*.yaml), 187 results
Searched for regex - name:|kind:|action: (tf_provider · devops/profiles/prod/generated/resources_yaml/91_redis.yaml), 6 results
Searched for regex - name:|kind:|action: (tf_provider · devops/profiles/prod/generated/resources_yaml/117_nifi.yaml), 9 results
Searched for regex - name:|kind:|action: (tf_provider · devops/profiles/prod/generated/resources_yaml/112_tenant.yaml), 9 results
Searched for regex - name:|action: (tf_provider · devops/profiles/prod/generated/resources_yaml/22_vc_nsxt.yaml), 8 results
Searched for regex - name:|action: (tf_provider · devops/profiles/prod/generated/resources_yaml/29_vc_vdc_group.yaml), 10 results
Searched for regex - name:|action: (tf_provider · devops/profiles/prod/generated/resources_yaml/100_openwhisk.yaml), 4 results
Проверю наличие operator//k8s/, прочитаю отчёт по gitea/registry и README про реестр.
Searched for files matching **/operator/**, no matches
Searched for files matching **/k8s/**, 3 matches
Финальная проверка: наличие registry-server-build/, детали оператора/S3/GPG в кластере, keycloak HAR.
Searched for files matching **/registry-server-build/**, no matches
Searched for files matching **/HAR/*keycloak*, no matches
Searched for regex S3_ENDPOINT|s3.msk-1|terraform-registry|S3_BUCKET|\.s3cfg, 20 results
Все 8 вопросов исследованы. Ниже ответы в формате находка → файлы → вывод → рекомендация.
Вопрос 1: Откуда версии 5.0.53–5.0.55?
Находка. Версия билда берётся не из main.go, а из профиля стенда:
- 03_build_and_upload_provider.sh:
VERSION = $1 → иначе $PROVIDER_VERSION/$RELEASE_VERSION (из profile.env) → иначе grep из main.go (fallback). - profile.env:
PROVIDER_VERSION="5.0.55". - profile.env:
PROVIDER_VERSION="2.1.23"(!). - main.go хранит
5.0.51— это лишь последний fallback, который почти никогда не срабатывает.
Вывод. 5.0.53/54/55 — это ручные билды с тестового стенда: оператор вручную bump-ит PROVIDER_VERSION в profiles/test/profile.env и запускает 03_build...sh. main.go при этом не обновляется (README прямо просит обновлять, но на практике это не делается — отсюда рассинхрон). Важно: prod-профиль застрял на 2.1.23, а test ушёл на 5.0.55 — это две независимые линейки версий. Мой прошлый анализ (3006_0.md) цитировал версии из кода (5.0.51/5.0.52), а регистр отдаёт версию из profile.env (5.0.55) — поэтому они не совпадают.
Рекомендация. Сделать profile.env единственным источником версии и автоматически прописывать её в main.go при сборке (или вычислять main.go-версию из env через ldflags -X). Сейчас 3 источника версии (main.go, test, prod) живут врозь.
Вопрос 2: Полный аудит фильтрации isDeleted
Находка. Все функции, делающие GET к /instances:
| Функция | Файл | isDeleted=false в URL |
Проверка в коде | Статус |
|---|---|---|---|---|
FindInstanceByDisplayName |
client.go | ✅ (search) / ❌ (fallback) | ✅ isInstanceDeleted() |
OK |
ListRefServiceInstances |
refsvc_resolve.go | ✅ | ✅ IsDeleted + только running |
OK |
findInstanceUidByDisplayNameRefSvc |
client.go | ❌ | ✅ if item.IsDeleted {continue} + status=="deleted" |
OK (исправлен!) |
findInstanceDisplayNameByUidRefSvc |
client.go | ❌ | ❌ нет | приемлемо |
getInstanceDisplayNameByUidRefSvc |
client.go | — (GET по UID) | — | n/a |
GetInstanceState/Raw/Details |
client.go / instance_outputs.go | — (GET по UID) | ✅/частично | n/a |
Вывод.
findInstanceUidByDisplayNameRefSvcУЖЕ исправлен — он пропускает deleted в коде (строки client.go) и предпочитает running>suspended. Замечание в 24_ai_analysis_pipeline_architecture_2026_06_30.md («НЕ ИСПРАВЛЕН?») устарело — баг класса #23 здесь закрыт. Единственный недочёт — нетisDeleted=falseв URL (лишний трафик, но не баг корректности).findInstanceDisplayNameByUidRefSvc(1058) — единственная функция БЕЗ фильтра deleted ни в URL, ни в коде. Но она ищет по точномуinstanceUid(уникальному) и возвращает displayName — это обратный маппинг для чтения state, не выбор «того/не того» инстанса. Класс багов #23 здесь не применим. Риск минимальный: вернёт имя deleted-инстанса, если в state остался его UID.
Рекомендация. Косметика: добавить &isDeleted=false в URL findInstanceUidByDisplayNameRefSvc (1057) и findInstanceDisplayNameByUidRefSvc (1060) для экономии трафика. Корректность уже обеспечена. Обновить вывод в файле 24 (он сеет ложную тревогу).
Вопрос 3: Gitea 413 (client_max_body_size)
Находка.
- charts — пустая (list_dir: folder empty).
- Манифесты в репо есть только для dashboard: ingress.yaml — ingress для
terra.k8c.ru/dashboard, аннотацийclient_max_body_size/proxy-body-sizeнет, и это не Gitea. gitea-naeel.giteak8s.services.ngcloud.ruупоминается только какgit_pathв resources.tf и закомментированно в luceUNDnode.tf. Сам Gitea — это управляемый сервис Nubes (см. svcs.json: «Gitea», «Комплексная услуга по созданию gitea», service_id 99/114), развёрнутый в кластереgiteak8s, а не из этого репо.
Вывод. Конфигурация nginx Gitea в этом репозитории отсутствует. client_max_body_size=1MB задаётся на ingress управляемого Gitea в кластере giteak8s.services.ngcloud.ru — внешняя конфигурация, недоступная для правки из этого репо.
Рекомендация. Лимит правится за пределами репо — на ingress Gitea-инстанса: аннотация nginx.ingress.kubernetes.io/proxy-body-size: "0" (или, например, 512m). Это требует доступа к namespace Gitea в кластере giteak8s. Из репо проблему не решить. (Требует данных кластера — отмечаю честно.)
Вопрос 4: Полный список suspend/resume-сервисов
Находка. YAML-спеки в devops/profiles/prod/generated/resources_yaml/ (43 файла, test идентичен), встроены через //go:embed * в embed.go.
26 сервисов с suspend И resume (service_id): 1 dummy, 2 template, 12 s3, 19 vc_org, 20 vc_org_saas, 21 vc_vdc, 23 vc_vm, 26 vapp, 27 vc_vm_v2, 28 vc_vm_v3, 50 nextcloud, 89 flask, 90 postgres, 92 mongodb, 93 rabbitmq, 94 lucee, 95 nodejs, 96 pgadmin, 98 http, 99 gitea, 115 mariadb, 116 kafka, 119 akhq, 120 clickhouse, 149 valo_tenant, 150 k8s_shturval.
17 сервисов без suspend/resume: 13 s3bucket, 22 vc_nsxt, 25 vcexternalip, 29 vc_vdc_group, 32 vmpostgre, 81 superset, 82 harbor, 88 ziti, 91 redis, 97 nodered, 100 openwhisk, 110 dnszone, 111 dnsrecord, 112 tenant, 113 vc_complex, 114 gitea_complex, 117 nifi.
Вывод.
- Асимметрии нет: suspend и resume всегда идут парой (26/26). Сервисов «suspend без resume» — 0.
suspend_on_destroy_defaultидеально совпадает с правиломhasSuspend → true: 26 suspend-сервисов =true, 17 =false. Расхождений нет.- Логика разделения здравая: stateless (DNS, external IP, tenant) — без suspend; stateful (БД, приложения, VM) — с suspend.
Рекомендация. По этому пункту всё чисто. Стоит лишь добавить в генератор validateSpec()-проверку «если есть suspend, обязан быть resume» как защиту на будущее (сейчас инвариант соблюдён случайно — генератор его не enforce-ит).
Вопрос 5: Terraform Operator — состояние и health
Находка — ключевая. Директорий operator/, registry-server-build/, корневого k8s/ НЕТ в этом checkout (file_search: «No files found»), хотя они активно упоминаются в README.md, REPO_CONTENTS.md, build-registry-image.yml (operator/build/Dockerfile.registry) и deploy-dev.sh (k8s/overlays/dev).
Что есть в репо — описание в 00_system_mechanics.md:
- 3 компонента: Operator (watch CRD → spawn Job), Registry API (Discovery protocol over S3), Builder Job (
golang:1.24-alpine: clone→build→upload S3). Это совпадает с тремя deployment'ами, что вы видели. - S3: бакет артефактов
terraform-providers, схема пути{hostname}/{namespace}/{provider}/{version}/{file},REGISTRY_HOSTNAME=terra.k8c.ru. Эндпойнтs3.msk-1.ngcloud.ru(upload_provider_s3.py, REPO_CONTENTS.md). - GPG в кластере — ФЕЙКОВЫЙ: 00_system_mechanics.md — «GPG Signing: Сейчас фейковое (создаётся пустой
.sig)». Реальная подпись private_key.asc используется только в локальном03_build...sh, а не в operator-Job. - Перезапуск оператора — штатная операция: в cheat-sheet прямо есть
kubectl rollout restart deploy/terraform-operator -n terra.
Выводы по вопросам:
- Перезапуск 2 дня назад — скорее всего ручной
rollout restart(документированная команда подхвата измененийmanifests/03-build-script.yaml), а не краш. Подтвердить можно только поkubectl describe pod/--previousлогам в кластере. (Требует кластера.) - Трёх deployment'ов достаточно для registry+CI (operator+registry-server+builder-job). Отдельного docs-server нет и не нужен: доки — статика в S3 (
terraform-registry/docs/...), publish-docs.sh. cloud-dashboard — отдельный UI, не часть registry. - GPG-ключи: в кластере подпись фейковая (tech debt из system_mechanics); реальный ключ только локально в secrets. S3-доступ оператора — через ENV
S3_*/REGISTRY_HOSTNAMEв Deployment (должны быть в Secretterraform-operator). - S3-конфиг бакета в репо — только переменные и схема путей; манифест Secret/Deployment отсутствует (он в недостающей папке
operator//k8s/).
Рекомендация. Критично: внедрить реальную GPG-подпись в operator-Job (смонтировать private_key.asc как K8s Secret) — сейчас артефакты из кластера подписаны пустышкой, Terraform может ругаться authentication signature from unknown issuer. Также — вернуть operator/+k8s/ в этот репо или явно задокументировать, что они в отдельном репозитории (сейчас CI ссылается на отсутствующие пути).
Вопрос 6: Тестовые стенды — что реально используется
Находка. Все .tf используют только Universal (source = "terra.k8c.ru/nubes/nubes"), Legacy (registry.terraform.io/nubes/nubes) не используется нигде.
| Стенд | Version | Endpoint | Активные ресурсы |
|---|---|---|---|
| TEST_STAND/LUCEE | 2.0.6 | test | postgres, lucee |
| TEST_STAND/MARIA_DB | 2.0.8 | test | mariadb, lucee, flask |
| TEST_STAND/POSTGRES | 5.0.52 | test | postgres + user + database |
| TEST_STAND/S3_EVENT_POC | 2.1.23 | — | s3bucket ×2 |
| PROD_STAND/PG1 | 2.1.26 | prod | postgres, lucee, nodejs, s3bucket |
| PROD_STAND/POSTGRES | 2.1.12 | prod | postgres, lucee, nodejs |
| PROD_STAND/RABBIT | 5.0.19 | test ⚠️ | rabbitmq |
| RABBIT/ (корень) | 2.1.10 | prod | rabbitmq, lucee, nodejs, s3bucket |
Выводы:
- Все стенды активны (с реальными ресурсами); часть компонентов закомментирована (VM в PG1, flask в PROD/POSTGRES, http-worker в RABBIT).
- Сильный разброс версий: test — 2.0.6…5.0.52, prod — 2.1.10…2.1.26. Каждый стенд пинит свою версию.
- TEST/POSTGRES не дублирует PROD/POSTGRES: разные версии (5.0.52 vs 2.1.12), имена (
pg4tf033vspg-tst0), TEST модульный (только БД+user+database сvar.realm), PROD связный (БД+lucee+nodejs, хардкод realm). Это разные конфигурации, не дубль. - Аномалия: PROD_STAND/RABBIT/main.tf указывает на test-endpoint (
deck-api-test.ngcloud.ru), хотя лежит в PROD_STAND — вероятно ошибка/недо-миграция.
Рекомендация. Привести версии стендов к двум канонам (одна test, одна prod), исправить endpoint в RABBIT, удалить мёртвые закомментированные .tf или вынести в examples/.
Вопрос 7: auth.k8s.ngcloud.ru / Keycloak / shturval
Находка. Поиск auth.k8s.ngcloud.ru, shturval, OIDC, kubeconfig по репо: совпадения только в самом файле вопросов 3006_1_questions.md. В коде/конфигах — ничего.
- HAR
keycloak.nubes.ru.harсуществует локально, но gitignored (HAR/*.harв .gitignore) — и это Keycloakkeycloak.nubes.ru(для управляемых сервисов Nubes), а неauth.k8s.ngcloud.ru. - Инструкции по доступу — только HOW_TO_SSH.md (SSH, не kubeconfig/OIDC).
kubectl-команды в docs (deploy-dev.sh, ops/*.md) предполагают готовый kubeconfig, способ его получения не описан.
Вывод. Конфигурации OIDC кластера, скриптов обновления токена и инструкций по kubeconfig в репо НЕТ. auth.k8s.ngcloud.ru (realm shturval) — внешняя система аутентификации кластера, никак не отражённая в репозитории. HAR относится к другому Keycloak (сервисный, keycloak.nubes.ru).
Рекомендация. Если доступ к кластеру через auth.k8s.ngcloud.ru нужен регулярно — добавить в secrets (gitignored) инструкцию/скрипт kubelogin/OIDC token refresh, по аналогии с HOW_TO_SSH.md. Сейчас это «племенное знание» вне репо. (Детали OIDC-эндпойнта — за пределами репо.)
Вопрос 8: Orphan PVC storage-check-* и PostgreSQL 0/0
Находка. Поиск storage-check по всему репо: совпадения только в файле вопросов. Ни одного .tf, манифеста или упоминания в docs/HISTORY, создающего storage-check-local/storage-check-vcd. Никаких упоминаний проблемных PostgreSQL-тенантов 509145c3/dc5db45d.
Вывод.
storage-check-*PVC не создаются Terraform-конфигами из этого репо. Имена (storage-check-local/storage-check-vcd= local/vCD storage classes) характерны для тестов доступности StorageClass — это типичные probe-PVC, которые создаёт облачная платформа/CSI или сам terraform-operator-Job при проверке хранилища, а не пользовательский TF. Подтвердить, что их делает именно operator, по репо нельзя (код оператора отсутствует в checkout, см. Q5).- PostgreSQL
0/0 28 днейс висящими PVC/Service — это suspended/недо-удалённые инстансы: StatefulSet масштабирован в 0 (suspend), но PVC и Service остаются (платформа не чистит при suspend). Это согласуется с семантикойsuspend_on_destroy(Q4) и багом 20_destroy_detach_semantics:state_only/suspend оставляет ресурсы в облаке.
Рекомендация. Источник storage-check-* и orphan-StatefulSet'ов нужно искать в кластере: kubectl get pvc storage-check-local -o yaml → смотреть ownerReferences/annotations (кто создатель). Если owner — terraform-operator SA или CSI healthcheck, чистить через него. Из репозитория эти ресурсы не управляются. (Требует доступа к кластеру.)
Сводка
| # | Вопрос | Ответ из репо? | Главный вывод |
|---|---|---|---|
| 1 | Версии 53-55 | ✅ Полностью | Из profiles/*/profile.env, ручной bump; main.go — fallback. prod=2.1.23, test=5.0.55 |
| 2 | Аудит isDeleted | ✅ Полностью | Все критичные функции фильтруют; findInstanceUidByDisplayNameRefSvc уже исправлен (файл 24 врёт) |
| 3 | Gitea 413 | ⚠️ Частично | Конфига nginx Gitea в репо нет — внешняя config кластера giteak8s |
| 4 | Suspend-сервисы | ✅ Полностью | 26 suspend+resume, 0 асимметрий, default совпадает идеально |
| 5 | Operator health | ⚠️ Частично | operator/+k8s/ отсутствуют в checkout; GPG в кластере фейковый; рестарт = штатный redeploy |
| 6 | Стенды | ✅ Полностью | Везде Universal, Legacy не используется; разброс версий; RABBIT prod→test endpoint |
| 7 | auth.k8s | ❌ Нет в репо | Ни OIDC, ни kubeconfig-скриптов; HAR — другой Keycloak (nubes.ru) |
| 8 | Orphan PVC | ❌ Нет в репо | storage-check-* не из TF; orphan PG = suspended-остатки; искать в кластере |
Требуют данных вне репо (честно): Q3 (ingress Gitea в giteak8s), Q5 (логи рестарта оператора, Secret S3/GPG), Q7 (OIDC-эндпойнт auth.k8s), Q8 (ownerReferences PVC в кластере).
Сохранить эти ответы в HISTORY/OPUS/3006_1.md или оставить в чате?
Создано задач: 6