docs: move root prompts
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Задача: FindInstanceByDisplayName не находит существующий инстанс — анализ
|
||||
|
||||
## Контекст
|
||||
|
||||
Провайдер `terraform-provider-nubes` v5.0.65, test-стенд.
|
||||
Инстанс: `NaeelOrg`, serviceId=19, instanceUid=`3f0850f2-3506-4efd-b84b-7270b5027ab5`, статус Running.
|
||||
|
||||
## Симптом
|
||||
|
||||
`terraform plan` с `adopt_existing_on_create=true`, `resource_name="NaeelOrg"` → `+ create`. Apply → «уже существует».
|
||||
|
||||
## Доказано curl-тестами
|
||||
|
||||
1. Search API: `GET /instances?search=NaeelOrg&serviceId=19&isAuxiliary=false&isDeleted=false` + `UA: Mozilla/5.0` → находит.
|
||||
2. GetInstanceStateRaw: `GET /instances/3f0850f2-...` → возвращает, isDeleted=false, Running.
|
||||
|
||||
## Ключевые файлы
|
||||
|
||||
- `provider/internal/core/client.go` — `FindInstanceByDisplayName()`:568, `doRequest()`:949, `GetInstanceStateRaw()`:753, `isInstanceDeleted()`:741
|
||||
- `provider/internal/resources_core/resource_diagnostics.go` — `PlanExistingResourceDiagnostics()`:14
|
||||
- `provider/internal/resources_core/crud.go` — `CreateResource()`:21, `adoptExistingInstanceOnCreate()`:142
|
||||
- `generated/test/go/19_vc_org_resource.go` — `ModifyPlan()`
|
||||
- `TEST_STAND/kuber/resources.tf` — манифест
|
||||
- `HISTORY/OPUS/3006_0_answers.md`:220 — search ✅ / fallback ❌
|
||||
|
||||
## Задание
|
||||
|
||||
Найти **точно**, почему `FindInstanceByDisplayName("NaeelOrg", 19)` возвращает nil. Если не хватает данных — сказать, какой curl-тест запустить.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Prompt for Opus — subresource duplicate/exist + state_out
|
||||
|
||||
## Файлы для анализа
|
||||
Только эти:
|
||||
- `/home/naeel/tf_provider/provider/internal/resources_core/subresource_guard.go` — ВЕСЬ
|
||||
- `/home/naeel/tf_provider/generated/test/go/90_postgres_database_resource.go` — Create (строки 130-250)
|
||||
- `/home/naeel/tf_provider/artifacts/output_inventory/running_suspended_output_fields_for_docs.json` — PostgreSQL (svc 90), строки 190-575
|
||||
|
||||
## Контекст
|
||||
После повторного apply subresource `pg_user_5` упал:
|
||||
```
|
||||
Операция вернула duplicate/exist, но объект не найден в state_out
|
||||
```
|
||||
|
||||
## Найденная причина
|
||||
- PostgreSQL `state_out` НЕ содержит `databases` и `users` (там только `externalConnect, internalConnect, monitoring`)
|
||||
- `SubresourceListKey("database")` → эвристика `name+"s"` → ищет ключ `"databases"` в state_out → `known=false`
|
||||
- Create-обработка duplicate требует `known && found` для adopt → `known=false` → всегда падает
|
||||
- Adopt subresource'а для PG **физически невозможен** через текущий механизм
|
||||
|
||||
## Конкретные вопросы
|
||||
|
||||
### Вопрос 1 (ПРИОРИТЕТ)
|
||||
`subresource_guard.go` — `FindSubresourceInStateOut`:
|
||||
- При `known=false` (ключа нет в state_out) — как должен вести себя duplicate/exist?
|
||||
- Сейчас: AddError "Нарушена консистентность"
|
||||
- Предлагаемое: доверять API-ошибке `already exists`, считать adopt успешным, вернуть существующий ID
|
||||
- Верно? Или нужен другой подход?
|
||||
|
||||
### Вопрос 2
|
||||
`SubresourceListKey` / `SubresourceIdentityKey` в `subresource_guard.go`:
|
||||
- Эвристики `name+"s"` и special-map на 2 сервиса
|
||||
- Нужен ли явный маппинг ключей из YAML/метаданных API вместо угадывания?
|
||||
- Где в API взять реальные имена ключей state_out для каждого сервиса?
|
||||
|
||||
### Вопрос 3
|
||||
Есть ли в API эндпоинты для прямого запроса списка subresource'ов (list_databases, list_users) — чтобы не полагаться на state_out?
|
||||
|
||||
### Вопрос 4
|
||||
`IsSubresourceAlreadyExistsError`:
|
||||
- Маркеры: `уже существует`, `already exists`, `duplicate`, `conflict`
|
||||
- Достаточно? Нужно ли добавить `409` (HTTP status), `exist`, `already exist`?
|
||||
|
||||
## Формат ответа
|
||||
На каждый вопрос: ДА/НЕТ + код (файл:строка) + конкретное исправление.
|
||||
Не читай другие файлы.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Задача: спроектировать ПРОСТУЮ логику «изменяемости» параметров (CreateOnly vs Modifiable)
|
||||
|
||||
## Проблема
|
||||
|
||||
Генератор terraform-провайдера строит проверку «Нельзя изменить X» (CreateOnly) на основе
|
||||
только instance-modify. Из-за этого возникают противоречивые и сломанные ситуации:
|
||||
|
||||
`generated/dev/resources_yaml/22_vc_nsxt.yaml`:
|
||||
- create (id 10), param `needEnableAVI` (id 340) — помечен `is_modifiable: true`;
|
||||
- instance-modify у `vc_nsxt` НЕТ (modify 111 — это **modifier** `vc_nsxt.network`).
|
||||
|
||||
Генератор:
|
||||
|
||||
```
|
||||
ComputeCreateOnly(createParams, instanceModifyParams):
|
||||
поле считается CreateOnly, если его code нет в instance-modify
|
||||
```
|
||||
|
||||
Следствие: `needEnableAVI` попадает в CreateOnly → генерится жёсткая проверка
|
||||
«Нельзя изменить need_enable_avi», хотя по YAML параметр `is_modifiable: true`.
|
||||
|
||||
Плюс `ConvertParams` вообще **не переносит** `is_modifiable` из ParamSpec в Param —
|
||||
поле теряется, логика его учесть не может.
|
||||
|
||||
## Ключевые файлы (текущая логика)
|
||||
|
||||
- `TOOLS/lib/types.go` — `ParamSpec.IsModifiable` (есть, `is_modifiable` сериализуется в YAML)
|
||||
- `TOOLS/resource-generator/internal/types/types.go` — `Param` (НЕТ поля IsModifiable)
|
||||
- `TOOLS/resource-generator/internal/loader/loader.go` — `ConvertParams` (не переносит IsModifiable)
|
||||
- `TOOLS/resource-generator/internal/params/params.go` — `ComputeCreateOnly` (игнорирует is_modifiable и modifier)
|
||||
- `TOOLS/resource-generator/internal/templates/instance.go` — шаблон, рендерит «Нельзя изменить» из `.CreateOnlyParams`
|
||||
- YAML: `generated/dev/resources_yaml/22_vc_nsxt.yaml` (modify 111 — `kind: modifier`)
|
||||
|
||||
## Существующие понятия операции
|
||||
|
||||
В YAML операции бывают видов:
|
||||
- `kind: instance` (`create` / `modify` / `suspend` / `resume` / `delete`)
|
||||
- `kind: modifier` (отдельный TF-ресурс, `modify` на родительском инстансе, например `vc_nsxt.network`)
|
||||
- `kind: subresource`
|
||||
- `kind: action`
|
||||
|
||||
## Цель
|
||||
|
||||
Спроектировать **единую, простую и понятную** модель «изменяемости» параметра, чтобы:
|
||||
1. параметр считался изменяемым, если он изменяем ХОТЯ БЫ через один канал
|
||||
(instance-modify ИЛИ modifier);
|
||||
2. «Нельзя изменить» генерировалось ТОЛЬКО для реально create-only параметров;
|
||||
3. `is_modifiable` из YAML был единственным источником правды (или явно согласован с каналами modify);
|
||||
4. не было противоречий вида «в YAML is_modifiable:true, а в коде «Нельзя изменить»».
|
||||
|
||||
## Вопросы к Opus
|
||||
|
||||
1. Какая каноническая модель: вычислять изменяемость по `is_modifiable` (флаг из YAML),
|
||||
по наличию кода в любом modify (instance + modifier), или по комбинации?
|
||||
2. Где именно проставлять/вычислять флаг — в yaml-generator (при генерации YAML), или в
|
||||
resource-generator (при генерации Go)?
|
||||
3. Как связать modifier-параметры (`vc_nsxt.network`) с parent-инстансом (`vc_nsxt`),
|
||||
чтобы instance знал, что `needEnableAVI` изменяется через modifier?
|
||||
4. Минимальный, без legacy-наслоений, набор правил.
|
||||
|
||||
Ответ — кратко, с конкретной архитектурой и точками правки (файл + функция).
|
||||
@@ -0,0 +1,73 @@
|
||||
# Спроектировать С НУЛЯ архитектуру/логику «ресурсов-модификаторов» (kind: modifier)
|
||||
|
||||
## Цель
|
||||
|
||||
Перепроектировать модификаторы целиком, чтобы исключить ВСЕ классы багов, не латать по одному.
|
||||
Нужна единая, полная модель поведения — без догадок и костылей. Перечислить ВСЕ кейсы.
|
||||
|
||||
## Что такое модификатор (текущая фактура)
|
||||
|
||||
В YAML (генерируется из API) операции бывают:
|
||||
- `kind: instance` (create/modify/suspend/resume/delete) — обычный CRUD-ресурс;
|
||||
- `kind: modifier` + `modifier: <name>` — отдельный TF-ресурс, который вызывает `modify`
|
||||
на родительском инстансе. Сейчас их два: `vc_org.ip_space`, `vc_nsxt.network`.
|
||||
|
||||
Реальные примеры:
|
||||
- `vc_org` → modifier `ip_space` (modify 207), параметр `vIPConfigure` (array-map-fixed);
|
||||
- `vc_nsxt` → modifier `network` (modify 111), параметры `needEnableAVI`(bool),
|
||||
`virtualServicesCount`(int>0), `qosProfile`(string), `ipSpaceName`(string),
|
||||
`routedNetConfiguration`(map-fixed).
|
||||
|
||||
## Текущий механизм (что есть — факты, не догадки)
|
||||
|
||||
1. Генератор: `TOOLS/resource-generator/internal/templates/modifier.go`
|
||||
- Create и Update **идентичны**: оба шлют `modify` с полным набором полей.
|
||||
- `Delete` — **no-op** (комментарий: «no confirmed inverse payload»).
|
||||
- Схема: `id` computed, `<service>_id` required, поля Optional (или Required если нет default).
|
||||
2. `resources_core.CompactParams` — выбрасывает пустые строки из payload.
|
||||
3. `resources_core.BuildActionID(instanceUID, operation, modifierName)` — константный ID,
|
||||
не привязан к реальной операции (opUid не сохраняется).
|
||||
4. `core.RunInstanceOperationUniversalByCode` — резолвит code→id через
|
||||
`GET /instanceOperations/{opUid}?fields=cfsParams` (fallback на `/default/{opId}`);
|
||||
отправляет переданные params, затем дозаполняет остальные их live-значением
|
||||
(guard: пропускает параметр, если нет ни ParamValue, ни DefaultValue).
|
||||
5. `Read` — через `RefreshResourceState`: читает `state_params` инстанса и
|
||||
перезаписывает input-поля из них.
|
||||
|
||||
## Уже выявленные КЛАССЫ багов (все реально случились)
|
||||
|
||||
- **A. Сброс create-поля при modify.** modify со сброшенными (null) параметрами
|
||||
трактуется бэкендом как reset-to-default: `needEnableAVI` стал false после
|
||||
create=true. Причина: модификатор шлёт только свои поля, `CompactParams` выкидывает
|
||||
пустые, бэкенд видит «отсутствующий» и сбрасывает.
|
||||
- **B. Досылка синтетики.** фикс «досылать всё» слал `"0"` для `integer > 0`
|
||||
(параметр `virtualServicesCount`), API 400 «Invalid format integer > 0».
|
||||
- **C. Ложное «Нельзя изменить».** `ComputeCreateOnly` считал `needEnableAVI`
|
||||
CreateOnly (change-forbidden), хотя в YAML `is_modifiable: true` — потому что
|
||||
генератор не учитывал modifier-канал и терял `IsModifiable`. (Зафиксировано отдельно.)
|
||||
- **D. No-op Delete оставляет эффект на платформе.** destroy модификатора убирает
|
||||
ресурс из state, но выделенные IP / включённый ALB остаются на платформе → drift.
|
||||
- **E. Повторный apply после taint/replace** снова гонит modify — риск повторной
|
||||
аллокации (для `ip_space`), идемпотентность не гарантирована.
|
||||
|
||||
## Вопросы к Опусу (ответить ПОЛНО, по пунктам, с точными местами правки)
|
||||
|
||||
1. **Канон «как сравнить и применить».** Должен ли модификатор перед modify
|
||||
читать текущее состояние и слать ДЕЛЬТУ (только реально изменившиеся поля),
|
||||
или ПТЦ полный payload? Как детектить drift в Read?
|
||||
2. **Досылка незаданных полей (паер-заливы A и B).** Какое каноническое правило:
|
||||
когда досылать live-значение, когда дефолт, когда пропускать? Как не сломать
|
||||
`integer > 0` и прочие constraints?
|
||||
3. **Delete/rollback.** Где искать обратный payload? Как правильно поступить, пока
|
||||
обратный payload НЕ подтверждён API (no-op допустим? явная ошибка? suspend?).
|
||||
4. **Idempotency + ID.** Как сделать ID модификатора отражающим фактическую операцию
|
||||
(opUid?) и как предотвратить двойную аллокацию при replace/повторном apply?
|
||||
5. **Связь с родителем.** Должен ли модификатор использовать `<service>_id` как ссылку
|
||||
на родителя (depends_on / borrow state), и как читать UUID родителя?
|
||||
6. **Create vs Update.** Допустимо ли иметь их идентичными, или нужен строго Update-семантик
|
||||
(нет create, только apply-по-десяти)?
|
||||
7. **Полный перечень кейсов.** Перечислить ВСЕ edge-кейсы, которые надо покрыть:
|
||||
create родителя → modifier; remove modifier; replace; partial params; unknown/absent.
|
||||
|
||||
Ответ — архитектурный документ (краткий, структурированный), с конкретными файлами
|
||||
и функциями. НЕ код-ревью, а ПРОЕКТ.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Уточнения к архитектуре модификаторов — расхождения с фактическим кодом
|
||||
|
||||
Не принимаю предыдущие ответы за истину. Сверка с реальным кодом выявила расхождения.
|
||||
Прошу пересмотреть/уточнить.
|
||||
|
||||
## Факт №1: `OperationSpec` — это алиас `lib.OperationSpec`, не локальный тип
|
||||
|
||||
В `TOOLS/resource-generator/internal/types/types.go`:
|
||||
```go
|
||||
type OperationSpec = lib.OperationSpec
|
||||
type ParamSpec = lib.ParamSpec
|
||||
```
|
||||
Канонический YAML-контракт лежит в `TOOLS/lib/types.go` (пакет `tf-tools/lib`),
|
||||
где уже определены `OperationSpec` (Name/ID/Kind/Action/Modifier/Subresource/Man/Params)
|
||||
и `ParamSpec`.
|
||||
|
||||
Ошибка в прошлом ответе: «добавить в types.go:39» — НЕ указано, что это `lib`.
|
||||
Новые поля `delete_strategy` / `idempotency` / `delete_params` должны быть
|
||||
в `TOOLS/lib/types.go`, иначе yaml-generator (который тоже импортирует lib)
|
||||
и resource-generator разойдутся.
|
||||
|
||||
Вопрос: подтверждаешь, что новый контракт добавляется в `lib/types.go\` (OperationSpec),
|
||||
а `resource-generator` получает его через алиас? Или нужно отдельное
|
||||
resource-generator-специфичное поле (не в lib, а в GenModifier)? Где граница:
|
||||
что в lib, что локально в GenModifier?
|
||||
|
||||
## Факт №2: `normalizeUniversalValueV6` — приватная, живёт в core, принимает core-структуру
|
||||
|
||||
Прошлый ответ: «сравнивать desired vs current после normalizeUniversalValueV6».
|
||||
Но:
|
||||
- `normalizeUniversalValueV6(val string, param universalCfsParam)` — **приватная** (маленькая буква);
|
||||
- принимает `universalCfsParam` (структуру пакета `core`);
|
||||
- сравнение pre-check «desired == current» предполагалось в `resources_core`
|
||||
(там `RunOperationByCodeWithTimeout`) или в шаблоне модификатора.
|
||||
|
||||
Вопрос: ГДЕ правильно делать pre-check и нормализованное сравнение?
|
||||
- вариант A: в `core` (там доступны и cfsParams, и normalize), экспортировать сравнение;
|
||||
- вариант B: в `resources_core` — тогда нужен экспортированный компаратор
|
||||
(`JSONStringsEquivalent` там уже есть), но `universalCfsParam` недоступен;
|
||||
- вариант C: сравнение только через `JSONStringsEquivalent` по JSON-строкам,
|
||||
без `normalizeUniversalValueV6`? (но тогда `" 5"` vs `"5"`, `true` vs `1` дадут ложный diff).
|
||||
|
||||
Как совместить нормализацию типов (bool→"true", int→"5") с местом, где сравнение
|
||||
происходит? Конкретный файл+функция.
|
||||
|
||||
## Дополнительные сомнения (прошу подтвердить/опровергнуть)
|
||||
|
||||
1. **Idempotency pre-check и «полный payload» конфликтуют?** Если desired==current → skip.
|
||||
Но при этом «полный payload» не шлётся вообще (skip). Это согласуется? Или при
|
||||
расхождении одного поля всё равно слать полный payload (и это нормализует всё)?
|
||||
|
||||
2. **`delete_strategy: inverse` + параметр, у которого НЕЛЬЗЯ обнулить** (напр.
|
||||
`virtualServicesCount` integer>0): прошлый ответ — «inverse недопустим, fail-fast».
|
||||
Но что если inverse-стратегия нужна только для ЧАСТИ полей, а не для всех?
|
||||
Т.е. `delete_params` покрывает `needEnableAVI:false`, а `virtualServicesCount`
|
||||
просто остаётся как есть. Допустимо ли «частичный inverse» (обратить только
|
||||
обратимое, остальное не трогать)? Или inverse обязан покрывать все поля?
|
||||
|
||||
3. **`noop_warn` (дефолт) — всегда ли безопасен?** Удаление модификатора из state
|
||||
при оставшемся эффекте на платформе — это drift. Допустимо ли вообще иметь
|
||||
`noop_warn` как ДЕФОЛТ, или для необратимых (ip_space) правильнее дефолт `error`
|
||||
(запретить destroy, пока не разберутся)? Что каноничнее?
|
||||
|
||||
Ответ — кратко, по пунктам.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Баг: modify-модификатор сбрасывает create-поля в дефолт (needEnableAVI true→false)
|
||||
|
||||
## Симптом
|
||||
|
||||
`nubes_vc_nsxt_network` (modifier vc_nsxt.network, modify 111) после create Edge с `needEnableAVI=true`, `virtualServicesCount=3` сбрасывает `needEnableAVI` на платформе обратно в `false`.
|
||||
|
||||
## Подтверждено по API (cfsParams операций)
|
||||
|
||||
Create Edge (op `0c169353`):
|
||||
- 340 needEnableAVI = **true**
|
||||
- 341 virtualServicesCount = **3**
|
||||
|
||||
Modify (SNAT-модификатор, op `d14a149e`):
|
||||
- 368 needEnableAVI = **null**
|
||||
- 369 virtualServicesCount = **null**
|
||||
- 856 qosProfile = **null**
|
||||
- 372 ipSpaceName = internet-ipv4-v1
|
||||
- 1112 routedNetConfiguration = {...}
|
||||
|
||||
Итоговый state.params Edge: `needEnableAVI = false`.
|
||||
|
||||
## Гипотеза
|
||||
|
||||
Модификатор строится через `resources_core.CompactParams`, который выбрасывает пустые `Optional`-поля. Бэкенд для `modify` трактует **пропущенный/null** параметр как «сбросить в дефолт» (false/0), а не «оставить как есть». Итог: modify с частичным payload затирает create-поля.
|
||||
|
||||
## Файлы
|
||||
|
||||
- `provider/internal/core/client.go` — `RunInstanceOperationUniversalByCode` (отправка params), `normalizeUniversalValueV6`
|
||||
- `provider/internal/resources_core/crud.go` — `RunOperationByCodeWithTimeout`, `CompactParams`
|
||||
- генератор: `TOOLS/resource-generator/internal/templates/modifier.go`, `internal/writers/writers.go` (WriteModifierResource)
|
||||
- сгенерированное: `generated/dev/go/22_vc_nsxt_network_modifier.go`
|
||||
- YAML: `generated/dev/resources_yaml/22_vc_nsxt.yaml` (modify 111, поля is_modifiable)
|
||||
|
||||
## Задание
|
||||
|
||||
Определить каноническое поведение:
|
||||
1. Должен ли modify слать **все** параметры операции (полный payload, включая необязательные с их текущими значениями), или допустимо слать только переданные?
|
||||
2. Где правильнее чинить: в генераторе (шаблоне modifier), в `CompactParams`, или в `RunInstanceOperationUniversalByCode` (досылать дефолты/текущие значения незаданных полей)?
|
||||
3. Есть ли риск, что «досылать дефолты» сломает другие модификаторы (напр. vc_org.ip_space)?
|
||||
|
||||
Ответ кратко, тезисно, с указанием конкретной строки/места фикса.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Ревью плана реализации: редизайн модификаторов
|
||||
|
||||
Прошу отревьюить план `PLAN_modifier_redesign.md` (10 шагов). Это проект к реализации,
|
||||
не код. Вызовись: найди дыры, пропущенные кейсы, ошибки в порядке шагов, нестыковки.
|
||||
|
||||
## Контекст решения (уже согласовано, НЕ пересматривать)
|
||||
|
||||
- Модификатор = декларативная проекция полей родителя, единый `reconcile()` (Create≡Update).
|
||||
- Полный payload (не дельта), досылка: задан→значение, иначе live→default→skip.
|
||||
- `delete_strategy`: noop_warn | inverse | error (дефолт noop_warn), `idempotency`: none | check_before_run.
|
||||
- Pre-check `desired==current` в `core` (не в resources_core, не в шаблоне), по живому `state_params`.
|
||||
- `is_modifiable` — единственный сигнал изменяемости (фикс CreateOnly уже есть).
|
||||
|
||||
## Ключевые файлы-факты (сверены с кодом)
|
||||
|
||||
- `TOOLS/lib/types.go` — `OperationSpec`/`ParamSpec` (алиасы в обоих генераторах).
|
||||
- `TOOLS/yaml-generator/main.go` — `serviceSpecificModifiers` (реестр исключений, источник канона).
|
||||
- `TOOLS/resource-generator/internal/loader/loader.go` — ветка `kind==modifier`, `ValidateSpec`.
|
||||
- `TOOLS/resource-generator/internal/templates/modifier.go` — шаблон.
|
||||
- `provider/internal/resources_core/crud.go` — `RunOperationByCodeWithTimeout`.
|
||||
- `provider/internal/resources_core/json_normalize.go` — `JSONStringsEquivalent` (импорт в core = цикл).
|
||||
- `provider/internal/core/operation_run_bycode.go` — клиентский запуск.
|
||||
|
||||
## Вопросы к ревью (ответить кратко, по пунктам)
|
||||
|
||||
1. Порядок шагов 1–10 корректен? Где есть скрытая зависимость, которую я пропустил?
|
||||
2. Шаг 5 (вынос JSON-эквивалентности в `core/jsonutil`) — правильный путь снять цикл
|
||||
импорта, или есть чище (напр. оставить `JSONStringsEquivalent` в resources_core и
|
||||
передавать нормализованные строки в core уже готовыми)?
|
||||
3. Шаг 6 — сигнатура `modifierDesiredEqualsCurrent(desired map[string]string, cfsParams []universalCfsParam) bool`
|
||||
корректна? Хватает ли данных для сравнения всех типов (bool/int/string/map-fixed/array-map-fixed)?
|
||||
4. Шаг 4.4 Delete=inverse — как именно слать modify: `delete_params` + досылка live остальных
|
||||
(полный payload) — это правильно, или есть подводный камень?
|
||||
5. Шаг 8 — расширение реестра `serviceSpecificModifiers` до структуры: верный источник?
|
||||
Или `delete_strategy`/`idempotency` правильнее держать отдельным реестром (не трогая тип map)?
|
||||
6. Пропущен ли какой-то кейс из 16 (13 + taint/replace/partial/unknown)?
|
||||
7. Есть ли риск сломать instance-ресурсы (не модификаторы) любым из шагов 1–8?
|
||||
|
||||
Ответ — тезисно, с указанием конкретного шага и что в нём поправить.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Код-ревью: модификаторы (kind: modifier) в универсальном провайдере
|
||||
|
||||
## Контекст
|
||||
|
||||
terraform-provider-nubes (universal). Операции с `kind: modifier` генерируются как отдельные TF-ресурсы и запускают операцию `modify`, передавая параметры **по коду** (`vIPConfigure`, `needEnableAVI`...), а не по числовому id.
|
||||
|
||||
Актуальные модификаторы:
|
||||
- `nubes_vc_org_ip_space` (vc_org.ip_space, modify 207) — выделение внешних IP (`vIPConfigure`).
|
||||
- `nubes_vc_nsxt_network` (vc_nsxt.network, modify 111) — сеть/SNAT Edge.
|
||||
|
||||
## Ключевые файлы
|
||||
|
||||
- генератор: `TOOLS/resource-generator/internal/templates/modifier.go`, `internal/loader/loader.go` (LoadSpecs → GenModifier), `internal/writers/writers.go` (WriteModifierResource)
|
||||
- рантайм: `provider/internal/resources_core/crud.go` (RunOperationByCodeWithTimeout)
|
||||
- клиент: `provider/internal/core/client.go` (RunInstanceOperationUniversalByCode)
|
||||
- сгенерированное: `generated/dev/go/19_vc_org_ip_space_modifier.go`, `22_vc_nsxt_network_modifier.go`, `registry.go`
|
||||
|
||||
## Известная проблема (уже диагностирована — НЕ ревьюить)
|
||||
|
||||
`GET /instanceOperations/{opUid}?fields=cfsParams` падает 500 (`getResourceRealmConfig` Struct→string) на проблемных инстансах. Fallback на `/instanceOperations/default/{opId}` планируется отдельно.
|
||||
|
||||
## Задание — короткий код-ревью
|
||||
|
||||
1. Корректность жизненного цикла modifier-ресурса: Create/Update/Read/Delete, идемпотентность, refresh из API.
|
||||
2. Реального Delete нет (destroy не откатывает операцию) — это ожидаемо? Подводные камни при повторном apply.
|
||||
3. Риски передачи параметров по коду (code → id) в `RunInstanceOperationUniversalByCode`.
|
||||
4. ТОП-3 самых критичных замечания именно по модификаторам.
|
||||
|
||||
Ответ — кратко, тезисно, без кода-простыней.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Проверка решения бага Dev-генератора
|
||||
|
||||
Ты выполняешь короткий read-only review. Ничего не меняй, не запускай генерацию,
|
||||
не собирай и не публикуй провайдер.
|
||||
|
||||
## Задача
|
||||
|
||||
Проверь, правильно ли диагностирован баг и правильно ли предложено решение:
|
||||
|
||||
1. `create.jsonEnv` может быть nested (`sub_params`), а `modify.jsonEnv` — без
|
||||
`sub_params`.
|
||||
2. `Merge` формирует каноническую схему из параметров операций.
|
||||
3. `AlignParamTypes` выравнивает типы, но не переносит `HasSubParams/SubParams`,
|
||||
если у operation-параметра `HasSubParams` изначально false.
|
||||
4. Шаблон `Update` поэтому генерирует scalar-вызовы для поля, которое в модели
|
||||
является nested-структурой.
|
||||
5. Универсальное решение — нормализовать каждый набор operation params
|
||||
относительно канонической `SchemaParams`, рекурсивно наследуя структурные
|
||||
свойства, без условий по стенду или сервису.
|
||||
|
||||
## Прочитать только эти файлы
|
||||
|
||||
1. `TOOLS/resource-generator/internal/params/params.go`
|
||||
2. `TOOLS/resource-generator/internal/loader/loader.go`
|
||||
3. `TOOLS/resource-generator/internal/helpers/helpers.go` — только функции
|
||||
`IsNested` и связанные с nested-моделями
|
||||
4. `TOOLS/resource-generator/internal/templates/instance.go` — только участки
|
||||
`Update` и проверки `IsNested`
|
||||
5. `TOOLS/resource-generator/internal/types/types.go`
|
||||
6. `generated/dev/resources_yaml/95_nodejs.yaml` — только `jsonEnv` в create и modify
|
||||
7. `generated/dev/go/95_nodejs_resource.go` — только модель `JsonEnv` и `Update`
|
||||
|
||||
Не изучай остальные сервисы, стенды, историю проекта или API вне этих файлов.
|
||||
|
||||
## Формат ответа
|
||||
|
||||
Ответь максимум в 5 коротких пунктах:
|
||||
|
||||
- **Вердикт:** прав / частично прав / неправ.
|
||||
- **Доказательство:** одна конкретная цепочка от YAML до ошибочного Go-кода.
|
||||
- **Решение:** корректно ли выравнивать operation params по канонической схеме.
|
||||
- **Риск:** один главный риск предлагаемого решения.
|
||||
- **Итог:** что именно нужно изменить или что менять не следует.
|
||||
|
||||
Не предлагай реализацию, diff, рефакторинг или дополнительные исследования.
|
||||
@@ -0,0 +1,359 @@
|
||||
# BRIEF: Анализ и рекомендации по документации Nubes Terraform Provider
|
||||
|
||||
**Для:** Claude Sonnet
|
||||
**Дата:** 2026-08-10
|
||||
**Задача:** Изучить ВЕСЬ пайплайн генерации документации, проанализировать фронтенд и UX, выдать подробные рекомендации по улучшению.
|
||||
|
||||
---
|
||||
|
||||
## 1. ЧТО ЭТО ТАКОЕ
|
||||
|
||||
Nubes Terraform Provider — это внутренний провайдер для Terraform, который управляет облачными сервисами (~43 сервиса: PostgreSQL, Redis, Kafka, S3, VMware и т.д.) через API платформы Nubes.
|
||||
|
||||
Документация — автосгенерированный статический сайт на mkdocs-material, который хостится в S3 и доступен юзерам провайдера.
|
||||
|
||||
**Юзер документации** — DevOps-инженер, который пишет Terraform-манифесты. Ему нужно:
|
||||
1. Быстро найти нужный ресурс (сервис)
|
||||
2. Понять какие параметры обязательные, какие опциональные, какие значения допустимы
|
||||
3. Скопировать готовый HCL-пример и подставить свои значения
|
||||
4. Узнать какие outputs можно использовать для связки ресурсов
|
||||
5. Понять что делает каждая операция (create/modify/suspend/resume/...)
|
||||
|
||||
---
|
||||
|
||||
## 2. ПОЛНЫЙ ПАЙПЛАЙН ГЕНЕРАЦИИ
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Шаг 1: YAML-спеки │
|
||||
│ generated/{stand}/resources_yaml/{service}.yml │
|
||||
│ ~43 YAML-файла: параметры, типы, defaults, constraints, MAN │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Шаг 2: docs-generator (Go) │
|
||||
│ TOOLS/docs-generator/main.go │
|
||||
│ │
|
||||
│ Вход: YAML + --version + --api-endpoint + --provider-source │
|
||||
│ Выход: плоские .md файлы в generated/{stand}/docs_llm/ │
|
||||
│ │
|
||||
│ На каждый сервис генерирует: │
|
||||
│ {name}.md — MAN (руководство пользователя) │
|
||||
│ {name}_example.md — HCL-примеры (minimal + full) │
|
||||
│ {name}_params_create.md — таблица create-параметров │
|
||||
│ {name}_params_modify.md — таблица modify-параметров │
|
||||
│ {name}_outputs.md — выходные параметры + cloud snapshot │
|
||||
│ {name}_ops.md — список всех операций │
|
||||
│ {name}_params.md — лендинг со ссылками │
|
||||
│ index.md — общий индекс всех ресурсов │
|
||||
│ _nav_fragment.yml — фрагмент для mkdocs sidebar │
|
||||
│ │
|
||||
│ Ключевые функции в writers/writers.go: │
|
||||
│ ResourceDocs() — вызывает все build* функции │
|
||||
│ buildCreateParamsPage() — таблицы обязательных/опциональных │
|
||||
│ buildExamplePage() — minimal + full HCL примеры │
|
||||
│ buildManualPage() — MAN: HTML → Markdown конвертация │
|
||||
│ buildOutputsPage() — output params + cloud snapshot │
|
||||
│ buildOpsPage() — список операций │
|
||||
│ renderParamTable() — Markdown-таблица параметров │
|
||||
│ renderNestedParams() — вложенные sub_params (map-fixed) │
|
||||
│ IndexMD() — индекс всех ресурсов │
|
||||
│ WriteNavFragment() — sidebar навигация │
|
||||
│ │
|
||||
│ Таблицы: Markdown (| Code | Type | ... |), НЕ HTML. │
|
||||
│ Навигация: inline-строка в header каждой страницы: │
|
||||
│ **Manual** · [Create params] · [Modify params] · ... │
|
||||
│ (активная страница выделена жирным) │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Шаг 3: LLM-обогащение (Python) │
|
||||
│ TOOLS/scripts/05_generate_docs_llm.py │
|
||||
│ │
|
||||
│ LLM: gpt-oss-120b (через api.aillm.ru) │
|
||||
│ На вход: все .md файлы сервиса + YAML + MAN │
|
||||
│ На выход: переписанные .md (те же имена файлов) │
|
||||
│ │
|
||||
│ Что LLM делает: │
|
||||
│ A. Переносит value_list из Constraints в Description │
|
||||
│ "value_list=1,3,5" → "Допустимые значения: **1, 3, 5**" │
|
||||
│ B. Преобразует regex в читаемый текст │
|
||||
│ "regex=^[a-f0-9-]+$" → "Формат: UUID" │
|
||||
│ C. Заполняет пустые Description (из MAN, имени параметра) │
|
||||
│ D. Описывает map-fixed группы (из sub-params имён + MAN) │
|
||||
│ E. Описывает операции без описания (suspend, resume, ...) │
|
||||
│ F. Переводит HTML MAN в читаемый Markdown │
|
||||
│ G. Заменяет TODO в примерах на реальные значения │
|
||||
│ │
|
||||
│ ЖЁСТКИЕ ЗАПРЕТЫ: │
|
||||
│ - НЕ менять имена параметров │
|
||||
│ - НЕ трогать HCL-блоки │
|
||||
│ - НЕ трогать навигационные строки │
|
||||
│ - НЕ менять структуру таблиц │
|
||||
│ - НЕ выдумывать типы/defaults/constraints │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Шаг 4: mkdocs-material (Python) │
|
||||
│ mkdocs.yml + mkdocs build │
|
||||
│ │
|
||||
│ Тема: material, синяя схема │
|
||||
│ Фичи: navigation.path, navigation.footer, navigation.indexes │
|
||||
│ Markdown-расширения: md_in_html, admonition, superfences, ... │
|
||||
│ CSS: extra.css — компактные шрифты, скрыты боковые панели │
|
||||
│ JS: fix-slash.js — авто-добавление / в конец URL │
|
||||
│ Выход: site/ — статический HTML │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Шаг 5: S3 (s3cmd sync) │
|
||||
│ s3://nubes-terraform-registry/docs/{stand}/{provider}/{ver}/ │
|
||||
│ Доступ: https://tf-registry.containerk8s.services.ngcloud.ru/ │
|
||||
│ docs/nubes-test/nubes/5.0.5/ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. ТЕКУЩАЯ СТРУКТУРА ФРОНТЕНДА
|
||||
|
||||
### 3.1 Макет страницы ресурса
|
||||
|
||||
Каждая страница ресурса (например, PostgreSQL) имеет:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ # Resource nubes_postgres · Service ID: 90 · PostgreSQL│
|
||||
│ │
|
||||
│ **Manual** · [Create params] · [Modify params] │
|
||||
│ · [Output params] · [Operations] · [Example] │ ← inline nav
|
||||
│ │
|
||||
│ ## MAN │
|
||||
│ (текст руководства — переведён из HTML в Markdown) │
|
||||
│ ... │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 Что скрыто
|
||||
|
||||
CSS скрывает обе боковые панели mkdocs:
|
||||
```css
|
||||
.md-sidebar--primary { display: none !important; }
|
||||
.md-sidebar--secondary { display: none !important; }
|
||||
```
|
||||
|
||||
Это значит:
|
||||
- **НЕТ sidebar-меню** — юзер не видит оглавление других ресурсов
|
||||
- **НЕТ table of contents** — юзер не видит структуру текущей страницы
|
||||
- Единственная навигация — inline-строка в header
|
||||
|
||||
### 3.3 CSS-особенности
|
||||
|
||||
- `font-size: 0.82rem` — компактный шрифт
|
||||
- `max-width: 61rem` — умеренная ширина контента
|
||||
- MAN-контент: `font-size: 0.62rem` — очень мелкий
|
||||
- НЕТ стилей для Markdown-таблиц (были для HTML-таблиц `.resource-table`, теперь не применяются)
|
||||
|
||||
### 3.4 Индексная страница
|
||||
|
||||
Генерируется `IndexMD()` — простая таблица:
|
||||
```
|
||||
| ID | Ресурс | Описание |
|
||||
|----|--------|----------|
|
||||
| 1 | nubes_dummy | Болванка |
|
||||
| 2 | nubes_template | Темплейт k8s |
|
||||
...
|
||||
```
|
||||
|
||||
Ссылки ведут на `{name}.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. ТЕКУЩИЕ ПРОБЛЕМЫ (ЧТО УЖЕ ИЗВЕСТНО)
|
||||
|
||||
### 4.1 Навигация — СЛАБОЕ МЕСТО №1
|
||||
- Боковые панели скрыты → юзер теряется между страницами
|
||||
- Inline-nav работает, но это неудобно: чтобы перейти к другому ресурсу, нужно вернуться на index
|
||||
- Нет breadcrumbs между ресурсами (хотя mkdocs умеет `navigation.path`)
|
||||
- Нет поиска по параметрам внутри ресурса
|
||||
|
||||
### 4.2 Таблицы параметров — СТАЛО ЛУЧШЕ
|
||||
- Были HTML-таблицы, mkdocs их не рендерил → юзер видел голый текст `map-fixedmap-fixed`
|
||||
- Исправлено: переведены на Markdown-таблицы → рендерятся корректно
|
||||
- НО: стили `.resource-table` больше не применяются (они были для HTML)
|
||||
- Описания параметров заполняются LLM, но не для всех (зависит от качества MAN)
|
||||
|
||||
### 4.3 Примеры (HCL) — работает
|
||||
- Minimal example (только required) + Full example (с defaults) в раскрывашке `<details>`
|
||||
- Юзер может скопировать и использовать
|
||||
|
||||
### 4.4 MAN-секция — перегружена
|
||||
- `font-size: 0.62rem` — очень мелко, трудно читать
|
||||
- HTML→Markdown конвертация в `htmlToMarkdown()` — regex-костыль, может глючить
|
||||
- MAN содержит HTML из админки, его качество зависит от того, кто и как заполнял
|
||||
|
||||
### 4.5 Нет версионирования в интерфейсе
|
||||
- Юзер не видит в UI какую версию провайдера он смотрит
|
||||
- Хотя URL содержит версию (`/5.0.5/`), в самой странице это не отображается
|
||||
|
||||
---
|
||||
|
||||
## 5. ЧТО ХОРОШО (НЕ ЛОМАТЬ)
|
||||
|
||||
1. **Автоматическая генерация из YAML** — параметры всегда актуальны, не отстают от API
|
||||
2. **Inline-навигация между страницами ресурса** — понятно где ты находишься
|
||||
3. **Markdown-таблицы** — рендерятся везде, не зависят от HTML-санитайзеров
|
||||
4. **LLM-обогащение** — описания становятся читаемыми (value_list, regex, пустые ячейки)
|
||||
5. **Cloud snapshot в outputs** — реальные ключи `state_out_flat` и `vault_secrets` из облака
|
||||
6. **Dual example** — minimal + full, юзер выбирает что нужно
|
||||
|
||||
---
|
||||
|
||||
## 6. ВОПРОСЫ К СОННЕТУ
|
||||
|
||||
### Блок A: Навигация и структура сайта
|
||||
|
||||
**A1.** Как организовать навигацию между ресурсами (43 сервиса)?
|
||||
- Варианты: sidebar mkdocs, отдельная страница-индекс с поиском, grouped by category
|
||||
- Плюсы/минусы каждого подхода для DevOps-юзера
|
||||
|
||||
**A2.** Нужно ли вернуть боковую панель mkdocs (sidebar)?
|
||||
- Если да — что в ней должно быть: дерево ресурсов? категории? поиск?
|
||||
- Если нет — как улучшить inline-nav + index page?
|
||||
|
||||
**A3.** Как юзер должен быстро найти нужный ресурс?
|
||||
- Группировка: Базы данных, Очереди, Хранилище, K8s, VMware, Приложения, Сеть
|
||||
- Поиск по имени/описанию?
|
||||
|
||||
### Блок B: Дизайн страницы ресурса
|
||||
|
||||
**B1.** MAN-секция сейчас `font-size: 0.62rem`. Как сделать читаемым?
|
||||
- Оставить компактным но разборчивым?
|
||||
- Сделать раскрывающимся (collapsed by default)?
|
||||
- Вынести ключевую информацию выше?
|
||||
|
||||
**B2.** Как лучше структурировать страницу ресурса?
|
||||
- Текущий порядок: Заголовок → Inline nav → MAN → ...
|
||||
- Может: Заголовок → Краткое описание → Пример (сразу!) → Параметры → MAN (внизу)?
|
||||
|
||||
**B3.** Нужна ли версия провайдера в UI?
|
||||
- Где показывать: в header? в title? в breadcrumb?
|
||||
|
||||
### Блок C: Таблицы параметров
|
||||
|
||||
**C1.** Как улучшить читаемость таблиц параметров?
|
||||
- Сейчас: Code | Type | Description | Constraints
|
||||
- Нужны ли: Required (yes/no), Default, категории параметров?
|
||||
- Группировка связанных параметров (например, все cluster_configuration вместе)?
|
||||
|
||||
**C2.** Как показывать вложенные параметры (map-fixed)?
|
||||
- Сейчас: ### clusterConfiguration → отдельная таблица sub_params
|
||||
- Лучше: раскрывающийся блок? инлайн в той же таблице?
|
||||
|
||||
### Блок D: Примеры и HCL
|
||||
|
||||
**D1.** Как улучшить страницу Example?
|
||||
- Сейчас: Minimal example сверху, Full example в `<details>`
|
||||
- Добавить: описание каждого блока? комментарии в коде?
|
||||
- Показывать реальные значения из облака?
|
||||
|
||||
### Блок E: Общие рекомендации
|
||||
|
||||
**E1.** Какие ещё элементы не хватает?
|
||||
- Changelog между версиями?
|
||||
- Ссылки на связанные ресурсы (PostgreSQL → как связать с Lucee/NodeJS)?
|
||||
- Предупреждения/важные заметки (lifecycle behaviour)?
|
||||
|
||||
**E2.** Приоритизация: что сделать в первую очередь для максимального UX-эффекта?
|
||||
|
||||
---
|
||||
|
||||
## 7. КОНТЕКСТ ДЛЯ ИЗУЧЕНИЯ
|
||||
|
||||
### Файлы для чтения (в порядке важности):
|
||||
|
||||
1. **mkdocs.yml** — конфигурация сайта, тема, фичи, CSS
|
||||
2. **TOOLS/docs-generator/internal/writers/writers.go** — ВСЯ генерация .md (~1000 строк, ключевой файл)
|
||||
3. **TOOLS/docs-generator/main.go** — CLI, флаги, оркестрация
|
||||
4. **TOOLS/scripts/05_generate_docs_llm.py** — LLM-обогащение, SYSTEM_PROMPT
|
||||
5. **docs/LLM_DOCS_GENERATION.md** — архитектурная документация
|
||||
6. **docs/30_registry/assets/extra.css** — CSS-стили
|
||||
7. **docs/30_registry/javascripts/fix-slash.js** — JS (trailing slash fix)
|
||||
|
||||
### Посмотреть живьём (если есть доступ):
|
||||
|
||||
- https://tf-registry.containerk8s.services.ngcloud.ru/docs/nubes-test/nubes/5.0.5/ — индекс ресурсов
|
||||
- https://tf-registry.containerk8s.services.ngcloud.ru/docs/nubes-test/nubes/5.0.5/postgres_params_create/ — пример страницы параметров PostgreSQL
|
||||
- https://tf-registry.containerk8s.services.ngcloud.ru/docs/nubes-test/nubes/5.0.5/postgres_example/ — пример HCL
|
||||
|
||||
---
|
||||
|
||||
## 8. ОГРАНИЧЕНИЯ
|
||||
|
||||
- **YAML = истина.** Параметры, типы, defaults, constraints берутся ТОЛЬКО из YAML. Не выдумывать.
|
||||
- **mkdocs-material** — выбранный фреймворк. Менять можно в рамках его возможностей.
|
||||
- **43 сервиса** — масштаб. Решения должны работать для всех, не только для PostgreSQL.
|
||||
- **Русский язык** — вся документация на русском.
|
||||
- **Целевая аудитория** — DevOps-инженеры, знают Terraform, не знают внутренностей Nubes.
|
||||
|
||||
---
|
||||
|
||||
## 9. ФОРМАТ ОТВЕТА
|
||||
|
||||
Жду от тебя **структурированный анализ**:
|
||||
|
||||
1. **Общая оценка** текущего состояния документации (что хорошо, что плохо)
|
||||
2. **Детальные рекомендации** по каждому блоку вопросов (A1-E2)
|
||||
3. **Приоритизированный план действий** (Quick wins → Среднесрок → Долгосрок)
|
||||
4. **Конкретные предложения** по коду/CSS/структуре где применимо
|
||||
5. **Антипаттерны** — что НЕ стоит делать и почему
|
||||
|
||||
Не спеши. Изучи все файлы. Подумай как ДЕВОПС который впервые видит этот провайдер и пытается написать манифест для PostgreSQL.
|
||||
|
||||
---
|
||||
|
||||
## 10. УТОЧНЯЮЩИЙ ВОПРОС: Как смержить nav
|
||||
|
||||
Ты рекомендуешь вернуть sidebar с категориями. Но в `mkdocs.yml` секция `nav:` — статическая (руководства, глоссарий), а ресурсы генерируются динамически через `WriteNavFragment()` в `_nav_fragment.yml`.
|
||||
|
||||
Как правильно смержить статический `nav:` (руководства) и динамический (43 ресурса по категориям) в mkdocs?
|
||||
|
||||
---
|
||||
|
||||
### Ответ Соннета
|
||||
|
||||
**Нет встроенного `!include` в mkdocs.** mkdocs-material не имеет нативного механизма подключения внешних YAML-фрагментов в `nav:`.
|
||||
|
||||
**Вариант 1 — Рекомендован: расширить существующий Python pre-build шаг**
|
||||
|
||||
В `04_build_and_publish_docs.sh` уже есть Python-блок, который пишет `.mkdocs.tmp.yml`. Добавить туда чтение `_nav_fragment.yml` и инъекцию в `nav:`:
|
||||
|
||||
```python
|
||||
import yaml
|
||||
|
||||
nav_fragment_path = Path(docs_dir) / "_nav_fragment.yml"
|
||||
if nav_fragment_path.exists():
|
||||
fragment = yaml.safe_load(nav_fragment_path.read_text(encoding="utf-8"))
|
||||
resources_nav = fragment.get("resources_nav", [])
|
||||
|
||||
config = yaml.safe_load(text)
|
||||
for item in config.get("nav", []):
|
||||
if isinstance(item, dict) and "Ресурсы" in item:
|
||||
item["Ресурсы"] = resources_nav
|
||||
break
|
||||
text = yaml.dump(config, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
||||
```
|
||||
|
||||
Плюсы: ноль новых зависимостей, PyYAML уже в окружении, merge в одном месте.
|
||||
|
||||
**Вариант 2:** docs-generator пишет полный mkdocs.yml (сложнее поддерживать).
|
||||
|
||||
**Вариант 3:** mkdocs-awesome-pages (не решает проблему merge).
|
||||
|
||||
**Дополнительно:** когда `docs_dir` переключается на `generated/{stand}/docs_llm`, пути `30_registry/guides/*.md` ломаются. Решение: копировать `30_registry` в `docs_dir` перед сборкой.
|
||||
|
||||
**Итого:** Вариант 1 — минимальные изменения, всё уже на месте.
|
||||
Reference in New Issue
Block a user