Files
tf_provider/HISTORY/OPUS/3006_0_full_backup.md
T

68 KiB
Raw Blame History

Что сказать 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

Read

Прочитаю ключевые файлы и запущу параллельное исследование по разным областям.

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

Read

Прочитаю основные файлы ядра и генератора, параллельно запущу исследование истории багов.

История багов проекта

Read , lines 1 to 450

Read

Read , lines 1 to 200

Read

Read

Read

Read

Read

Read

Read

Read

Read

Read , lines 1 to 100

Read

Read , lines 1 to 80

Read

Read

Read , lines 1 to 80

Read

Read

Read , lines 1 to 80

Read

Read

Read

Read

Read

Read

Read

Read , lines 450 to 900

Read

Прочитаю crud.go (включая adoptExistingInstanceOnCreate) и начало генератора, а также тесты.

Read

Read , lines 1 to 120

Изучу генератор, тесты, транспорт и состояние secrets в gitignore.

Генератор gen_v2 и тесты

Searched for files matching **/.gitignore, 6 matches

Read , lines 120 to 178

Searched for files matching tf_provider · **/*_test.go, 2 matches

Read , lines 1 to 100

Read , lines 1 to 50

Read

Searched for regex func \(.*\) main\(\)|func.*Generate (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), no results

Read , lines 100 to 300

Read , lines 50 to 57

Read , lines 46 to 46

Searched for text template.Must|var.*=|const.*= (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 8 results

Read

Read , lines 300 to 500

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

Read , lines 500 to 700

Read , lines 800 to 900

Searched for regex panic|error|yaml\.Unmarshal|unknown|default|fallback (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 20 results

Read , lines 850 to 1200

Read

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

Read

Searched for regex ^`$ (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 3 results

Read , lines 1420 to 1430

Read , lines 1865 to 1880

Searched for regex .* (tf_provider · universal_rebuild/internal/resources_core/crud_test.go), 56 results

Read , lines 210 to 350

Searched for regex buildSubresourceForceNewCodes|computeCreateOnly|analyzeJsonPlanMod (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 10 results

Read

Searched for regex func.*IsJson|func.*RefSvc|func.*computeCreateOnly|func.*analyzeJsonPlanMod (tf_provider · universal_rebuild/tools/gen_v2/generate_resources_v2.go), 3 results

Read , lines 2040 to 2150

Read

Read

Изучил архитектуру, ядро, генератор, историю багов и тесты. Сохраню анализ и представлю его.

Created memory file

Анализ 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.Source error → паника с понятным сообщением.
  • 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 /instancesPOST /instanceOperationsGET cfsParams → resolve refs → POST каждого param → validate-cfsrunwaitForOperationFinish.

Хорошо. Завершение по 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) — но также обрабатывать «недосозданный» инстанс: предлагать авто-cleanup not_created-инстанса при adopt_existing_on_create=true, а не только ручное удаление.
  • Рассмотреть keep-alive (убрать req.Close=true) для поллинга — меньше TLS-handshake.

Риск. Авто-cleanup not_created — операция удаления, требует явного флага и подтверждения семантики (нельзя удалять то, что пользователь мог создавать вручную параллельно).


4. CRUD / Adopt — adoptExistingInstanceOnCreate

Состояние. Покрытые ветки в crud.go:

  1. operation_in_progress/pending → ошибка «дождитесь».
  2. !adopt_existing_on_create → ошибка с подсказкой про import.
  3. not_created → ошибка.
  4. running → ref-валидация → adopt (возврат UUID).
  5. suspended → required-params check → resume → проверка статуса после resume → ref-валидация → adopt.
  6. Иначе (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/detach destroy → 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: jsonIsJson+JsonNormalize plan-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. Баги из истории — что системное, что осталось

Системные классы (порождали серии багов):

  1. Фильтрация deleted (23, 22, 21) — API возвращает deleted-инстансы, провайдер их не отсеивал. Закрыт 5.0.50 (isDeleted=false + isInstanceDeleted() + ошибка при >1 совпадении).
  2. Определение конца операции (05, 02, 04) — зависание поллинга. Закрыт (критерий dtFinish).
  3. Динамические param ID (create ID ≠ modify ID, 06) — закрыт runtime-discovery.
  4. 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_create true/false).
  • Golden-тесты генератора (YAML→Go).
  • Контракт-тесты на основе HAR (см. п. ниже).

Риск. Все закрытые системные баги (deleted, polling, param-ID) не защищены от регрессии. Любой рефакторинг ядра/генератора — рулетка.


9. Безопасность

Состояние.

  • Токены: *.token в .gitignore ; private_key.asc в .gitignore ; id_ed25519.txt .
  • InsecureSkipVerify default = 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 (блокеры для прода)

  1. Retry + backoff в doRequest для 429/503/сетевых сбоев (уважать Retry-After).
  2. Обработка orphan-инстанса not_created: авто-cleanup при явном флаге вместо «удалите вручную».
  3. Документировать и протестировать поведение при конкурентном 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

  1. С чего начать реализацию? A) P0 (надёжность runtime) / B) P1 (тесты+защита от дрейфа — фундамент для безопасного рефакторинга) / C) аудит безопасности секретов (быстро, но критично, если ключ в истории git).
  2. Вывод Legacy — делать сейчас параллельно или после стабилизации Universal тестами? Рекомендую после (нужен паритет VM/vApp, который без тестов рискован).
  3. Нужен ли вам этот анализ как отдельный 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-проверка по умолчанию).

Главные слабости (по убыванию):

  1. Тесты почти отсутствуют (8 unit-тестов, без моков, без integration). Все закрытые баги не защищены от регрессии — это самый большой риск.
  2. Генератор молча ломается при дрейфе API (неизвестный kind тихо игнорируется, YAML не валидируется) — при этом сам же ARCHITECTURE.md требует «fail fast on drift». Декларация ≠ реализация.
  3. Нет транзакционности create и retry — обрыв посередине оставляет orphan, transient-сбой рвёт apply.
  4. Два провайдера с дублированным ядром — тех-долг, баги фиксятся в двух местах.

Вердикт: концепция и инженерная зрелость ядра — выше среднего, видно опытную руку. Но проект держится на «героике» (ручное тестирование на реальных стендах, комментарии «НЕ МЕНЯТЬ»), а не на автоматических гарантиях. Один слой тестов + fail-fast в генераторе превратили бы его из «работает, потому что мы знаем где не трогать» в по-настоящему надёжный. Сейчас это крепкий прототип на пути к продакшн-grade, а не законченный продукт.

Read memory

Created

Готово: 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 частично требуют данных из кластера (не из репо) — он это честно отметит.

Внесены изменения.

Список задач обновлен

Read

Исследую все 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

Read

Нашёл ключевое для вопроса 2 — прочитаю функцию findInstanceUidByDisplayNameRefSvc и файлы версий.

Searched for regex VERSION|version|5\.0\. (**/devops/03_build_and_upload_provider.sh), 13 results

Read , lines 900 to 1010

Read

Read

Вопросы 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

Read

Read

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

Read , lines 1057 to 1140

Read

Аудит suspend/resume сервисов

Аудит тестовых стендов

Read

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

Read

Read

Read

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

Read

Read

Read

Read

Read

Read

Read , lines 1 to 50

Read , lines 1 to 50

Read

Read

Read , lines 1 to 100

Read

Read

Read , lines 1 to 100

Read

Read

Read

Read

Read

Read

Read

Searched for regex suspend|resume (tf_provider · devops/profiles/prod/generated/resources_yaml/**/*.yaml), 20 results

Read , lines 1 to 50

Read , lines 1 to 60

Read

Read

Read

Searched for regex suspend|resume (tf_provider · devops/profiles/prod/generated/resources_yaml/**/*.yaml), 187 results

Read , lines 1 to 20

Read , lines 459 to 469

Read , lines 1 to 20

Read , lines 238 to 248

Read , lines 1 to 20

Read , lines 1 to 20

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

Read , lines 40 to 127

Read

Финальная проверка: наличие 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

Read

Все 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.

Выводы по вопросам:

  1. Перезапуск 2 дня назад — скорее всего ручной rollout restart (документированная команда подхвата изменений manifests/03-build-script.yaml), а не краш. Подтвердить можно только по kubectl describe pod/--previous логам в кластере. (Требует кластера.)
  2. Трёх deployment'ов достаточно для registry+CI (operator+registry-server+builder-job). Отдельного docs-server нет и не нужен: доки — статика в S3 (terraform-registry/docs/...), publish-docs.sh. cloud-dashboard — отдельный UI, не часть registry.
  3. GPG-ключи: в кластере подпись фейковая (tech debt из system_mechanics); реальный ключ только локально в secrets. S3-доступ оператора — через ENV S3_*/REGISTRY_HOSTNAME в Deployment (должны быть в Secret terraform-operator).
  4. 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

Выводы:

  1. Все стенды активны (с реальными ресурсами); часть компонентов закомментирована (VM в PG1, flask в PROD/POSTGRES, http-worker в RABBIT).
  2. Сильный разброс версий: test — 2.0.6…5.0.52, prod — 2.1.10…2.1.26. Каждый стенд пинит свою версию.
  3. TEST/POSTGRES не дублирует PROD/POSTGRES: разные версии (5.0.52 vs 2.1.12), имена (pg4tf033 vs pg-tst0), TEST модульный (только БД+user+database с var.realm), PROD связный (БД+lucee+nodejs, хардкод realm). Это разные конфигурации, не дубль.
  4. Аномалия: 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) — и это Keycloak keycloak.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