docs: пометить отменённый заход модификаторов как LEGACY + исправить ложные факты
- баннеры «ЛОЖНЫЙ ПУТЬ — ОТМЕНЕНО» на 4 файла HISTORY/OPUS/2026-09-22_modifier_* и docs/60_strategy/modifier_resources_ideology_and_specification.md - vIPConfigure: replace-семантика, НЕ накопительная (по тесту docs/ORG_IP_MODIFIER_TEST_2026-09-22.md) - обновлены ссылки на перенесённые материалы (docs/... -> NOTES/..., HOW_TO/...)
This commit is contained in:
@@ -7,7 +7,7 @@ Goal: users supply UI names only; provider resolves names to UUID/ID internally
|
||||
|
||||
## Architecture sources
|
||||
- docs/help/architecture-and-methods.md: universal core + CRUD flow, resources generated from YAML.
|
||||
- docs/ARCHITECTURE_NEW.md: core in universal_rebuild/internal/core, generated resources in internal/resources_gen, shared CRUD in crud.go.
|
||||
- NOTES/30_analysis/ARCHITECTURE_NEW.md: core in universal_rebuild/internal/core, generated resources in internal/resources_gen, shared CRUD in crud.go.
|
||||
- docs/00_overview/ai_universal_provider_gen.md: core utilities include FindInstanceByDisplayName; resources generated from YAML.
|
||||
- docs/ai_universal_provider_gen.md: same architecture; core utilities include FindInstanceByDisplayName.
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
> ⛔ **LEGACY / НЕ ИСТОЧНИК ИСТИНЫ (помечено 2026-09-24).**
|
||||
> Идеология отменённого захода (метки `kind: modifier` в YAML, реестр модификаторов в `yaml-generator`,
|
||||
> `delete_strategy`/`inverse` как механизм генератора). Сохранена как история.
|
||||
> Актуальное: `docs/CHAT_RESUME_IAC_2026-09-24.md`.
|
||||
> ⛔ **ЛОЖНЫЙ ПУТЬ — ОТМЕНЕНО (пометка 2026-09-24). ТАК ДЕЛАТЬ НЕЛЬЗЯ.**
|
||||
> Отменённый заход: доменная логика модификаторов вшивалась в универсальный генератор
|
||||
> (`kind: modifier` в YAML + реестр в `yaml-generator`, `delete_strategy`/`inverse`). Ломало агностичность
|
||||
> провайдера и порождало баги. Файл сохранён ТОЛЬКО как история, не источник истины.
|
||||
> Актуально: `NOTES/40_chat_summaries/CHAT_RESUME_IAC_2026-09-24.md`.
|
||||
|
||||
# Архитектурная концепция: Modifier-ресурсы (Идеология, правила и интеграция в Terraform Provider)
|
||||
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
<!-- ⛔ LEGACY: deck-api.ngcloud.ru ЗАКРЫВАЕТСЯ. Актуальный API: lk-api-gateway.ngcloud.ru/api/v1/svc -->
|
||||
# Universal Rebuild — Архитектура и рабочая цепочка (актуально)
|
||||
|
||||
Документ для нового чата: описывает, как устроено «универсальное ядро», как формируются YAML‑спеки сервисов и как генерируются Go‑ресурсы. Учитывает ошибки/уроки из текущего чата.
|
||||
|
||||
---
|
||||
|
||||
## 0) Базовые правила работы
|
||||
- Не менять существующий Go‑код без явного согласования.
|
||||
- Новая логика — только новые функции/файлы (если не было явного разрешения на правку).
|
||||
- Все операции с облаком — read‑only, если отдельно не разрешено создание/удаление.
|
||||
- Для токенов: новый `access_token` сохранять в `/home/naeel/terra/HH-MM-SS.token`.
|
||||
|
||||
---
|
||||
|
||||
## 1) Архитектура (слои)
|
||||
|
||||
### 1.1 Универсальное ядро (core)
|
||||
**Папка:** `universal_rebuild/internal/core`
|
||||
- `client.go` — универсальный клиент API и общий flow операций.
|
||||
- **Критерий завершения операции:** `dtFinish` (см. комментарии в коде). Логику запрещено менять без согласования.
|
||||
|
||||
### 1.2 Провайдер (provider)
|
||||
**Папка:** `universal_rebuild/internal/provider`
|
||||
- Конфигурация провайдера, получение токена, подключение ресурсов через реестр.
|
||||
|
||||
### 1.3 Сгенерированные ресурсы
|
||||
**Папка:** `universal_rebuild/internal/resources_gen`
|
||||
- Автогенерируемые ресурсы Terraform по YAML‑спецификациям.
|
||||
- `registry.go` (генерируется) — регистрирует все ресурсы.
|
||||
- `crud.go` — общие CRUD‑хелперы (создание/modify/delete). Этот файл **не генерируется**, его нужно сохранять.
|
||||
|
||||
### 1.4 YAML‑спеки ресурсов
|
||||
**Папка:** `universal_rebuild/resources_yaml`
|
||||
- YAML для каждого сервиса. Источник истины для генератора ресурсов.
|
||||
- Формат включает `create.params`, `modify.params`, `lifecycle`.
|
||||
|
||||
### 1.5 Генераторы
|
||||
- **YAML‑генератор без instanceUid**
|
||||
- `universal_rebuild/tools/service_params_gen/main.go`
|
||||
- Получает параметры сервиса напрямую через `/index.cfm?endpoint=...`.
|
||||
- **Go‑генератор**
|
||||
- `universal_rebuild/tools/gen/main.go`
|
||||
- Читает YAML и генерирует ресурсы + `registry.go`.
|
||||
|
||||
### 1.6 Отдельные modifier-ресурсы
|
||||
Для parent-level операций `modify`, которые должны выполняться отдельным шагом Terraform-цепочки, используется `kind: modifier`.
|
||||
|
||||
Пример:
|
||||
|
||||
```yaml
|
||||
- name: modify
|
||||
kind: modifier
|
||||
action: modify
|
||||
modifier: ip_space
|
||||
params: []
|
||||
```
|
||||
|
||||
Такой блок не попадает в обычный instance CRUD. Go-генератор создаёт отдельный ресурс с именем `nubes_<service>_<modifier>`. Ресурс принимает ID родительского инстанса и параметры операции, выполняет parent `modify` при Create/Update и читает актуальные значения из `state_params` при Read.
|
||||
|
||||
Для `vcOrg` используется modifier `ip_space` с параметром `vIPConfigure`; для `vcNsxt` используется modifier `network` с параметрами операции сетевой настройки. Nested API-параметры modifier-ресурсов передаются как JSON-строки, поэтому их Terraform-значения должны быть валидным JSON.
|
||||
|
||||
Удаление modifier пока является no-op: подтверждённого обратного payload для отмены выделенных IP или SNAT нет. Операции удаления родительского сервиса не являются rollback и намеренно не вызываются.
|
||||
|
||||
---
|
||||
|
||||
## 2) Как получить параметры сервиса (без instanceUid)
|
||||
|
||||
Источник описан в:
|
||||
- `docs/40_analysis/har/discovery/service_parameters_fetch.md`
|
||||
|
||||
API‑цепочка:
|
||||
1) `GET /api/v1/index.cfm?endpoint=/services/{svcId}`
|
||||
- даёт список операций сервиса (`operations`)
|
||||
2) `GET /api/v1/index.cfm?endpoint=/serviceOperation/{svcOperationId}`
|
||||
- даёт `cfsParams` (id, code, type, required, default, valueList, refSvcId, func)
|
||||
3) (опц.) `GET /api/v1/index.cfm?endpoint=/param-value-list/{svcOperationCfsParamId}`
|
||||
|
||||
**Почему так:** прямых эндпойнтов на список параметров по `service_id` нет. Параметры извлекаются из описаний операций.
|
||||
|
||||
---
|
||||
|
||||
## 3) YAML‑генерация (service_params_gen)
|
||||
|
||||
**Файл:** `universal_rebuild/tools/service_params_gen/main.go`
|
||||
|
||||
Входные переменные:
|
||||
- `NUBES_API_TOKEN` (если не задан — берётся из `test_universal/terraform.tfvars`)
|
||||
- `NUBES_API_ENDPOINT` (по умолчанию `https://deck-api.ngcloud.ru/api/v1/index.cfm`)
|
||||
- `NUBES_SERVICE_ID` (обязателен)
|
||||
- `NUBES_SERVICE_NAME` (опц.)
|
||||
- `NUBES_OUTPUT` (опц.)
|
||||
|
||||
Пример:
|
||||
```bash
|
||||
cd /home/naeel/terra/universal_rebuild
|
||||
NUBES_SERVICE_ID=1 NUBES_SERVICE_NAME=dummy go run ./tools/service_params_gen/main.go
|
||||
```
|
||||
|
||||
Результат:
|
||||
- `/home/naeel/terra/universal_rebuild/resources_yaml/dummy.yaml`
|
||||
|
||||
---
|
||||
|
||||
## 4) Go‑генерация (tools/gen)
|
||||
|
||||
**Файл:** `universal_rebuild/tools/gen/main.go`
|
||||
|
||||
Что делает:
|
||||
- читает все YAML из `resources_yaml/`
|
||||
- генерирует ресурсы в `internal/resources_gen/`
|
||||
- генерирует `registry.go`
|
||||
|
||||
Команда:
|
||||
```bash
|
||||
cd /home/naeel/terra/universal_rebuild
|
||||
go run ./tools/gen/main.go
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5) Build
|
||||
|
||||
```bash
|
||||
cd /home/naeel/terra/universal_rebuild
|
||||
go build -o terraform-provider-nubes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6) Важные нюансы и ошибки (из опыта чата)
|
||||
|
||||
### 6.1 resourceRealm
|
||||
- `resourceRealm` **должен задаваться пользователем в .tf**, если параметр required.
|
||||
- Нельзя автоподставлять `serviceName` как realm: API отклонит (пример с Postgres).
|
||||
|
||||
### 6.2 Warnings о дубликатах
|
||||
- Предупреждение о `RESOURCE WITH SAME NAME EXISTS` должно появляться только на create (когда state.ID отсутствует).
|
||||
- Для managed ресурсов (ID уже есть) предупреждения быть не должно.
|
||||
|
||||
### 6.3 Deleted ресурсы
|
||||
- Если `explainedStatus=deleted` или `isDeleted=true` → ресурс считается отсутствующим, state должен очищаться.
|
||||
|
||||
### 6.4 Имена параметров
|
||||
- Генератор может создавать «разбитые» snake_case для CamelCase (например `resource_c_p_u`).
|
||||
- Это ожидаемо, но если критично — нужен отдельный маппинг (по согласованию).
|
||||
|
||||
---
|
||||
|
||||
## 7) Проверенная цепочка (dummy)
|
||||
|
||||
1) YAML:
|
||||
```bash
|
||||
NUBES_SERVICE_ID=1 NUBES_SERVICE_NAME=dummy go run ./tools/service_params_gen/main.go
|
||||
```
|
||||
2) Go‑код:
|
||||
```bash
|
||||
go run ./tools/gen/main.go
|
||||
```
|
||||
3) Build:
|
||||
```bash
|
||||
go build -o terraform-provider-nubes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8) Что делать дальше
|
||||
- Повторять пункты 3–5 для каждого сервиса:
|
||||
- `NUBES_SERVICE_ID=13 NUBES_SERVICE_NAME=bucket`
|
||||
- `NUBES_SERVICE_ID=90 NUBES_SERVICE_NAME=postgres`
|
||||
- Проверять YAML на корректность required‑параметров, `resourceRealm` и defaults.
|
||||
- Поднимать ресурсы в `.tf` и тестировать: create / modify / suspend / delete.
|
||||
|
||||
---
|
||||
|
||||
## 9) Где искать доп. материалы
|
||||
- Архитектура (старые, но полезные):
|
||||
- `docs/ai_universal_provider_gen.md`
|
||||
- `docs/00_overview/ai_universal_provider_gen.md`
|
||||
- API discovery:
|
||||
- `docs/40_analysis/har/discovery/service_parameters_fetch.md`
|
||||
|
||||
---
|
||||
|
||||
**Короткая формула:**
|
||||
`API (services → serviceOperation)` → `YAML` → `Go resources + registry` → `build` → `tests`.
|
||||
|
||||
---
|
||||
|
||||
## 10) Политика удаления (карантин)
|
||||
- Многие инстансы **нельзя удалять сразу**: из-за возможных данных действует **2‑недельный карантин**.
|
||||
- **Dummy** подпадает под эту политику.
|
||||
- Для `nubes_dummy` при **destroy** или удалении из манифеста выполняется **`suspend`**, а не `delete`.
|
||||
- При `apply`, если инстанс dummy в состоянии **suspend**, выполняется **`resume`**.
|
||||
@@ -1,133 +0,0 @@
|
||||
# Universal Provider Work Summary (Detailed)
|
||||
|
||||
## 1) Repository & Workspace
|
||||
- Workspace root: `/home/naeel/terra`
|
||||
- Active generator project: `/home/naeel/terra/nubes_provider_gen`
|
||||
- Shared Terraform test config (the one you requested): `/home/naeel/terra/test_persistent/main.tf`
|
||||
- Provider dev override for this shared config: `/home/naeel/terra/test_persistent/dev_override.tfrc`
|
||||
|
||||
## 2) What Was Built / Changed (High Level)
|
||||
### Universal generator-based provider (Go)
|
||||
We implemented a generator that reads YAML specs in `resources_yaml` and produces Go resources in `internal/resources_gen`, plus a resource registry.
|
||||
|
||||
### Exporter (auto YAML generation)
|
||||
Created/extended exporter tool to pull CFS params and outputs from API:
|
||||
- Path: `/home/naeel/terra/nubes_provider_gen/tools/export_yaml/main.go`
|
||||
- Exports full `create` and `modify` params (cfsParams)
|
||||
- Added outputs extraction from `state/out`
|
||||
- Handles non-string `defaultValue`
|
||||
- Resolves `svcOperationId` via instance details and operations history
|
||||
- Doesn’t fail if `modify` op is absent (for resources that have no modify)
|
||||
|
||||
### Generator updates
|
||||
- Path: `/home/naeel/terra/nubes_provider_gen/tools/gen/main.go`
|
||||
- Added `outputs` to YAML schema and generated resource schema
|
||||
- Added output fetch during Create/Read/Update (uses `GetInstanceOutputs`)
|
||||
- If outputs fetch fails, outputs are set to `null` (avoid unknowns)
|
||||
- Improved `attrNameFromCode` to avoid `resource_c_p_u` / `allow_no_s_s_l` style
|
||||
- Required params with defaults now become optional+computed in schema
|
||||
|
||||
### Core client updates
|
||||
- Path: `/home/naeel/terra/nubes_provider_gen/internal/core/client.go`
|
||||
- Added `GetInstanceOutputs()` to fetch `state.out` for computed outputs
|
||||
|
||||
### Documentation
|
||||
- Added MAYDO section in `/home/naeel/terra/docs/ai_universal_provider_gen.md` for future enhancements
|
||||
|
||||
## 3) Key Files (Current)
|
||||
- Generator: `/home/naeel/terra/nubes_provider_gen/tools/gen/main.go`
|
||||
- Exporter: `/home/naeel/terra/nubes_provider_gen/tools/export_yaml/main.go`
|
||||
- Core client: `/home/naeel/terra/nubes_provider_gen/internal/core/client.go`
|
||||
- YAML specs: `/home/naeel/terra/nubes_provider_gen/resources_yaml/*.yaml`
|
||||
- Generated resources: `/home/naeel/terra/nubes_provider_gen/internal/resources_gen/*`
|
||||
- Shared Terraform test config: `/home/naeel/terra/test_persistent/main.tf`
|
||||
- Dev override: `/home/naeel/terra/test_persistent/dev_override.tfrc`
|
||||
|
||||
## 4) Exported YAML Specs (Current)
|
||||
### Dummy
|
||||
- File: `/home/naeel/terra/nubes_provider_gen/resources_yaml/dummy.yaml`
|
||||
- `create` and `modify` params exported
|
||||
- `outputs` are **absent** (API returns `state.out = null` for dummy)
|
||||
|
||||
### Bucket (S3 bucket)
|
||||
- File: `/home/naeel/terra/nubes_provider_gen/resources_yaml/bucket.yaml`
|
||||
- `modify` is empty (API does not expose modify op for that instance)
|
||||
- `outputs` are **absent** (API returns `state.out = null`)
|
||||
|
||||
### Postgres
|
||||
- File: `/home/naeel/terra/nubes_provider_gen/resources_yaml/postgres.yaml`
|
||||
- CFS params exported, outputs include:
|
||||
- `externalConnect`, `internalConnect`, `monitoring` (all as string)
|
||||
- `resourceDisk` type conflict fixed (kept as string for both create/modify)
|
||||
|
||||
## 5) Shared Terraform Test Config (as requested)
|
||||
File: `/home/naeel/terra/test_persistent/main.tf`
|
||||
Contains **dummy + bucket + postgres**. Current content:
|
||||
- `nubes_dummy.test_bolt`:
|
||||
- display_name = `Terraform-Test-Bolvanka-20260201-01`
|
||||
- resource_realm = `dummy`
|
||||
- delete_mode = `suspend`
|
||||
- resume_if_exists = `true`
|
||||
- `nubes_bucket.test_bucket`:
|
||||
- display_name = `tf-bucket-gen-20260201-02`
|
||||
- delete_mode = `delete`
|
||||
- resume_if_exists = `false`
|
||||
- `nubes_postgres.test_pg`:
|
||||
- display_name = `tf-postgres-20260201-03`
|
||||
- delete_mode = `state_only`
|
||||
- resume_if_exists = `false`
|
||||
- other required params from exported YAML
|
||||
|
||||
**Dev override** now points to generator build:
|
||||
`/home/naeel/terra/test_persistent/dev_override.tfrc` → `"terrareg.kube5s.ru/nubes/nubes" = "/home/naeel/terra/nubes_provider_gen"`
|
||||
|
||||
## 6) Tokens & Auth Handling
|
||||
Rule: on new access_token, save to `/home/naeel/terra/HH-MM-SS.token` (expiry time).
|
||||
Saved tokens so far:
|
||||
- `/home/naeel/terra/11-53-16.token` (old)
|
||||
- `/home/naeel/terra/15-20-50.token` (old)
|
||||
- `/home/naeel/terra/15-23-19.token` (old)
|
||||
- `/home/naeel/terra/15-25-59.token` (latest)
|
||||
|
||||
Important:
|
||||
- `/home/naeel/terra/test_persistent/terraform.tfvars` was updated to use the latest token from `15-25-59.token`.
|
||||
- `/user` endpoint returns 200 with the latest token (so token is valid).
|
||||
|
||||
## 7) Test Runs / Current State
|
||||
### Dummy & Bucket
|
||||
- Dummy created successfully (id: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`).
|
||||
- Bucket created successfully (id: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`).
|
||||
|
||||
### Postgres
|
||||
- Postgres resource is **not in Terraform state** (it was removed with `terraform state rm`).
|
||||
- New create was attempted with display_name `tf-postgres-20260201-03` but apply was **cancelled** by user.
|
||||
- Last error before name change: duplicate display name (400). Now fixed with new display_name.
|
||||
|
||||
### TLS timeouts
|
||||
Occasional TLS handshake timeouts when creating resources. Retrying apply usually works.
|
||||
|
||||
## 8) Known Issues / Fixes Applied
|
||||
- **Outputs**: provider now sets outputs to `null` if `state/out` is missing/unavailable, preventing “unknown after apply” errors.
|
||||
- **Attribute names**: fixed to avoid `resource_c_p_u` or `allow_no_s_s_l`.
|
||||
- **Required + default**: required params with defaults are optional+computed in schema.
|
||||
- **Bucket delete**: fails if instance not fully created. Fixed by waiting and retrying destroy.
|
||||
|
||||
## 9) What To Do Next (Continuation Steps)
|
||||
1) Rebuild provider (if not already):
|
||||
- `cd /home/naeel/terra/nubes_provider_gen`
|
||||
- `go run ./tools/gen`
|
||||
- `go build -o terraform-provider-nubes`
|
||||
2) Ensure token in `/home/naeel/terra/test_persistent/terraform.tfvars` is valid.
|
||||
3) Apply from shared config:
|
||||
- `cd /home/naeel/terra/test_persistent`
|
||||
- `TF_CLI_CONFIG_FILE=./dev_override.tfrc terraform apply -auto-approve`
|
||||
4) If TLS timeouts occur, re-run `terraform apply`.
|
||||
5) If Postgres creation fails due to name conflict, change `display_name` to a new unique value.
|
||||
|
||||
## 10) Notes on Outputs
|
||||
- For dummy & bucket: `state.out` is null, so YAML does not list outputs.
|
||||
- For postgres: outputs are present in YAML; provider sets them to `null` when not returned yet.
|
||||
|
||||
---
|
||||
|
||||
If you want a shorter summary or specific error fixes, tell me what to focus on.
|
||||
@@ -1,89 +0,0 @@
|
||||
# Резюме сессии: Диагностика K8s (Штурвал), VDC и архитектура провайдера (2026-09-20)
|
||||
|
||||
## 1. Инфраструктурный контекст
|
||||
- **ВМ 213 (jump-host / dev)**:
|
||||
- SSH: `ssh vps` (`5.172.178.213`, user `naeel`, key `~/.ssh/naeel_vm_id_ed25519`).
|
||||
- kubectl контекст по умолчанию: `tazetdinovn@gmail.com@naeel-test-3`.
|
||||
- **Кластер `naeel-test-3`**:
|
||||
- API: `https://185.247.187.146:6443`.
|
||||
- Узлы: 6 нод (3 control-plane, 3 workers), версия v1.34.1 / платформа Штурвал 2.12.1.
|
||||
- **Кластер `devclustername`**:
|
||||
- API: `https://185.247.187.226:6443`.
|
||||
- Статус: порт 6443 на Edge доступен, но TLS сбрасывается (`connection reset by peer`) — виртуальные машины кластера находятся в `suspend` / выключены.
|
||||
|
||||
---
|
||||
|
||||
## 2. Что было сделано и починено в кластере `naeel-test-3`
|
||||
|
||||
### Проблема:
|
||||
В веб-интерфейсе Штурвала кластер висел в статусе **«Работает с ошибками» (⚠️ 2/4 по NodeConfigItems)**.
|
||||
|
||||
### Причина:
|
||||
1. На активном воркере `naeel-test-3-workers-5p8w7-vxzch` висело ожидание применения конфигураций (`RebootPending` с типом `drainonly`).
|
||||
2. Очередь на применение/drain была заблокирована (`SlotsOccupied`), так как в CRD `nodeconfigs.node.shturval.tech` осталась старая удалённая нода `naeel-test-3-workers-5p8w7-nqkr2` с зависшим флагом `rebootallowed: true`.
|
||||
|
||||
### Решение:
|
||||
1. Вычищены все фантомные объекты `nodeconfigs`, которых уже нет среди реальных K8s-нод (сняты блокирующие finalizers).
|
||||
2. Слот освободился: контроллер `shturval-node-config` корректно выполнил `drainonly` на воркере `vxzch` (под `pythonk8s` переехал на соседний воркер `wwqj2`).
|
||||
3. Применились оставшиеся `NodeConfigItem` (`generic-init-config`, `all-to-nubes-registry`).
|
||||
4. **Текущий статус**:
|
||||
- Все 6 нод в статусе `Ready`.
|
||||
- Все 6 `nodeconfigs` в статусе `READY: true`.
|
||||
- Все 4 `nodeconfigitems` в статусе `ready: true` (**4/4, статус кластера зелёный**).
|
||||
- Под `pythonk8s` (`drhider.pythonk8s.dev.nubes.ru`, ns `20a75175-a58c-49cb-b8fa-e86367b1a8dc`) поднят и работает (1/1 Running).
|
||||
- Потребление ресурсов: среднее ~22m CPU и ~103 MiB RAM на под; суммарно на весь 6-узловой кластер ~1.6 CPU и ~7.5 GiB RAM (минимальный фоновый простой).
|
||||
|
||||
---
|
||||
|
||||
## 3. Блокировка операций в личном кабинете облака (Suspend / Modify)
|
||||
|
||||
### Симптом:
|
||||
При попытке выполнить операцию `suspend` кластера в UI облака возникает ошибка:
|
||||
`Concurrent operations are not supported (job status: cannot obtain job result) There is a started operation on this instance`
|
||||
|
||||
### Диагностика через API Gateway (`lk-api-gateway-dev.ngcloud.ru`):
|
||||
- Инстанс кластера: `e78a40b9-7de1-4c88-af7a-ad7f7efaa7c2`.
|
||||
- Зависшая операция: `modify` (`opUid: 2fb7dd59-732a-4bb9-add2-6bddd7329f72`).
|
||||
- Причина зависания: оркестратор CFS поймал таймаут (`Timeout has been exceeded`), выполнил откат, записал лог ошибки, но **не проставил `dtFinish` и `isSuccessful` в БД**.
|
||||
- Статус операции в API остался незакрытым, из-за чего API Gateway блокирует любые новые операции над инстансом.
|
||||
- **Внимание**: это баг бэкенда платформы облака (CFS/оркестратора). Средствами `kubectl` внутри кластера это не лечится — требуется сброс статуса операции на стороне API платформы или через поддержку.
|
||||
- Ошибка в истории операций: `Не удалось включить кластер. Ошибка: Error not found from 'parseTerraformError'` — вызвана тем, что общий обработчик бэкенда облака по ошибке прогнал текст через парсер ошибок Terraform. Внутри K8s Terraform не используется.
|
||||
|
||||
---
|
||||
|
||||
## 4. Архитектурные правила по VDC и Terraform-провайдеру
|
||||
|
||||
### Почему возникает ошибка дубликата VDC:
|
||||
`VDC с именем 'WZ03709-saas-snb1-i24-vcpu50' уже существует внутри организации 'WZ03709-saas'`
|
||||
- Имя VDC генерируется бэкендом детерминированно: `{org}-{type}-{segment}-{cpu}-vcpu{reservation}`.
|
||||
- В одном сегменте организации существует максимум два класса VDC:
|
||||
- `vcpu50` (50% гарантия vCPU — для dev/test, оверселлинг, дешевле);
|
||||
- `vcpu80` (80% гарантия vCPU — для prod/баз данных, жесткая фиксация ресурсов).
|
||||
- Создавать третий VDC с тем же процентом SLA в рамках одной организации невозможно (конфликт уникальности имен в vCloud) и бессмысленно: изоляция проектов внутри VDC делается через **vApp**, **сети (Org Networks)** и правила фаервола. При нехватке ресурсов VDC масштабируется через `modify`.
|
||||
- **Канон Terraform**: при работе с уже созданными VDC в манифесте указывать:
|
||||
```hcl
|
||||
adopt_existing_on_create = true
|
||||
```
|
||||
(согласно `docs/60_strategy/provider_philosophy.md`).
|
||||
|
||||
### Зачем нужны дочерние ресурсы-действия (Action / Sub-resources):
|
||||
Цепочка развертывания vCloud/NSX-T имеет циклические зависимости:
|
||||
1. `vcOrg -> create`
|
||||
2. `vcVdc -> create`
|
||||
3. `vcNsxt -> create` (Edge Gateway)
|
||||
4. `vcOrg -> modify` (выделение белых IP в организацию, так как появился Edge)
|
||||
5. `vcNsxt -> modify` (настройка SNAT под конкретный выделенный IP)
|
||||
6. `k8sShturval -> create` (нодам нужен интернет через SNAT)
|
||||
|
||||
Чтобы пользователю не приходилось делать несколько ручных прогонов `terraform apply` с ручным редактированием `.tf` файлов между шагами, в провайдере создаются **дочерние ресурсы-действия**.
|
||||
- Под капотом провайдера эти дочерние ресурсы транслируются в точечные вызовы **`modify`** над родительскими объектами.
|
||||
- Это стандартная практика в Terraform (аналог `aws_security_group_rule` для `aws_security_group`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Что делать дальше в новом чате
|
||||
1. Если продолжаем работу с провайдером Nubes:
|
||||
- Проверить статус зависшей операции `2fb7dd59-732a-4bb9-add2-6bddd7329f72` в API Gateway.
|
||||
- Разрабатывать/тестировать логику дочерних ресурсов (action/sub-resources) и `modify`-пайплайна по стандарту `docs/60_strategy/provider_philosophy.md`.
|
||||
2. Если требуется проверить второй кластер (`devclustername` / `185.247.187.226`):
|
||||
- Убедиться, что кластер выведен из suspend в веб-интерфейсе (чтобы поднялся API server на порту 6443).
|
||||
@@ -1,155 +0,0 @@
|
||||
# Резюме сессии: VDC create-flow, диалог с Opus и правка fallback (2026-09-21)
|
||||
|
||||
## 1. Контекст задачи
|
||||
- Цель: довести до рабочего состояния создание `nubes_vc_vdc` в `FullPipe`.
|
||||
- Симптом: при создании VDC провайдер падал на `GET /instanceOperations/{opUid}?fields=cfsParams` с HTTP 500.
|
||||
- В ходе разбора было подтверждено, что обычные сервисы через тот же провайдер работали, а VDC попадал в отдельную проблемную ветку backend-обработки.
|
||||
|
||||
---
|
||||
|
||||
## 2. Диагностика проблемы
|
||||
|
||||
### 2.1. Что ломалось
|
||||
- В `provider/internal/core/client.go` create-flow делал `GET /instanceOperations/{opUid}?fields=cfsParams` сразу после создания операции.
|
||||
- Для VDC этот запрос приводил к ошибке backend’а:
|
||||
- `Invalid call of the function [getResourceRealmConfig]`
|
||||
- `Cannot cast Object type [Struct] to a value of type [string]`
|
||||
- источник ошибки: `/app/api/v1/resources/instance_operation_cfs_param.cfc`
|
||||
- Причина по отчету: для `vc_vdc` вычислялся динамический `descr` у `storageConfig.name`, и backend падал на `resourceRealm`, который в DEV хранится как `Struct`, а не `string`.
|
||||
|
||||
### 2.2. Почему обычные сервисы не ломались
|
||||
- На обычных сервисах этот `GET` либо не попадал в проблемный backend-код, либо не требовал вычисления `resourceRealm`.
|
||||
- Для VDC в YAML есть специфическая зависимость:
|
||||
- `generated/dev/resources_yaml/21_vc_vdc.yaml`
|
||||
- `storageConfig.name` содержит вычисляемый `descr` с `getResourceRealmConfig(...resourceRealm...)`.
|
||||
- Для обычных сервисов, например `vapp` и `postgres`, такого вычисляемого `resourceRealm`-контекста нет.
|
||||
|
||||
### 2.3. Почему `hasUnresolvedParams` мешал
|
||||
- Эвристика проверяла **все** строковые параметры, а не только параметры с `ref_svc_id`.
|
||||
- Для VDC это ломало fallback на обычных литералах вроде:
|
||||
- `providerVdc = fast-2.8`
|
||||
- `networkProvider = default`
|
||||
- Эти значения не UUID и не JSON, но и резолвить их не нужно.
|
||||
- В результате при падении GET провайдер вместо продолжения переходил в ошибку.
|
||||
|
||||
---
|
||||
|
||||
## 3. Диалог с Opus
|
||||
|
||||
### 3.1. Что просили у Opus
|
||||
- Проверить только:
|
||||
- `provider/internal/core/client.go`
|
||||
- `docs/DEBUG_REPORT_VC_VDC_500.md`
|
||||
- `generated/dev/resources_yaml/21_vc_vdc.yaml`
|
||||
- `generated/dev/resources_yaml/26_vapp.yaml`
|
||||
- `generated/dev/resources_yaml/90_postgres.yaml`
|
||||
- Вопросы к Opus были узкими:
|
||||
1. почему обычные сервисы работали, а VDC начал падать на `GET ?fields=cfsParams`
|
||||
2. есть ли в VDC специфическая структура или зависимость, которой нет у обычных сервисов
|
||||
3. является ли `hasUnresolvedParams` неверной эвристикой именно в этом месте
|
||||
4. что именно надо исправить
|
||||
|
||||
### 3.2. Что ответил Opus по сути
|
||||
- Root cause — не данные Terraform и не сами строки `fast-2.8` / `default`, а backend-ошибка на `GET /instanceOperations/{opUid}?fields=cfsParams` именно для VDC.
|
||||
- VDC отличается от обычных сервисов тем, что в его YAML есть динамический `descr` для `storageConfig.name`, который тянет `resourceRealm`.
|
||||
- `hasUnresolvedParams` была признана лишней и хрупкой эвристикой: она может ломать fallback на обычных строках.
|
||||
- Итоговое решение Opus: при ошибке GET идти дальше по браузерному flow, без условий по всем строковым параметрам.
|
||||
|
||||
### 3.3. Дополнительные уточнения в диалоге
|
||||
- Был отдельный спор по формулировке про Lucee / ColdFusion backend.
|
||||
- В итоге было зафиксировано, что этот термин — не отдельная гипотеза, а просто обозначение backend-слоя, который уже фигурировал в отчетах и traceback’ах.
|
||||
- Opus также подтвердил, что для VDC этот GET нужен только как вспомогательный шаг для `resolveRefSvcParamValues`, а не как обязательный бизнес-этап.
|
||||
|
||||
---
|
||||
|
||||
## 4. Что изменили в коде
|
||||
|
||||
### 4.1. `provider/internal/core/client.go`
|
||||
- В `CreateGenericInstanceUniversalV6` удалён gate по `hasUnresolvedParams`.
|
||||
- Теперь логика такая:
|
||||
- если `GET /instanceOperations/{opUid}?fields=cfsParams` успешен — парсим и резолвим `ref_svc_id`
|
||||
- если GET падает — просто продолжаем POST’ить параметры, а потом идём в `validate-cfs` и `run`
|
||||
- Функция `hasUnresolvedParams` удалена полностью.
|
||||
- После удаления была убрана осиротевшая документационная строка, оставшаяся над `isHexDigit`.
|
||||
|
||||
### 4.2. `provider/internal/core/client_test.go`
|
||||
- Добавлен тест:
|
||||
- `TestCreateGenericInstanceUniversalV6_ContinuesWhenOpDetailsGETFails`
|
||||
- Тест моделирует:
|
||||
- `POST /instances`
|
||||
- `POST /instanceOperations`
|
||||
- `GET /instanceOperations/{opUid}?fields=cfsParams` → 500
|
||||
- `POST /instanceOperationCfsParams`
|
||||
- `GET /instanceOperations/{opUid}/validate-cfs`
|
||||
- `POST /instanceOperations/{opUid}/run`
|
||||
- финальный `GET /instances/{uid}`
|
||||
- Проверка теста:
|
||||
- create-flow завершился успешно
|
||||
- все 7 параметров были отправлены с ожидаемыми значениями
|
||||
- polling по операции был ровно один раз
|
||||
|
||||
---
|
||||
|
||||
## 5. Проверка после правки
|
||||
- `cd /home/naeel/TF/tf_provider/provider && go test ./internal/core` — успешно.
|
||||
- После ревью был пойман и исправлен только косметический хвост:
|
||||
- старый комментарий над `isHexDigit`, оставшийся после удаления `hasUnresolvedParams`.
|
||||
- После этого пакет `internal/core` снова прошёл тесты.
|
||||
|
||||
---
|
||||
|
||||
## 6. Вывод по итогам сессии
|
||||
- Проблема была не в обычных сервисах как таковых, а в специфике VDC-данных и backend-пути, который срабатывал на `GET ?fields=cfsParams`.
|
||||
- `hasUnresolvedParams` была неверной эвристикой именно в create-flow VDC и ломала рабочий fallback.
|
||||
- Правильное поведение: если GET падает, не гадать по строковым параметрам, а продолжать browser-like flow через POST параметров, validate и run.
|
||||
|
||||
---
|
||||
|
||||
## 7. Что дальше
|
||||
- Следующий этап — уже не правка логики, а публикация и стендовая проверка при необходимости.
|
||||
- DEV-релиз `2.0.3` успешно собран и загружен в registry `nubes-dev` через `TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev`.
|
||||
- Перед этим уже был подготовлен короткий запрос на ревью для Opus и получен ответ, который подтвердил направление правки.
|
||||
|
||||
---
|
||||
|
||||
## 8. Отдельный диалог про `organization_uid`, refSvcId и универсальное поведение
|
||||
|
||||
### 8.1. Что стало проблемой
|
||||
- В `vc_vdc` поле `organization_uid` можно передавать как display name (`kontora`), так и как UUID организации.
|
||||
- В коде `provider/internal/resources_gen/21_vc_vdc_resource.go` это поле сейчас резолвится через `ResolveRefSvcParamValue(...)` в UUID.
|
||||
- После `apply` Terraform видит расхождение: в конфиге было имя, в state оказался UUID, и появляется ошибка `Provider produced inconsistent result after apply`.
|
||||
- Параллельно в этом же ресурсе остаются ручные `EqualFold`-хаки, которые пытаются сохранить старое значение, но не решают кейс "имя vs UUID".
|
||||
|
||||
### 8.2. Почему это сравнивали с S3
|
||||
- Для `nubes_s3_bucket` похожее поведение уже работает: ref-поле `s3_user_uid` проходит через общий механизм refSvc-резолва и state-refresh.
|
||||
- В S3 есть симметричный путь: UUID можно принимать на вход, а состояние при чтении синхронизируется через общий mapping-слой.
|
||||
- Поэтому S3 не падает на inconsistency, а VDC падает из-за локальных restore-хаков и разного поведения на create/read/update.
|
||||
|
||||
### 8.3. Что выяснили по коду
|
||||
- Ключевой участок VDC:
|
||||
- [provider/internal/resources_gen/21_vc_vdc_resource.go](provider/internal/resources_gen/21_vc_vdc_resource.go#L197-L202)
|
||||
- [provider/internal/resources_gen/21_vc_vdc_resource.go](provider/internal/resources_gen/21_vc_vdc_resource.go#L323-L338)
|
||||
- [provider/internal/resources_gen/21_vc_vdc_resource.go](provider/internal/resources_gen/21_vc_vdc_resource.go#L388-L403)
|
||||
- [provider/internal/resources_gen/21_vc_vdc_resource.go](provider/internal/resources_gen/21_vc_vdc_resource.go#L511-L525)
|
||||
- В S3 аналогичный слой устроен аккуратнее:
|
||||
- [provider/internal/resources_gen/13_s3bucket_resource.go](provider/internal/resources_gen/13_s3bucket_resource.go#L149-L159)
|
||||
- [provider/internal/resources_core/state_refresh.go](provider/internal/resources_core/state_refresh.go#L82-L96)
|
||||
- [provider/internal/resources_core/params_ref_mapping.go](provider/internal/resources_core/params_ref_mapping.go#L124-L147)
|
||||
|
||||
### 8.4. Что решил сделать дальше
|
||||
- Пользователю нужен не частный фикс только для VDC, а универсальная схема для всех refSvcId-полей.
|
||||
- Была сформулирована задача для Opus: определить, какой канон выбрать для state, где делать name→UUID и UUID→display_name, и как убрать ручные `EqualFold`-хаки без поломки S3 и других уже рабочих ресурсов.
|
||||
- Отдельно зафиксировано требование: ответ Opus нужен короткий, но сам вопрос должен быть подробным и однозначным.
|
||||
|
||||
### 8.5. Важный вывод на сейчас
|
||||
- Универсальное решение пока не внедрено.
|
||||
- Текущий безопасный путь — сначала получить короткий архитектурный ответ от Opus, а уже потом править генератор и пересобирать ресурсы.
|
||||
|
||||
---
|
||||
|
||||
## 9. Детерминированная пересборка генератора
|
||||
|
||||
- После отдельного разбора `kind: modifier` выяснилось, что падение генерации было эксплуатационным: запускался устаревший бинарник `resource-generator`, а не текущие исходники.
|
||||
- В `TOOLS/scripts/02_generate_resources_and_docs_v2.sh` убран `mtime`-гард через `find ... -newer`; генераторы теперь всегда собираются заново перед прогоном.
|
||||
- Это сделано специально, чтобы старый бинарник больше не мог скрыть поддержку новых `kind`-веток в YAML-спеках.
|
||||
- Дополнительно `TOOLS/resource-generator/bin/` добавлен в ignore, чтобы локальный stale-артефакт не путал следующий запуск.
|
||||
@@ -1,194 +0,0 @@
|
||||
# Передача контекста: Terraform-провайдер Nubes
|
||||
|
||||
Дата: 2026-09-22 | Версия DEV: 2.0.8 | HEAD: 13beb9c (`release(dev): 2.0.8`)
|
||||
|
||||
Документ для старта новой сессии. Прочитать целиком перед любыми действиями.
|
||||
|
||||
---
|
||||
|
||||
## 1. Правила работы (соблюдать строго)
|
||||
|
||||
- **Никаких действий без прямого разрешения.** Правки, сборки, заливки, коммиты, запуск
|
||||
terraform, запросы в API — только по явной команде оператора.
|
||||
- **Вопрос в любой форме = только ответ.** Не выполнять действий, не предлагать «а ещё могу».
|
||||
- **Коммитить после каждой правки**, разбивая по смыслу. Не копить в рабочем дереве.
|
||||
- **Не расширять область работ.** Формулировка «сделай актуальным везде» не даёт права
|
||||
на дополнительные шаги.
|
||||
- **Не догадываться.** Не уверен — сказать прямо и спросить. Причину бага доказывать
|
||||
фактами (логи, трассировки, содержимое файлов), а не гипотезами.
|
||||
- Язык ответов — русский.
|
||||
|
||||
---
|
||||
|
||||
## 2. Проект
|
||||
|
||||
- Репозиторий: `/home/naeel/TF/tf_provider`
|
||||
- Провайдер: `terraform-provider-nubes`, Go, `terraform-plugin-framework v1.8.0`
|
||||
- **Go-модуль провайдера лежит в `provider/`** (не в корне). `go build ./...` из корня
|
||||
падает с «directory prefix . does not contain main module».
|
||||
- Генераторы в `TOOLS/`:
|
||||
- `yaml-generator` — из API в YAML-спеки;
|
||||
- `resource-generator` — из YAML в Go (шаблоны `text/template` в
|
||||
`TOOLS/resource-generator/internal/templates/`);
|
||||
- `docs-generator` — из YAML в Markdown.
|
||||
- Пайплайн: `TOOLS/scripts/01_generate_yamls.sh` → `02_generate_resources_and_docs_v2.sh`
|
||||
→ `03_build_and_upload_provider.sh` (скрипт `03` сам прогоняет `01` и `02`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Состояние на 2026-09-21 (конец сессии)
|
||||
|
||||
- Ветка `master`, **рабочее дерево чистое**, HEAD = `13beb9c` (`release(dev): 2.0.8`).
|
||||
- Локальные коммиты **в origin не пушились**.
|
||||
- **DEV-версия: 2.0.8**, залита в
|
||||
`tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes`,
|
||||
подпись GPG `CB3A0DF161ECC416` (`tazet@narod.ru`, ключ `secrets/private_key.asc`).
|
||||
- S3: `prod-s3/nubes-terraform-registry/...`, endpoint `https://s3.msk-1.ngcloud.ru`.
|
||||
- Namespace: `nubes-dev`, провайдер `nubes`.
|
||||
- Terraform v1.9.5 локально.
|
||||
|
||||
---
|
||||
|
||||
## 4. Что починено в этой сессии
|
||||
|
||||
| Коммит | Что |
|
||||
|---|---|
|
||||
| `7ecd2aa` | детерминированная пересборка генераторов (удалён устаревший бинарь) |
|
||||
| `2286d34` | destroy-guard в `ModifyPlan` + универсальный refSvc (имя или UUID) |
|
||||
| `1401003` | release 2.0.4 |
|
||||
| `d608fba` | FullPipe: `edge.tf`, `storage_config` fast→SATA |
|
||||
| `92e04da` | TODO-документ по багу docs-generator |
|
||||
| `bffe3d9` | refSvc-поля без `Computed` (unset = null, а не unknown) |
|
||||
| `61c7e20` | HISTORY сессии |
|
||||
| `54b0baa` | release 2.0.5 |
|
||||
| `af2e10b` | docs-generator: `map-fixed` → `= { ... }` (аргумент, не блок) |
|
||||
| `7c2cc67` | docs-generator: `array-map-fixed` → `jsonencode([...])` |
|
||||
| `3ca0752` | docs-generator: строковые дефолты в кавычках |
|
||||
| `8d405ba` | core: lifecycle-aware подсказки + `supportsSuspend` в сигнатурах |
|
||||
| `c6715e8` | генератор: `supportsSuspend` в diagnostics; нет `suspend_on_destroy` без suspend |
|
||||
| `69808bd` | release 2.0.6 |
|
||||
| `67c4d2f` | `TOOLS/scripts/validate_docs_examples.sh` |
|
||||
| `14ada09` | ТЗ для Flash по tainted-replace |
|
||||
| `724f5f7` | **убрана create-time проверка существования из `ModifyPlan`** (ломал tainted-replace и `destroy`) |
|
||||
| `25080b7` | release 2.0.7 |
|
||||
| `3c0157a` | **`ShouldBeOptionalComputed`**: read-back параметры без Default → `Optional+Computed` |
|
||||
| `4b34cc7` | **core: гарантия known** — `unknown → null` для read-back полей |
|
||||
| `13beb9c` | release 2.0.8 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Ключевые архитектурные факты (не переоткрывать заново)
|
||||
|
||||
1. **Две копии сгенерированного кода.**
|
||||
- `provider/internal/resources_gen/` — в `.gitignore`, локальный артефакт;
|
||||
- `generated/dev/go/` — актуальный вывод генератора; именно его компилирует релиз
|
||||
(скрипт `03` копирует его в temp-копию `provider`).
|
||||
Проверять надо **`generated/dev/go`**, не `resources_gen`. Проверка не той копии
|
||||
уже приводила к ложным выводам.
|
||||
|
||||
2. **Рецепт проверки сборки без релиза:**
|
||||
```bash
|
||||
TMP=$(mktemp -d) && cp -R provider "$TMP/provider" && \
|
||||
find "$TMP/provider/internal/resources_gen" -maxdepth 1 -type f -name '*.go' -delete && \
|
||||
cp generated/dev/go/*.go "$TMP/provider/internal/resources_gen/" && \
|
||||
(cd "$TMP/provider" && go build ./...) && echo BUILD_OK && rm -rf "$TMP"
|
||||
```
|
||||
|
||||
3. **Версия правится в 3 файлах:** `TOOLS/config/dev/profile.env` (`VERSION`),
|
||||
`DEV_STAND/FullPipe/versions.tf`, `VERSIONS.md`.
|
||||
Затем коммит `release(dev): X.Y.Z` и
|
||||
`./TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev X.Y.Z`.
|
||||
|
||||
4. **Перезаливка той же версии бесполезна** — Terraform не перекачает провайдер.
|
||||
Всегда бампать версию.
|
||||
|
||||
5. `*.tfvars` в `.gitignore` (реальный токен в `terraform.tfvars` безопасен от коммита).
|
||||
|
||||
6. **Read-back инвариант.** Провайдер читает параметр обратно из `state_params` инстанса
|
||||
(`RefreshResourceState` + `InputField`). Такой параметр обязан быть `Optional+Computed`,
|
||||
если он не `Required` и без `Default` — правило `helpers.ShouldBeOptionalComputed`.
|
||||
Иначе `plan=null` vs `state=значение` → «Provider produced inconsistent result after
|
||||
apply».
|
||||
|
||||
7. `RefreshResourceState` при отсутствии кода в `state_params` схлопывает `unknown → null`
|
||||
(иначе «Provider produced invalid result object after apply: ... was unknown»).
|
||||
|
||||
8. **`terraform destroy` при tainted-ресурсе** выполняет внутренний обычный plan, который
|
||||
планирует замену (destroy+create); create-узел замены приходит в `ModifyPlan` с prior
|
||||
state = null — отличить замену от создания невозможно. Поэтому create-time проверка
|
||||
существования из `ModifyPlan` убрана (коммит `724f5f7`), проверка осталась в `Create`.
|
||||
|
||||
9. У сервиса без операции `suspend` в YAML: `SupportsSuspendDestroy=false` →
|
||||
`deleteMode := "delete"`, атрибут `suspend_on_destroy` не генерируется, а подсказки в
|
||||
diagnostics не предлагают adopt (он невозможен).
|
||||
|
||||
10. Вложенные параметры (`map-fixed`) — `schema.SingleNestedAttribute`; в HCL это
|
||||
**аргумент** `= { ... }`, не блок. `array-map-fixed` — `schema.StringAttribute`
|
||||
(JSON-строка).
|
||||
|
||||
11. Логи и артефакты отладки: `/tmp/nubes_find_debug.log` (пишет только Plan-диагностика),
|
||||
`/tmp/plan_trace.txt`, `/tmp/plan_destroy.txt`, `/tmp/plan_norefresh.txt`.
|
||||
|
||||
12. Секреты: `secrets/private_key.asc`, `secrets/public_key.asc`, `secrets/dev.token`.
|
||||
Не выводить содержимое в чат.
|
||||
|
||||
---
|
||||
|
||||
## 6. Файлы, которые нужно прочесть
|
||||
|
||||
**Обязательно:**
|
||||
|
||||
1. `.github/copilot-instructions.md` — жёсткие правила оператора.
|
||||
2. `VERSIONS.md` — что и когда залито.
|
||||
3. `HISTORY/2026-09-21_fullpipe_vdc_nsxt_and_refsvc_fixes.md` — журнал предыдущей сессии.
|
||||
4. `TOOLS/resource-generator/internal/templates/instance.go` — главный шаблон ресурса
|
||||
инстанса (ModifyPlan / Create / Read / Update / Delete / Schema).
|
||||
5. `TOOLS/resource-generator/internal/helpers/helpers.go` — `ShouldBeOptionalComputed`,
|
||||
`ParamDefaultExpr`, `IsNested`, nested-хелперы.
|
||||
6. `provider/internal/resources_core/state_refresh.go` — read-back и инвариант known.
|
||||
7. `provider/internal/resources_core/resource_diagnostics_required.go` — create-time
|
||||
проверки существования/усыновления, `runningConflictHint` / `suspendConflictHint`.
|
||||
8. `TOOLS/scripts/03_build_and_upload_provider.sh` и `TOOLS/scripts/build-provider.sh` —
|
||||
релизный пайплайн.
|
||||
9. `DEV_STAND/FullPipe/` — `versions.tf`, `vdc.tf`, `edge.tf`, `variables.tf`, `outputs.tf`.
|
||||
10. `docs/TODO/docs_generator_nested_attr_syntax.md` — описание бага docs-generator
|
||||
(уже исправлен, см. раздел 8).
|
||||
|
||||
**По необходимости:**
|
||||
|
||||
- `TOOLS/resource-generator/internal/templates/{subresource,modifier,action}.go`
|
||||
- `TOOLS/docs-generator/internal/writers/writers.go` — `formatParamOrBlock`, `sampleValue`,
|
||||
`isNestedListParam`
|
||||
- `provider/internal/resources_core/crud.go` — adopt / suspend / delete
|
||||
- `docs/prompt_for_flash_fix_tainted_replace.md` — разбор tainted-replace
|
||||
(реализован в `724f5f7`)
|
||||
- `TOOLS/scripts/validate_docs_examples.sh` — прогон `terraform validate` по примерам из доков
|
||||
- Память репозитория: `/memories/repo/registry-versions.md`
|
||||
|
||||
---
|
||||
|
||||
## 7. Стенд FullPipe (Organization → vDC → Edge)
|
||||
|
||||
- Каталог `DEV_STAND/FullPipe`, провайдер берётся из `versions.tf` (сейчас 2.0.8).
|
||||
- `nubes_vc_vdc.vdc` — `suspend_on_destroy = true`, `adopt_existing_on_create = true`.
|
||||
- `nubes_vc_nsxt.edge` — `routed_net_configuration` задаётся **через `=`** (объект), не блоком.
|
||||
- Подхватить новую версию: `terraform init -upgrade`.
|
||||
- На 2026-09-21 `nubes_vc_nsxt.edge` был **tainted** в state (последствие прошлых
|
||||
неудачных apply). Убирается `terraform untaint nubes_vc_nsxt.edge`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Открытые вопросы
|
||||
|
||||
1. **`docs/TODO/docs_generator_nested_attr_syntax.md` устарел** — баг исправлен
|
||||
(`af2e10b`, `7c2cc67`, `3ca0752`), но в файле статус «не исправлено».
|
||||
2. **Стенд FullPipe не проверен end-to-end на 2.0.8** — нет подтверждённого успешного
|
||||
`apply` (Organization → vDC → Edge) и `destroy` после фиксов.
|
||||
3. Полный прогон `terraform validate` по всем примерам из доков (63 сервиса) не делался —
|
||||
проверен только `vc_nsxt`. Скрипт для прогона готов:
|
||||
`TOOLS/scripts/validate_docs_examples.sh`.
|
||||
4. `TOOLS/resource-generator/internal/templates/modifier.go:65` — та же схема `Computed`
|
||||
без ветки read-back. Для бага «inconsistent result after apply» не критично
|
||||
(Create/Update модификаторов не читают обратно в state), но при работе с `kind: modifier`
|
||||
держать в голове.
|
||||
5. Пуш локальных коммитов в `origin/master` не делался.
|
||||
@@ -1,199 +0,0 @@
|
||||
# CHAT RESUME: IaC-развёртывание Штурвала — состояние на 2026-09-24
|
||||
|
||||
> **Кому:** новый чат / новый участник. Читать целиком, это хендовер.
|
||||
> **Что это:** сводка длинной сессии (2026-09-23/24) по вопросу «как дать клиенту IaC для цепочки Штурвал».
|
||||
> **Статус:** решение НЕ принято, код НЕ написан. Есть анализ, проверенные факты и развилка.
|
||||
|
||||
---
|
||||
|
||||
## 0. TL;DR (одним абзацем)
|
||||
|
||||
Клиенту нужен **настоящий IaC**: один конфиг + `terraform apply` = вся инфраструктура. Цепочка Штурвала
|
||||
(`vcOrg → vcVdc → vcNsxt → [modify оргов/эдж] → k8sShturval`) упирается в две операции `modify`, которые
|
||||
провайдер сейчас выразить не может: в схеме tf-ресурса их параметров нет (схема строится только из `create`).
|
||||
Ручной ЛК и скрипт **отклонены** — это не IaC. Единственный каноничный путь — **отдельные tf-ресурсы под
|
||||
модификации** (как сделано в провайдере VMware Cloud Director, прецедент в папке `!/`),
|
||||
плюс желательно, чтобы платформа отдавала через API выводимые значения (`ipSpaceName`), которые юзер знать не может.
|
||||
Часть прежних «блокеров» оказалась **ложной** — см. §7, это критично.
|
||||
|
||||
---
|
||||
|
||||
## 1. Задача
|
||||
|
||||
Развернуть Штурвал (k8s) целиком через Terraform, с корректным `apply`/`plan`/`destroy`:
|
||||
|
||||
```
|
||||
vcOrg -> create (в нашем случае орг уже создана вручную и НЕ в state)
|
||||
vcVdc -> create
|
||||
vcNsxt -> create (Edge)
|
||||
------------------------------
|
||||
vcOrg -> modify (аллокация внешних IP: vIPConfigure)
|
||||
vcNsxt -> modify (включить SNAT, указать внешний IP из vcOrg: ipSpaceName)
|
||||
------------------------------
|
||||
k8sShturval -> create
|
||||
```
|
||||
|
||||
Операции строго последовательны. Проблема: между `nsxt.create` и `shturval.create` стоят два `modify`,
|
||||
которые в один tf-ресурс не укладываются.
|
||||
|
||||
---
|
||||
|
||||
## 2. Позиции участников (Telegram 2026-09-23)
|
||||
|
||||
| Кто | Позиция |
|
||||
|---|---|
|
||||
| **Владимир (наш)** | Пытается сделать всё терраформом. `create` — ок, `modify` — «просто так не получится, надо создавать дополнительные ресурсы-модификаторы». Сомневался: может, руками в ЛК или скриптом. |
|
||||
| **Георгий** | **Основной запрос клиента — IaC.** Ручной ЛК/скрипт «совсем не подойдёт». Правильно указал порядок: сначала nsxt, потом квота на оргу. |
|
||||
| **Дмитрий** | Хочет «terraform apply одного ямлика со всей инфрой — и всё». Спрашивал, как это реализовано в провайдере Cloud Director из провайдерской УЗ. |
|
||||
| **Виталий** | Дал 3 tf-файла (`vmware_org.tf`, `vdc.tf`, `network.tf.tmpl`) — **официальный провайдер Cloud Director**. Ключевое: `ipSpace → providerGateway → providerVdc` (цепочка, которую юзер не знает), «квоту делаем через API на оргу, т.к. эджей много, а орга одна», «при создании параметры подкладываются», «в организации один T0». |
|
||||
|
||||
---
|
||||
|
||||
## 3. Подтверждённые факты (с источниками)
|
||||
|
||||
1. **Схема tf-ресурса строится ТОЛЬКО из `create`** (генератор `TOOLS/resource-generator`).
|
||||
→ modify-only параметры в схему не попадают.
|
||||
2. **`vIPConfigure`** (vc_org, modify id **207**, param id **662**, `array-map-fixed`, sub: `name`=39, `count`=40)
|
||||
есть **только** в modify. В `create` (id 136) — только `resourceRealm`(418), `organizationType`(556), `orgSuffix`(1125).
|
||||
Файл: `generated/dev/resources_yaml/19_vc_org.yaml`.
|
||||
3. **`ipSpaceName`** (vc_nsxt, modify id **111**, param id **372**, string, required false) есть **только** в modify.
|
||||
В `create` (id 10) — `vdcUid`, `needEnableAVI`(340), `virtualServicesCount`(341), `qosProfile`(825), `routedNetConfiguration`(1110).
|
||||
Файл: `generated/dev/resources_yaml/22_vc_nsxt.yaml`.
|
||||
4. **`vIPConfigure` — replace-семантика, НЕ накопительная.** Повторный modify с тем же count не задваивает (1→1),
|
||||
работает вверх/вниз/до 0, `count=0` принимается (несмотря на `minvalue:1`), live читается из `state.params`.
|
||||
Источник: `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md` (проверено на стенде DEV_STAND/FullPipe, провайдер 2.0.9).
|
||||
5. **`ipSpaceName` — выводимое значение:** цепочка `providerVdc → providerGateway → ipSpace`, юзер его не знает.
|
||||
Платформа сейчас «подкладывает» недостающие параметры при создании пустой орги. Список доступных ipSpace
|
||||
в статической выгрузке (`/instanceOperations/default/{id}`) — пустой, виден только в ЛК/живом инстансе. (Виталий + форензика)
|
||||
6. **Допущение платформы: в организации один T0/провайдер-шлюз.** Рост T0 отложен, но при нём схема сломается.
|
||||
7. **Доступ к значению modify-параметра:** live берётся из `GET /instances/{uid}` → `state.params`,
|
||||
а НЕ из `cfsParams.paramValue` (это сохранённый дефолт формы от прошлых прогонов, см. `dtCreated` в HAR).
|
||||
8. **Flow modify (HAR `org_enough_.har`):** `POST /instanceOperations {instanceUid, operation:"modify"}` →
|
||||
`POST /instanceOperationCfsParams {paramValue, instanceOperationUid, svcOperationCfsParamId}` →
|
||||
`POST /instanceOperations/{opUid}/validate-cfs` → `POST /instanceOperations/{opUid}/run`.
|
||||
9. **Прецедент (VCD, папка `!/`):** та же цепочка делается **отдельными ресурсами** с `depends_on`:
|
||||
`vcd_nsxt_alb_settings` (`count = var.alb_enable ? 1 : 0`), `vcd_nsxt_alb_edgegateway_service_engine_group`
|
||||
(`reserved_virtual_services`), `vcd_network_routed_v2`, `vcd_ip_space_custom_quota` (на оргу).
|
||||
Приём «включено/выключено» = существование ресурса; **inverse = удаление ресурса**.
|
||||
10. **Nubes — надстройка над Cloud Director.** Наши `vcOrg`/`vcVdc`/`vcNsxt` создают объекты в VCD.
|
||||
Провайдер Nubes — обёртка над API Nubes, отдельный от официального `terraform-provider-vcd`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Почему «просто добавить поле» / «насильно в state» / «скрипт» — не работает
|
||||
|
||||
- **Добавить поле в .tf** → падает на `plan`: атрибута нет в схеме (см. §3.1).
|
||||
- **Terraform не может внутри одного ресурса** сделать «create → через N шагов modify».
|
||||
Декларативный Update требует **желаемого состояния**; `ipSpaceName` — это включение SNAT, а не значение поля.
|
||||
- **Вписать в tfstate** нельзя: state валидируется по схеме провайдера, а «записанное» состояние ≠ реальность
|
||||
(получишь чистый `plan` при сломанной инфраструктуре). `null_resource`/`terraform_data` + `local-exec` даёт
|
||||
только **факт** выполнения, не состояние.
|
||||
- **Ручной ЛК / скрипт вне tf** — отклонено: это не IaC (нет версионирования, воспроизводимости, дрейфа, отката).
|
||||
- **Правка провайдера «по-старому»** (метки `kind: modifier` в YAML + реестр в `yaml-generator`) — **отменённый заход**,
|
||||
см. §7.
|
||||
|
||||
---
|
||||
|
||||
## 5. Каноничное решение (что делать)
|
||||
|
||||
**Два независимых требования — нужны оба:**
|
||||
|
||||
1. **Отдельные tf-ресурсы под `modify`** (наша сторона): e.g. `nubes_org_ip_allocation` (vIPConfigure),
|
||||
`nubes_nsxt_network` (`needEnableAVI`/`virtualServicesCount`/`ipSpaceName`/`routedNetConfiguration`),
|
||||
привязка через `depends_on` к орге/эджу.
|
||||
- `Read` = читать родителя (`state.params`), `Delete` = обратный modify (`count=0` / `needEnableAVI=false` /
|
||||
`ipSpaceName="no-needed"`), `Create/Update` = `modify` с параметрами.
|
||||
2. **Доступ к выводимым значениям через API** (сторона платформы): динамические `valueList` + цепочка
|
||||
`providerVdc → providerGateway → ipSpace`. Иначе юзер подсматривает в ЛК (ручной ввод как временный долг допустим,
|
||||
но поле надо делать `Optional+Computed`, чтобы позже включить автоподстановку без breaking change).
|
||||
|
||||
**Дизайн-требования, заложить сразу:**
|
||||
- `ip_space_name` → `Optional + Computed`;
|
||||
- optional селектор шлюза (`t0_id` / `provider_gateway`) — чтобы рост T0 не сломал схему;
|
||||
- не тащить доменные метки в универсальный YAML (см. §7).
|
||||
|
||||
---
|
||||
|
||||
## 6. Что можно делать уже сейчас, не дожидаясь платформы
|
||||
|
||||
- **`vIPConfigure` (аллокация IP на оргу) — можно делать начисто**: блокеров нет, replace-семантика подтверждена тестом,
|
||||
Read/Delete выражаются через `state.params` и `count=0`.
|
||||
- **`needEnableAVI` / `virtualServicesCount`** — выразимы (есть и в create, и в modify).
|
||||
- **`ipSpaceName`** — единственное, что упирается в платформу; временно — ввод юзером (значение он и так смотрит в ЛК).
|
||||
- **`routedNetConfiguration`** — есть в create (1110) и modify (1112), выразимо.
|
||||
|
||||
**Не решено (требует решения до кода):**
|
||||
- откуда генератор берёт **список** доменных ресурсов (это не данные API, а доменное знание — то самое место,
|
||||
где раньше был реестр в `yaml-generator`). Форму выбрать осознанно: явный список vs отдельный вход.
|
||||
- какие шаги вообще остаются на **провайдерском** (облачном) уровне, а какие отдаются тенанту
|
||||
(квота ipSpace / ALB — возможно, это уровень облака, и тогда в клиентский tf они не входят).
|
||||
|
||||
---
|
||||
|
||||
## 7. ⚠️ Исправленные ошибки (НЕ повторять!)
|
||||
|
||||
1. **Ложный факт «`vIPConfigure` накопительный».** Был протащен в промпт для Opus как «подтверждённый», из-за чего
|
||||
Opus построил вывод «накопительный API несовместим с декларативной моделью» и объявил два «блокера»
|
||||
(Read счётчика, адресное освобождение). **Оба ложны** — тест `ORG_IP_MODIFIER_TEST_2026-09-22.md` доказывает
|
||||
идемпотентность и работу в обе стороны. Документы исправлены.
|
||||
2. **Прежняя repo-память (`modifier-gotchas.md`) содержала устаревшие утверждения** (эпоха 09-21/22):
|
||||
`kind: modifier`, `delete_strategy`, `nubes_vc_nsxt_network`, «у vc_nsxt нет instance-modify»,
|
||||
«Update = no-op». **Файл перезаписан** актуальными фактами. НЕ использовать старую формулировку.
|
||||
3. **Старые «модификаторы» были написаны и даже работали** (09-22), но заход признан негодным:
|
||||
доменную логику вшили в универсальный генератор (метки в YAML). Соответствующие документы помечены баннером LEGACY.
|
||||
|
||||
---
|
||||
|
||||
## 8. Карта файлов
|
||||
|
||||
**АКТУАЛЬНО (источник истины):**
|
||||
- `docs/CHAT_RESUME_IAC_2026-09-24.md` ← этот файл
|
||||
- `docs/SHTURVAL_IAC_MODIFY_ANALYSIS_2026-09-23.md` — анализ, варианты A–E, мнение
|
||||
- `docs/OPUS_ANSWER_IAC_SHTURVAL_MODIFY_2026-09-23.md` — ответ Opus + поправки (ложные блокеры сняты)
|
||||
- `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md` — проверенные факты по vIPConfigure
|
||||
- `docs/prompts/prompt_for_opus_iac_shturval_modify.md` — промпт (факты исправлены)
|
||||
- `generated/dev/resources_yaml/19_vc_org.yaml`, `22_vc_nsxt.yaml` — спеки (факты по операциям/параметрам)
|
||||
- `HAR/org_enough_.har`, `HAR/org2.har`, `HAR/edge_.har` — live-семантика modify
|
||||
- `!/` — прецедент Cloud Director (3 файла `vcd_*`), НЕ наш код
|
||||
|
||||
**LEGACY (история, НЕ источник истины):**
|
||||
- `PLAN_modifier_redesign.md` ⛔ (баннер добавлен)
|
||||
- `docs/60_strategy/modifier_resources_ideology_and_specification.md` ⛔ (баннер добавлен)
|
||||
- `docs/inverse_rollback_analysis_2026-09-23.md`
|
||||
- `prompt_for_opus_modifier_global_architecture.md`, `prompt_for_opus_modifier_review_2.md` (корень)
|
||||
- `docs/prompts/prompt_for_opus_modifier_*.md`, `docs/prompts/prompt_for_opus_modifiable_architecture.md`
|
||||
- `HISTORY/OPUS/2026-09-22_modifier_*.md`
|
||||
- `PLAN_regenerate_providers_0.0.1.md` — перекрыт `PLAN_FLASH_reversion_cleanup.md` (0.0.1 объявлен легаси)
|
||||
|
||||
> Примечание: `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md` — **актуален** (это отчёт по проверке, не план).
|
||||
|
||||
**Инфра-контекст:**
|
||||
- `TOOLS/config/<стенд>/profile.env` → `NUBES_API_ENDPOINT`, `TOKEN_FILE`
|
||||
- `VERSIONS.md` — залитые версии (DEV на 09-22 = 2.0.13; в `generated/dev/provider_build/` лежат 2.0.17 — расхождение)
|
||||
|
||||
---
|
||||
|
||||
## 9. Развилка (ждёт решения)
|
||||
|
||||
| Вариант | Суть | Вердикт |
|
||||
|---|---|---|
|
||||
| **A** | Отдельные tf-ресурсы под modify (+ позже data-source для ipSpace) | канон; реализуемо сейчас для `vIPConfigure` |
|
||||
| **B** | То же, но значения вводит юзер вручную | приемлемый временный долг при `Optional+Computed` |
|
||||
| **C** | Ждать новых спеков платформы | часть работ всё равно можно начать сейчас |
|
||||
| **D** | Ручной ЛК / скрипт вне tf | ❌ отклонено (требование IaC) |
|
||||
| **E** | Пресеты/дефолтное окружение | снижает боль на старте, IaC не заменяет |
|
||||
|
||||
**Не принято:** делать ли `modify`-ресурсы доменными «руками» (и как их перечислять в генераторе) —
|
||||
вопрос архитектуры; и что из шагов остаётся за облаком.
|
||||
|
||||
---
|
||||
|
||||
## 10. Открытые вопросы к людям
|
||||
|
||||
1. **Георгию/продукту:** какие шаги цепочки — тенантские, а какие — уровень облака (квота ipSpace, ALB)?
|
||||
От этого зависит объём ресурсов в клиентском tf.
|
||||
2. **Виталию (платформа):** можете отдавать через API (а) динамические списки значений (`ipSpace`),
|
||||
(б) цепочку `providerVdc → providerGateway → ipSpace`?
|
||||
3. **Виталию:** файлы `!/` — ваш инструмент провижининга от провайдерской УЗ или справочный пример?
|
||||
(в диалоге он сказал только «это провайдер от клауд директора»)
|
||||
4. **Команде:** откуда генератор берёт список доменных ресурсов-модификаций (форма решения, без меток в YAML).
|
||||
@@ -1,23 +0,0 @@
|
||||
# Резюме — tf_provider документация
|
||||
|
||||
## Сайты
|
||||
- 5.1.2 (рабочий): https://registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> <!-- ⛔ LEGACY: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->/docs/nubes-test/nubes/5.1.2/
|
||||
- 5.2.0 (мусор): удалить
|
||||
|
||||
## Генератор
|
||||
TOOLS/bin/docs-generator -resources generated/test/resources_yaml -docs generated/test/docs -services TOOLS/config/test/services_list.txt -version "5.1.2" -api-endpoint "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
|
||||
|
||||
## CSS
|
||||
generated/test/docs/assets/extra.css — 1800px, синий/зелёный, без переносов
|
||||
|
||||
## Промпт
|
||||
docs/prompt_deepseek_flash.txt — для DeepSeek Flash: MAN HTML→MD, русские заголовки, убрать br. НИЧЕГО не удалять.
|
||||
|
||||
## Сборка
|
||||
python3 -m mkdocs build --config-file /tmp/mkdocs_clean.yml
|
||||
|
||||
## S3
|
||||
mc cp --recursive generated/test/site/ "reg/terraform-registry/docs/nubes-test/nubes/5.1.2/"
|
||||
|
||||
## ЗАДАЧА
|
||||
На 5.1.2: MAN HTML→Markdown + русские заголовки + убрать br. Параметры из API не трогать.
|
||||
@@ -1,72 +0,0 @@
|
||||
# Резюме + план действий (новый чат)
|
||||
|
||||
Дата: 2026-02-01
|
||||
|
||||
## Контекст
|
||||
- Репозиторий: /home/naeel/terra
|
||||
- Генератор провайдера: /home/naeel/terra/nubes_provider_gen
|
||||
- Адрес провайдера (актуально): registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> <!-- ⛔ LEGACY: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->/nubes/nubes
|
||||
- Registry S3 bucket: terraform-registry (endpoint s3.msk-1.ngcloud.ru)
|
||||
- Dev override: /home/naeel/terra/test_persistent/dev_override.tfrc
|
||||
|
||||
## Что уже сделано
|
||||
1) **VM ресурс сгенерирован**:
|
||||
- YAML: /home/naeel/terra/nubes_provider_gen/resources_yaml/vm.yaml
|
||||
- Go: /home/naeel/terra/nubes_provider_gen/internal/resources_gen/vm_resource.go
|
||||
2) **Тестовая конфигурация**:
|
||||
- /home/naeel/terra/test_persistent/main.tf
|
||||
- Добавлен ресурс nubes_vm.test_vm (использует vapp_uid xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, образ Ubuntu_22-20G).
|
||||
3) **Провайдер v2.0.0 собран и загружен в registry**:
|
||||
- Артефакты в S3 по пути: s3://terraform-registry/registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->/nubes/nubes/2.0.0/
|
||||
- Файлы: terraform-provider-nubes_2.0.0_linux_amd64.zip, _SHA256SUMS, _SHA256SUMS.sig
|
||||
4) **GPG ключ в наличии**:
|
||||
- Key ID: FFE0F4D723F14BCA
|
||||
5) **Токены**:
|
||||
- Правило: имя файла = время окончания токена (HH-MM-SS). Последний: /home/naeel/terra/23-25-49.token
|
||||
6) **Параметры VM**:
|
||||
- vapp_uid найден в debug_vm/terraform.tfvars
|
||||
|
||||
## Текущие проблемы
|
||||
- Terraform init без dev_override лезет в registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> и может падать.
|
||||
- Для apply нужны актуальные токены (401 были частые).
|
||||
|
||||
## План действий (VM)
|
||||
1) **Проверить токен**:
|
||||
- Убедиться, что в /home/naeel/terra/test_persistent/terraform.tfvars актуальный api_token.
|
||||
- Если новый токен получен — сохранить его как /home/naeel/terra/HH-MM-SS.token (время exp). Обновить terraform.tfvars.
|
||||
|
||||
2) **Init/Apply через dev_override**:
|
||||
- В /home/naeel/terra/test_persistent выполнить:
|
||||
- TF_CLI_CONFIG_FILE=./dev_override.tfrc terraform init
|
||||
- TF_CLI_CONFIG_FILE=./dev_override.tfrc terraform apply -auto-approve
|
||||
|
||||
3) **Создание VM**:
|
||||
- Проверить, что nubes_vm.test_vm создаётся.
|
||||
- Если TLS handshake timeout — повторять apply (как ранее с Postgres).
|
||||
|
||||
4) **Modify тесты (один инстанс)**:
|
||||
- Менять параметры (vm_cpu / vm_ram / access_port_list / need_add_zabbix_template) последовательно.
|
||||
- После каждого modify — apply, ждать завершения.
|
||||
|
||||
5) **Suspend/Resume**:
|
||||
- delete_mode = "suspend", resume_if_exists = true
|
||||
- terraform destroy -target nubes_vm.test_vm -auto-approve
|
||||
- terraform apply -auto-approve (должен сделать resume)
|
||||
|
||||
6) **Если VM тесты не пройдут**:
|
||||
- Тогда анализировать HAR в /home/naeel/terra/har/ (инструкции: сначала тесты, потом HAR).
|
||||
|
||||
## Важные файлы
|
||||
- VM YAML: nubes_provider_gen/resources_yaml/vm.yaml
|
||||
- VM Go: nubes_provider_gen/internal/resources_gen/vm_resource.go
|
||||
- Test config: test_persistent/main.tf
|
||||
- Token file: /home/naeel/terra/23-25-49.token (пример)
|
||||
|
||||
## Команды (шаблон)
|
||||
- Build:
|
||||
- cd /home/naeel/terra/nubes_provider_gen && go build -o terraform-provider-nubes
|
||||
- Apply:
|
||||
- cd /home/naeel/terra/test_persistent
|
||||
- TF_CLI_CONFIG_FILE=./dev_override.tfrc terraform init
|
||||
- TF_CLI_CONFIG_FILE=./dev_override.tfrc terraform apply -auto-approve
|
||||
|
||||
@@ -1,760 +0,0 @@
|
||||
<!-- ⛔ LEGACY: deck-api.ngcloud.ru ЗАКРЫВАЕТСЯ. Актуальный API: lk-api-gateway.ngcloud.ru/api/v1/svc -->
|
||||
# Полный анализ кодовой базы и план развития
|
||||
|
||||
**Дата:** 2026-03-13
|
||||
**Проект:** Terraform Provider for Nubes Cloud
|
||||
**Версия провайдера:** dev (legacy) / 5.0.18 (universal_rebuild)
|
||||
**Go:** 1.24 / Terraform Plugin Framework: v1.16 (legacy), v1.8 (rebuild)
|
||||
|
||||
---
|
||||
|
||||
## Содержание
|
||||
|
||||
1. [Общая архитектура](#1-общая-архитектура)
|
||||
2. [Инвентаризация кода](#2-инвентаризация-кода)
|
||||
3. [Критические проблемы (P0)](#3-критические-проблемы-p0)
|
||||
4. [Серьёзные проблемы (P1)](#4-серьёзные-проблемы-p1)
|
||||
5. [Средний приоритет (P2)](#5-средний-приоритет-p2)
|
||||
6. [Анализ по слоям](#6-анализ-по-слоям)
|
||||
7. [Эволюция API-клиента](#7-эволюция-api-клиента)
|
||||
8. [Генератор кода v2](#8-генератор-кода-v2)
|
||||
9. [Соответствие provider_philosophy.md](#9-соответствие-provider_philosophymd)
|
||||
10. [Тестирование](#10-тестирование)
|
||||
11. [Безопасность](#11-безопасность)
|
||||
12. [Дорожная карта (Roadmap)](#12-дорожная-карта-roadmap)
|
||||
13. [Рекомендации по агенту/модели](#13-рекомендации-по-агентумодели)
|
||||
|
||||
---
|
||||
|
||||
## 1. Общая архитектура
|
||||
|
||||
### Два провайдера в одном репозитории
|
||||
|
||||
| Компонент | Каталог | Версия | Registry Address | Статус |
|
||||
|-----------|---------|--------|------------------|--------|
|
||||
| **Legacy Provider** | `/internal/`, `/main.go` | dev | `registry.terraform.io/nubes/nubes` | Ручной код, 13 ресурсов |
|
||||
| **Universal Provider** | `/universal_rebuild/` | 5.0.18 | `registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> <!-- ⛔ LEGACY: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->/nubes/nubes` | Генерируемый, ~50 ресурсов |
|
||||
|
||||
### Архитектурные слои
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ TERRAFORM CLI / HCL │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Provider Layer (provider.go) │
|
||||
│ ├── Resource Registration │
|
||||
│ ├── Auth (token / env / file) │
|
||||
│ └── HTTP Client Init │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Resource Layer │
|
||||
│ ├── Manual (vm, edge, vdc, vapp, postgres…) │ ← legacy, internal/provider/
|
||||
│ └── Generated (50+ services) │ ← universal_rebuild/internal/resources_gen/
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Core Layer │
|
||||
│ ├── UniversalClient (API V6 flow) │
|
||||
│ ├── Instance Lookup / State │
|
||||
│ ├── Operation Runner / Polling │
|
||||
│ └── Timeout Management │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ CRUD Layer (resources_core/) │
|
||||
│ ├── CreateResource / UpdateResource / Delete │
|
||||
│ ├── adoptExistingInstanceOnCreate() │
|
||||
│ ├── State Refresh / Output Mapping │
|
||||
│ └── Params Compare / Ref Resolution │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Nubes Cloud API (deck-api.ngcloud.ru/api/v1) │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Конвейер генерации
|
||||
|
||||
```
|
||||
API (live) YAML specs Go code + Docs
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
01_generate_yamls.sh → resources_yaml/*.yaml → 02_generate_*.sh
|
||||
(service_ops_gen) (gen_v2 + docs_template_gen_v2)
|
||||
│
|
||||
▼
|
||||
03_build_and_upload.sh → S3
|
||||
04_build_and_publish_docs.sh → S3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Инвентаризация кода
|
||||
|
||||
### Legacy Provider (`internal/`)
|
||||
|
||||
| Файл | LOC | Назначение | Качество |
|
||||
|------|-----|------------|----------|
|
||||
| `provider/provider.go` | 110 | Регистрация, auth, HTTP клиент | ⚠️ InsecureSkipVerify |
|
||||
| `provider/client_impl.go` | ~350 | HTTP-клиент (NubesClient) | Без retry, без polling |
|
||||
| `provider/vm_resource.go` | ~500 | VM lifecycle (лучший ресурс) | ✅ Полный CRUD + import |
|
||||
| `provider/edge_resource.go` | ~400 | Edge gateway | ✅ Полный flow |
|
||||
| `provider/vdc_resource.go` | ~300 | VDC с triggers | ✅ Multi-stage create |
|
||||
| `provider/vapp_resource.go` | ~250 | vApp | ✅ Базовый CRUD |
|
||||
| `provider/postgres_resource.go` | ~200 | Postgres | ⚠️ Только create/delete |
|
||||
| `provider/s3bucket_resource.go` | ~200 | S3 bucket | ✅ CRUD |
|
||||
| `provider/pgadmin_resource.go` | ~150 | PgAdmin | ⚠️ Ограниченный |
|
||||
| `provider/org_resource.go` | ~100 | Organization | ⚠️ Минимальный |
|
||||
| `provider/quickstart_resource.go` | ~300 | Full-stack helper | Специальный |
|
||||
| `provider/tubulus_resource.go` | ~200 | AI/Gemini интеграция | Экспериментальный |
|
||||
| `provider/validators.go` | ~150 | Валидаторы | ✅ Хорошо |
|
||||
| `core/client.go` | 1065 | UniversalClient (V1–V6) | 🔴 Раздут, 6 версий |
|
||||
| `core/instance_lookup.go` | 87 | Поиск инстансов | ⚠️ Нет exact match |
|
||||
| `core/instance_ops.go` | 159 | Операции + polling | ⚠️ Fixed 5s interval |
|
||||
| **Итого** | **~4500** | | |
|
||||
|
||||
### Universal Rebuild (`universal_rebuild/`)
|
||||
|
||||
| Каталог | Файлов | LOC | Назначение |
|
||||
|---------|--------|-----|------------|
|
||||
| `internal/core/` | 5 | ~1400 | UniversalClient V6, timeouts, refSvc |
|
||||
| `internal/provider/` | 2 | ~300 | Provider setup, timeout embed |
|
||||
| `internal/resources_core/` | 16 | ~2500 | CRUD, state refresh, diagnostics, params |
|
||||
| `internal/resources_gen/` | ~100+ | ~15000+ | Сгенерированные ресурсы (50 сервисов) |
|
||||
| `resources_yaml/` | ~50 | — | YAML-спецификации сервисов |
|
||||
| `tools/gen_v2/` | 1 | ~2500 | Генератор Go-кода |
|
||||
| `tools/docs_template_gen_v2/` | 1 | ~800 | Генератор документации |
|
||||
| `tools/service_ops_gen/` | 1 | ~800 | API → YAML генератор |
|
||||
| `tools/service_params_gen/` | 1 | ~600 | Параметрический генератор |
|
||||
| **Итого** | **~180** | **~24000+** | |
|
||||
|
||||
### Generated Code Summary (`resources_gen/`)
|
||||
|
||||
| Тип ресурса | Кол-во | Примеры |
|
||||
|-------------|--------|---------|
|
||||
| Instance (CRUD) | ~50 | postgres, rabbitmq, kafka, k8s, vc_vm |
|
||||
| Subresource (user/db) | ~20 | postgres_user, postgres_database |
|
||||
| Action (restart/etc.) | ~10 | postgres_restart, postgres_recovery |
|
||||
| Registry | 1 | registry.go (auto-сгенерированный список) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Критические проблемы (P0)
|
||||
|
||||
### P0-1: InsecureSkipVerify=true в production
|
||||
|
||||
**Где:** `internal/provider/provider.go:103`
|
||||
|
||||
```go
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true, // ← MITM уязвимость
|
||||
}
|
||||
```
|
||||
|
||||
**Риск:** Атака "человек посередине" (MITM) — перехват API-токенов и данных.
|
||||
|
||||
**Решение:**
|
||||
```go
|
||||
// Новый атрибут провайдера:
|
||||
"insecure": schema.BoolAttribute{
|
||||
Optional: true,
|
||||
Description: "Skip TLS certificate verification (dev only)",
|
||||
},
|
||||
// + env var NUBES_INSECURE
|
||||
```
|
||||
|
||||
**Также проверить:** `universal_rebuild/internal/provider/provider.go` — аналогичная проблема.
|
||||
|
||||
---
|
||||
|
||||
### P0-2: Захардкоженный путь debug-лога
|
||||
|
||||
**Где:** `internal/core/client.go:18`
|
||||
|
||||
```go
|
||||
f, err := os.OpenFile("/home/naeel/terra/debug_nubes.log", ...)
|
||||
```
|
||||
|
||||
**Риск:** Сбой на любой другой машине. Потенциальная утечка данных в файл вне проекта.
|
||||
|
||||
**Решение:**
|
||||
- Использовать `tflog` (terraform plugin logging) вместо файлового лога
|
||||
- Или env var `NUBES_DEBUG_LOG` с fallback на `/tmp/nubes_debug.log`
|
||||
|
||||
---
|
||||
|
||||
### P0-3: Ноль автотестов
|
||||
|
||||
**Факт:** В репозитории не найдено ни одного `*_test.go` файла.
|
||||
|
||||
**Риск:**
|
||||
- Регрессии при правках генератора
|
||||
- Невозможно валидировать lifecycle-логику без ручной проверки
|
||||
- Нет CI/CD confidence
|
||||
|
||||
**Решение:** См. раздел [10. Тестирование](#10-тестирование).
|
||||
|
||||
---
|
||||
|
||||
### P0-4: 6 версий CreateGenericInstance в одном файле
|
||||
|
||||
**Где:** `internal/core/client.go` — 1065 строк, 6 методов.
|
||||
|
||||
| Версия | Строки | Статус |
|
||||
|--------|--------|--------|
|
||||
| V1 `CreateGenericInstance` | 52-168 | Legacy, не используется |
|
||||
| V2 `...Universal` | 195-334 | Legacy |
|
||||
| V3 `...UniversalV2` | 361-504 | Legacy |
|
||||
| V4 `...UniversalV3` | 531-676 | Legacy |
|
||||
| V5 `...UniversalV4` | 703-840 | Legacy |
|
||||
| V6 `...UniversalV5` | 867-1000 | Production |
|
||||
|
||||
**Риск:** Путаница — какой метод вызывать? Разная нормализация. Разные баги.
|
||||
|
||||
**Решение:**
|
||||
- V1–V5 — пометить `// Deprecated: use CreateGenericInstanceUniversalV5`
|
||||
- Убедиться, что все ресурсы используют V6/V5
|
||||
- В перспективе — удалить мёртвый код (после аудита вызовов)
|
||||
|
||||
---
|
||||
|
||||
## 4. Серьёзные проблемы (P1)
|
||||
|
||||
### P1-1: Lifecycle-флаги — legacy vs. canonical
|
||||
|
||||
**Требование (provider_philosophy.md §7-9):**
|
||||
- `adopt_existing_on_create` (default: `false`)
|
||||
- `suspend_on_destroy` (default: `true`)
|
||||
|
||||
**Реальность в legacy:**
|
||||
- `internal/generated/bolvan_resource_universal_lifecycle.go` использует `delete_mode` и `resume_if_exists`
|
||||
- Это прямо запрещено в стратегии
|
||||
|
||||
**Реальность в universal_rebuild:**
|
||||
- `resources_core/crud.go` использует `resumeIfExists` bool параметр
|
||||
- Генератор `gen_v2` генерирует канонические флаги `suspend_on_destroy`, `adopt_existing_on_create`
|
||||
- **Разрыв:** CRUD-слой принимает bool, но не полностью реализует decision matrix из §7
|
||||
|
||||
**Решение:**
|
||||
1. Обновить `crud.go` — полная реализация status-matrix:
|
||||
- `not created` → hard error
|
||||
- `creating/pending/failed` → hard error
|
||||
- `suspend` + `adopt=false` → hard error с диагностикой
|
||||
- `running` + `adopt=true` → adopt (import)
|
||||
- `running` + `adopt=false` → hard error "already exists"
|
||||
2. Legacy bolvanka — отдельная задача, не трогать
|
||||
|
||||
---
|
||||
|
||||
### P1-2: Read() — стабы в сгенерированных ресурсах
|
||||
|
||||
**Проблема:** Многие сгенерированные ресурсы имеют пустой `Read()`.
|
||||
|
||||
**Последствия:**
|
||||
- Terraform не видит state drift (облако изменилось, TF state устарел)
|
||||
- `terraform plan` после `apply` показывает расхождения
|
||||
- `terraform import` бесполезен без Read
|
||||
|
||||
**Уже решено в universal_rebuild?**
|
||||
Да, `state_refresh.go:RefreshResourceState()` обеспечивает полный read-back. Но нужно убедиться, что все сгенерированные ресурсы этот метод ВЫЗЫВАЮТ в своём Read().
|
||||
|
||||
---
|
||||
|
||||
### P1-3: Нет retry/backoff для API-вызовов
|
||||
|
||||
**Где:** Все HTTP-вызовы через `doRequest()` — один попытка, без retry.
|
||||
|
||||
**Реальный сценарий:**
|
||||
- API вернул 503 (maintenance) → terraform apply упал
|
||||
- Сетевой timeout → terraform apply упал
|
||||
- Rate limit (429) → terraform apply упал
|
||||
|
||||
**Решение:**
|
||||
```go
|
||||
// Добавить в core/client.go
|
||||
func (c *UniversalClient) doRequestWithRetry(ctx context.Context, ...) (*http.Response, error) {
|
||||
maxRetries := 3
|
||||
backoff := 2 * time.Second
|
||||
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||
resp, err := c.doRequest(ctx, ...)
|
||||
if err == nil && resp.StatusCode < 500 && resp.StatusCode != 429 {
|
||||
return resp, nil
|
||||
}
|
||||
if attempt < maxRetries {
|
||||
time.Sleep(backoff * time.Duration(1<<attempt)) // exponential
|
||||
}
|
||||
}
|
||||
return lastResp, lastErr
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P1-4: Polling с фиксированным интервалом 5с
|
||||
|
||||
**Где:** `instance_ops.go:81` — `time.NewTicker(5 * time.Second)`
|
||||
|
||||
**Проблема:**
|
||||
- Для быстрых операций (suspend ~10с) — 5с интервал нормально
|
||||
- Для долгих (create VM ~5мин) — 5с создаёт лишние API-запросы
|
||||
- Нет adaptive polling (увеличение интервала со временем)
|
||||
|
||||
**Решение:**
|
||||
```go
|
||||
// Adaptive polling: 3s → 5s → 10s → 15s → 30s (max)
|
||||
intervals := []time.Duration{3*time.Second, 5*time.Second, 10*time.Second, 15*time.Second, 30*time.Second}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P1-5: FindInstanceByDisplayName — неточный поиск
|
||||
|
||||
**Где:** `instance_lookup.go:29`
|
||||
|
||||
**Проблема:** Ищет подстрокой по `displayName`, нет exact match. Если есть "mydb" и "mydb-test", может вернуть неверный инстанс.
|
||||
|
||||
**Решение:** Добавить exact match фильтр после получения результатов:
|
||||
```go
|
||||
if instance.DisplayName == displayName { // exact match
|
||||
return instance, nil
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Средний приоритет (P2)
|
||||
|
||||
### P2-1: Дублирование client.go между legacy и universal_rebuild
|
||||
|
||||
Два файла `client.go`:
|
||||
- `internal/core/client.go` (1065 строк, V1–V6)
|
||||
- `universal_rebuild/internal/core/client.go` (1034 строк, V6 + refSvc)
|
||||
|
||||
**Решение:** Legacy provider постепенно заменяется universal_rebuild. Не рефакторить — просто фиксировать (freeze) legacy.
|
||||
|
||||
---
|
||||
|
||||
### P2-2: Update() не реализован в большинстве ресурсов
|
||||
|
||||
Сгенерированные ресурсы через `gen_v2` уже генерируют Update() если у сервиса есть `modify` операция. Ручные ресурсы (edge, vdc, org) — нет.
|
||||
|
||||
**Решение:** Для ручных ресурсов — оставить как есть (ForceNew), если modify не критичен. Для generated — уже работает.
|
||||
|
||||
---
|
||||
|
||||
### P2-3: Нет валидации YAML-спецификаций
|
||||
|
||||
Генератор `01_generate_yamls.sh` → YAML → `gen_v2` — нет промежуточной валидации YAML на корректность/полноту.
|
||||
|
||||
**Решение:** Добавить JSON Schema для YAML-спецификаций и валидировать перед генерацией.
|
||||
|
||||
---
|
||||
|
||||
### P2-4: Нормализация параметров — разные стратегии
|
||||
|
||||
| Версия | Стратегия normalization |
|
||||
|--------|------------------------|
|
||||
| V1-V2 | empty → empty |
|
||||
| V3-V4 | empty → `{}` (map) / `[]` (array) |
|
||||
| V5-V6 | TrimSpace + null-string + `\"\"` → empty |
|
||||
|
||||
**Решение:** Зафиксировать V6 behavior как единственный стандарт. Задокументировать.
|
||||
|
||||
---
|
||||
|
||||
### P2-5: Отсутствие structured logging
|
||||
|
||||
- `tflog` используется, но нет единого формата
|
||||
- Нет trace ID / correlation ID для chain запросов
|
||||
- Debug-лог в файл вместо terraform framework
|
||||
|
||||
**Решение:** Стандартизировать tflog с трейсами:
|
||||
```go
|
||||
tflog.Debug(ctx, "API request", map[string]interface{}{
|
||||
"method": "POST",
|
||||
"url": url,
|
||||
"instance_uid": uid,
|
||||
"operation": opName,
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Анализ по слоям
|
||||
|
||||
### 6.1 Provider Layer
|
||||
|
||||
| Аспект | Legacy | Universal Rebuild |
|
||||
|--------|--------|-------------------|
|
||||
| Auth | token/env/file ✅ | token/env/file ✅ |
|
||||
| TLS | InsecureSkipVerify 🔴 | InsecureSkipVerify 🔴 |
|
||||
| HTTP Timeout | 300s | Configurable ✅ |
|
||||
| Resources | 13 (ручные) | 50+ (генерируемые) |
|
||||
| Schema | Hand-written | Generated from YAML ✅ |
|
||||
|
||||
### 6.2 Resource Layer
|
||||
|
||||
**Legacy ресурсы (ручные):**
|
||||
- VM — лучший: полный CRUD, import, status polling, рабочие timeout'ы
|
||||
- Edge — хороший: multi-step create, operation discovery
|
||||
- VDC — хороший: triggers для пересоздания
|
||||
- Postgres, PgAdmin, Org — минимальные, часть Methods не реализованы
|
||||
|
||||
**Universal ресурсы (generated):**
|
||||
- Полный CRUD для instance если есть create/modify/suspend operations
|
||||
- Subresource (user, database) с ForceNew если нет modify
|
||||
- Action (restart, recovery) с trigger field `run_id`
|
||||
- Lifecycle flags: `suspend_on_destroy`, `adopt_existing_on_create` ✅
|
||||
- State refresh через `RefreshResourceState` ✅
|
||||
|
||||
### 6.3 Core Layer
|
||||
|
||||
**Сильные стороны:**
|
||||
- Единый API flow (V6): create → init op → send params → validate → run → wait
|
||||
- Timeout management с override per-service
|
||||
- RefSvc resolution (linked services)
|
||||
- Operation polling с dtFinish проверкой
|
||||
|
||||
**Слабые стороны:**
|
||||
- Нет retry / circuit breaker
|
||||
- Фиксированный polling interval
|
||||
- 6 версий Create в legacy (мусор)
|
||||
- Debug log в файл (hardcoded path)
|
||||
|
||||
### 6.4 CRUD Layer (`resources_core/`)
|
||||
|
||||
**Сильные стороны:**
|
||||
- `adoptExistingInstanceOnCreate()` — базовая adopt-логика
|
||||
- `RefreshResourceState` — generic state sync с type conversion
|
||||
- `RequiredParamsMismatch()` — сравнение params при adopt
|
||||
- `resource_diagnostics.go` — форматированные диагностики
|
||||
|
||||
**Слабые стороны:**
|
||||
- Decision matrix из philosophy § 7 не полностью реализована
|
||||
- Delete behavior: "state_only" по умолчанию если пустая строка — нарушает `suspend_on_destroy=true` default
|
||||
- Нет обработки статусов `creating`, `pending`, `failed`
|
||||
|
||||
---
|
||||
|
||||
## 7. Эволюция API-клиента
|
||||
|
||||
```
|
||||
V1 (simple)
|
||||
│ + server defaults
|
||||
V2 (Universal)
|
||||
│ + name/code hints для normalization
|
||||
V3 (UniversalV2)
|
||||
│ + label field
|
||||
V4 (UniversalV3)
|
||||
│ + TrimSpace, null → empty
|
||||
V5 (UniversalV4)
|
||||
│ + debug file logging, `\"\"` handling
|
||||
V6 (UniversalV5) ← PRODUCTION (legacy provider)
|
||||
│
|
||||
└─→ V6 Rebuilt ← PRODUCTION (universal_rebuild)
|
||||
+ refSvc resolution
|
||||
+ configurable timeouts
|
||||
+ ensureInstanceCreated()
|
||||
+ operation timeout per-service
|
||||
```
|
||||
|
||||
**Рекомендация:**
|
||||
- Legacy V1-V5 → пометить deprecated, не удалять (immutability policy)
|
||||
- В перспективе: legacy provider замораживается, весь development идёт в `universal_rebuild`
|
||||
|
||||
---
|
||||
|
||||
## 8. Генератор кода v2
|
||||
|
||||
### Архитектура генератора
|
||||
|
||||
```
|
||||
tools/gen_v2/generate_resources_v2.go (~2500 LOC)
|
||||
│
|
||||
├── loadSpecs() → Парсинг YAML per-service
|
||||
│ ├── Instance operations → GenResource
|
||||
│ ├── Subresource operations → GenSubresource
|
||||
│ └── Action operations → GenAction
|
||||
│
|
||||
├── writeInstanceResource() → Go template → *_resource.go
|
||||
├── writeSubresource() → Go template → *_subresource.go
|
||||
├── writeActionResource() → Go template → *_action.go
|
||||
└── writeRegistry() → registry.go (список всех ресурсов)
|
||||
```
|
||||
|
||||
### Что генерирует
|
||||
|
||||
Для каждого сервиса от 1 до 3 файлов:
|
||||
|
||||
1. **Instance Resource** (`90_postgres_resource.go`):
|
||||
- `Schema()` — из YAML params (create + modify)
|
||||
- `Create()` → `resources_core.CreateResourceWithTimeout()`
|
||||
- `Read()` → `resources_core.RefreshResourceState()`
|
||||
- `Update()` → `resources_core.UpdateResourceWithTimeout()` (если есть modify)
|
||||
- `Delete()` → `resources_core.DeleteResourceWithTimeout()`
|
||||
- `ImportState()` → passthrough ID
|
||||
|
||||
2. **Subresource** (`90_postgres_user_resource.go`):
|
||||
- ForceNew для всех params (если нет modify_user)
|
||||
- Create → `RunOperationByCode("create_user")`
|
||||
- Delete → `RunOperationByCode("delete_user")`
|
||||
|
||||
3. **Action** (`90_postgres_restart.go`):
|
||||
- Trigger field `run_id` (PlanModifier: UseStateForUnknown)
|
||||
- Create → `RunOperationByCode("restart")`
|
||||
|
||||
### Сильные стороны генератора
|
||||
|
||||
- ✅ Единый YAML → Go pipeline
|
||||
- ✅ Canonical lifecycle flags (`suspend_on_destroy`, `adopt_existing_on_create`)
|
||||
- ✅ RefSvc resolution для linked services
|
||||
- ✅ ForceNew detection для create-only params
|
||||
- ✅ Type mapping (string/bool/int64)
|
||||
- ✅ Auto-registry generation
|
||||
|
||||
### Слабые стороны
|
||||
|
||||
- ❌ Не генерирует `*_test.go`
|
||||
- ❌ Нет go fmt / go vet на generated output
|
||||
- ❌ Нет YAML schema validation перед генерацией
|
||||
- ❌ Нет diff-отчёта (что изменилось при регенерации)
|
||||
- ⚠️ Template embedded inline (не отдельные `.tmpl` файлы) — сложно поддерживать при росте
|
||||
|
||||
---
|
||||
|
||||
## 9. Соответствие provider_philosophy.md
|
||||
|
||||
### Матрица статусов (§7)
|
||||
|
||||
| Статус инстанса | adopt=false | adopt=true | Реализовано? |
|
||||
|-----------------|-------------|------------|--------------|
|
||||
| Not found / Deleted | Create | Create | ✅ |
|
||||
| Suspended | **Hard error** | Resume + Adopt | ⚠️ Частично |
|
||||
| Running | **Hard error** | Adopt (import) | ⚠️ Частично |
|
||||
| Not Created | **Hard error** | **Hard error** | ❌ Нет проверки |
|
||||
| Creating/Pending | **Hard error** | **Hard error** | ❌ Нет проверки |
|
||||
| Failed | **Hard error** | **Hard error** | ❌ Нет проверки |
|
||||
|
||||
### Destroy behavior (§7)
|
||||
|
||||
| Флаг | Действие | Реализовано? |
|
||||
|------|----------|--------------|
|
||||
| `suspend_on_destroy=true` (default) | Suspend | ✅ |
|
||||
| `suspend_on_destroy=false` | State-only | ✅ |
|
||||
|
||||
### Diagnostics format (§8)
|
||||
|
||||
**Требуется:**
|
||||
```
|
||||
resource_name: mydb
|
||||
service_id: 90
|
||||
instance_uid: abc-123
|
||||
status: running
|
||||
status_raw: Running
|
||||
flag: adopt_existing_on_create = false
|
||||
decision: Error — instance already exists
|
||||
action: Set adopt_existing_on_create = true or rename resource
|
||||
```
|
||||
|
||||
**Реализовано:** `resource_diagnostics.go` реализует multi-line format, но не все поля и не все кейсы.
|
||||
|
||||
### GAP Analysis
|
||||
|
||||
| Требование | Статус | Файл |
|
||||
|------------|--------|------|
|
||||
| Canonical flags in schema | ✅ | gen_v2 templates |
|
||||
| Decision matrix on Create | ⚠️ 60% | crud.go |
|
||||
| Hard error on "Not Created" | ❌ | crud.go |
|
||||
| Hard error on "Creating/Pending/Failed" | ❌ | crud.go |
|
||||
| Multi-line diagnostics | ⚠️ 70% | resource_diagnostics.go |
|
||||
| Param mismatch check on adopt | ✅ | required_params_compare.go |
|
||||
| Plan messages with cloud status | ❌ | Not implemented |
|
||||
|
||||
---
|
||||
|
||||
## 10. Тестирование
|
||||
|
||||
### Текущее состояние
|
||||
|
||||
**Тестовых файлов:** 0
|
||||
**Unit tests:** 0
|
||||
**Integration tests:** 0
|
||||
**Acceptance tests:** 0
|
||||
|
||||
### План тестирования
|
||||
|
||||
#### Фаза 1: Unit Tests для Core (приоритет — P0)
|
||||
|
||||
| Тест | Файл | Покрытие |
|
||||
|------|------|----------|
|
||||
| `TestNormalizeUniversalValue` | `core/client_test.go` | Нормализация всех типов |
|
||||
| `TestIsInstanceDeleted` | `core/instance_lookup_test.go` | Все статусы |
|
||||
| `TestIsStatusSuspended` | `core/crud_test.go` | Edge cases |
|
||||
| `TestAdoptLogicMatrix` | `core/crud_test.go` | Все комбинации status × adopt flag |
|
||||
| `TestParamsMismatch` | `core/params_compare_test.go` | Сравнение params |
|
||||
| `TestRefreshResourceState` | `core/state_refresh_test.go` | Type conversion |
|
||||
| `TestOperationTimeoutParsing` | `core/timeouts_test.go` | Config loading |
|
||||
| `TestResolveRefSvcParam` | `core/refsvc_test.go` | UUID ↔ display_name |
|
||||
|
||||
**Оценка:** ~40 test cases, ~800 LOC
|
||||
|
||||
#### Фаза 2: Integration Tests (приоритет — P1)
|
||||
|
||||
| Тест | Описание |
|
||||
|------|----------|
|
||||
| `TestCreateAndDeleteInstance` | Full lifecycle на test stand |
|
||||
| `TestAdoptExistingInstance` | Create → suspend → re-create with adopt=true |
|
||||
| `TestModifyInstance` | Create → modify → verify params changed |
|
||||
| `TestSubresourceLifecycle` | User create → delete |
|
||||
| `TestActionExecution` | Restart trigger |
|
||||
|
||||
**Требуется:** Test stand (dev profile) + test service (dummy/bolvanka)
|
||||
|
||||
#### Фаза 3: Acceptance Tests (приоритет — P2)
|
||||
|
||||
```bash
|
||||
TF_ACC=1 go test ./internal/... -v -run TestAcc
|
||||
```
|
||||
|
||||
С реальным Terraform CLI: plan → apply → verify → destroy.
|
||||
|
||||
---
|
||||
|
||||
## 11. Безопасность
|
||||
|
||||
### Текущие уязвимости
|
||||
|
||||
| # | Уязвимость | OWASP | Severity | Где |
|
||||
|---|-----------|-------|----------|-----|
|
||||
| S1 | InsecureSkipVerify=true | A07:Crypto Failures | CRITICAL | provider.go:103 |
|
||||
| S2 | Hardcoded debug log path | A05:Security Misconfig | HIGH | core/client.go:18 |
|
||||
| S3 | Нет input validation на API responses | A03:Injection | MEDIUM | core/client.go |
|
||||
| S4 | Token в памяти без rotation | A07:Auth Failures | MEDIUM | provider.go |
|
||||
| S5 | Нет rate limiting | A04:Insecure Design | LOW | core/client.go |
|
||||
| S6 | Secrets в репозитории (secrets/) | A05:Security Misconfig | HIGH | secrets/ |
|
||||
|
||||
### Рекомендации по безопасности
|
||||
|
||||
1. **S1:** `insecure` flag в provider schema (default: false), env var `NUBES_INSECURE`
|
||||
2. **S2:** Удалить файловый debug log, использовать только `tflog`
|
||||
3. **S3:** Валидировать JSON responses на ожидаемые поля
|
||||
4. **S6:** Перенести secrets в vault / CI variables, добавить в `.gitignore`
|
||||
|
||||
---
|
||||
|
||||
## 12. Дорожная карта (Roadmap)
|
||||
|
||||
### Фаза 0: Стабилизация (текущая — P0 fixes)
|
||||
|
||||
| # | Задача | Объём | Зависимости |
|
||||
|---|--------|-------|-------------|
|
||||
| 0.1 | Сделать InsecureSkipVerify конфигурируемым | 25 LOC | — |
|
||||
| 0.2 | Заменить hardcoded debug log на tflog/env var | 15 LOC | — |
|
||||
| 0.3 | Пометить V1–V5 CreateGenericInstance deprecated | Комментарии | — |
|
||||
| 0.4 | Unit tests для core layer (Фаза 1) | ~800 LOC | — |
|
||||
| 0.5 | Добавить secrets/ в .gitignore | 1 строка | — |
|
||||
|
||||
### Фаза 1: Compliance с provider_philosophy.md
|
||||
|
||||
| # | Задача | Объём | Зависимости |
|
||||
|---|--------|-------|-------------|
|
||||
| 1.1 | Полная decision matrix в crud.go | ~100 LOC | 0.4 |
|
||||
| 1.2 | Hard error для "Not Created", "Creating", "Failed" | ~50 LOC | 1.1 |
|
||||
| 1.3 | Multi-line diagnostics для всех кейсов | ~100 LOC | 1.1 |
|
||||
| 1.4 | Plan messages с cloud status | ~80 LOC | 1.1 |
|
||||
| 1.5 | Integration test для adopt matrix | ~200 LOC | 1.1 |
|
||||
|
||||
### Фаза 2: Надёжность
|
||||
|
||||
| # | Задача | Объём | Зависимости |
|
||||
|---|--------|-------|-------------|
|
||||
| 2.1 | Retry с exponential backoff для API-вызовов | ~80 LOC | — |
|
||||
| 2.2 | Adaptive polling intervals | ~40 LOC | — |
|
||||
| 2.3 | Exact match в FindInstanceByDisplayName | ~10 LOC | — |
|
||||
| 2.4 | YAML schema validation перед генерацией | ~200 LOC | — |
|
||||
| 2.5 | go fmt + go vet в pipeline генерации | ~10 LOC | — |
|
||||
|
||||
### Фаза 3: Масштабирование
|
||||
|
||||
| # | Задача | Объём | Зависимости |
|
||||
|---|--------|-------|-------------|
|
||||
| 3.1 | Генерация *_test.go в gen_v2 | ~500 LOC | 2.4 |
|
||||
| 3.2 | Diff-отчёт при регенерации | ~200 LOC | — |
|
||||
| 3.3 | Acceptance tests (TF_ACC) | ~500 LOC | 1.5 |
|
||||
| 3.4 | Structured logging с trace ID | ~150 LOC | — |
|
||||
| 3.5 | CI pipeline (build → test → publish) | Config | 3.1, 3.3 |
|
||||
|
||||
### Фаза 4: Production Hardening
|
||||
|
||||
| # | Задача | Объём | Зависимости |
|
||||
|---|--------|-------|-------------|
|
||||
| 4.1 | Заморозить legacy provider (internal/) | Процесс | 3.5 |
|
||||
| 4.2 | Миграция ручных ресурсов в universal_rebuild | Большая | 4.1 |
|
||||
| 4.3 | Мониторинг generation stability | Infra | 3.5 |
|
||||
| 4.4 | Нагрузочное тестирование (параллельный apply) | ~200 LOC | 3.3 |
|
||||
|
||||
---
|
||||
|
||||
## 13. Рекомендации по агенту/модели
|
||||
|
||||
### Выбор модели для разных задач
|
||||
|
||||
| Тип задачи | Рекомендуемый агент | Почему |
|
||||
|-------------|---------------------|--------|
|
||||
| **Архитектурные решения** | Claude Opus 4.6 (текущий) | Глубокий контекст, сложная логика |
|
||||
| **Отладка сложных багов** | Claude Opus 4.6 | Лучше держит контекст, видит неочевидные связи |
|
||||
| **Анализ codebase, code review** | Claude Opus 4.6 | Качество анализа выше |
|
||||
| **Генерация Go-кода по шаблонам** | Claude Sonnet 4.6 | Достаточно для шаблонного кода, экономичнее |
|
||||
| **Написание тестов** | Claude Sonnet 4.6 | Шаблонная работа |
|
||||
| **Массовые правки в генераторе** | Claude Sonnet 4.6 | Достаточно контекста в одном файле |
|
||||
| **Правки shell-скриптов** | Claude Sonnet 4.6 | Простые правки |
|
||||
| **Документация** | Claude Sonnet 4.6 | Текстовая генерация |
|
||||
| **Lifecycle state machine** | Claude Opus 4.6 | Сложные state transitions |
|
||||
| **API reverse-engineering** | Claude Opus 4.6 | Нужен глубокий анализ HAR/JSON |
|
||||
|
||||
### Стратегия переключения
|
||||
|
||||
1. **Планирование и design review** → Opus 4.6
|
||||
2. **Имплементация запланированного** → Sonnet 4.6
|
||||
3. **Баг не воспроизводится / непонятная причина** → Opus 4.6
|
||||
4. **Регенерация и routine CI** → Sonnet 4.6
|
||||
|
||||
### Практический совет
|
||||
|
||||
Для текущей фазы развития (стабилизация + compliance):
|
||||
- **Opus 4.6** для задач 1.1–1.4 (lifecycle decision matrix — сложная логика)
|
||||
- **Sonnet 4.6** для задач 0.1–0.5, 2.1–2.5, 3.1 (шаблонные правки, тесты)
|
||||
|
||||
---
|
||||
|
||||
## Приложение A: Ключевые файлы для изучения
|
||||
|
||||
| Приоритет | Файл | Зачем |
|
||||
|-----------|------|-------|
|
||||
| ★★★ | `docs/60_strategy/provider_philosophy.md` | Канон lifecycle-логики |
|
||||
| ★★★ | `universal_rebuild/internal/resources_core/crud.go` | CRUD + adopt-логика |
|
||||
| ★★★ | `universal_rebuild/internal/core/client.go` | API V6 flow |
|
||||
| ★★☆ | `universal_rebuild/tools/gen_v2/generate_resources_v2.go` | Генератор |
|
||||
| ★★☆ | `universal_rebuild/internal/resources_core/state_refresh.go` | State sync |
|
||||
| ★★☆ | `universal_rebuild/internal/resources_core/resource_diagnostics.go` | Диагностики |
|
||||
| ★☆☆ | `devops/ARCHITECTURE.md` | Build pipeline design |
|
||||
| ★☆☆ | `internal/core/client.go` | Legacy reference (V1–V6) |
|
||||
|
||||
## Приложение B: Команды для быстрого старта
|
||||
|
||||
```bash
|
||||
# Проверка компиляции (universal_rebuild)
|
||||
cd universal_rebuild && go build ./...
|
||||
|
||||
# Проверка компиляции (legacy)
|
||||
cd /home/naeel/remote_dev/terraform && go build ./...
|
||||
|
||||
# Генерация YAML из API
|
||||
cd devops && bash 01_generate_yamls.sh
|
||||
|
||||
# Генерация Go-кода из YAML
|
||||
cd devops && bash 02_generate_resources_and_docs_template_v2.sh
|
||||
|
||||
# Сборка провайдера
|
||||
cd devops && bash 03_build_and_upload_provider.sh
|
||||
|
||||
# Публикация документации
|
||||
cd devops && bash 04_build_and_publish_docs.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Документ сформирован автоматически на основе полного анализа кодовой базы. Обновлять при существенных архитектурных изменениях.*
|
||||
@@ -1,63 +0,0 @@
|
||||
# Отчет о проблеме: Ошибка 500 при получении cfsParams для vc_vdc и план исправления
|
||||
|
||||
## 1. Проблема
|
||||
При создании виртуального дата-центра `nubes_vc_vdc` (сервис 21 `vc_vdc`, операция создания 9) Terraform завершается с ошибкой клиента:
|
||||
```text
|
||||
не удалось получить детали операции: ошибка API 500: Invalid call of the function [getResourceRealmConfig], first Argument [resourceRealm] is of invalid type, Cannot cast Object type [Struct] to a value of type [string]: the function is located at [/app/api/v1/resources/instance_operation_cfs_param.cfc]
|
||||
```
|
||||
|
||||
## 2. Анализ причины
|
||||
1. **Место падения в провайдере**: `provider/internal/core/client.go` (строка 272 в `createInstanceWithContext`):
|
||||
```go
|
||||
opDetailsResp, _, err := c.doRequest(ctx, "GET", fmt.Sprintf("/instanceOperations/%s?fields=cfsParams", opUid), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("не удалось получить детали операции: %w", err)
|
||||
}
|
||||
```
|
||||
2. **Поведение бэкенда Nubes (Lucee/ColdFusion)**:
|
||||
- В файле `/app/api/v1/resources/instance_operation_cfs_param.cfc` при обработке запроса `?fields=cfsParams` для операции 9 вызывается функция `getResourceRealmConfig(resourceRealm)`.
|
||||
- Для сервиса `vc_vdc` поле `resourceRealm` в БД DEV-окружения хранится как комплексный объект (`Struct`), а не скалярная строка (`string`).
|
||||
- При попытке приведения типа `Struct -> string` бэкенд падает с HTTP 500.
|
||||
|
||||
3. **Сравнение с веб-интерфейсом (HAR/vdc.har)**:
|
||||
- В официальном веб-интерфейсе ЛК запрос `GET /instanceOperations/{opUid}?fields=cfsParams` **вообще не выполняется**.
|
||||
- Браузер выполняет строго следующий флоу:
|
||||
1. `POST /instanceOperations` -> получает `instanceOperationUid`
|
||||
2. `POST /instanceOperationCfsParams` -> отправляет каждое значение CFS-параметра
|
||||
3. `GET /instanceOperations/{opUid}/validate-cfs` -> валидация бэкендом
|
||||
4. `POST /instanceOperations/{opUid}/run` -> запуск операции в оркестраторе
|
||||
5. Polling `GET /instanceOperations/{opUid}` -> ожидание статуса завершения
|
||||
|
||||
4. **Зачем провайдер делает шаг 2**:
|
||||
- Шаг 2 в провайдере использовался исключительно для вызова `resolveRefSvcParamValues(ctx, opDetails.InstanceOperation.CfsParams, params)` — чтобы узнать `refSvcId` параметров и попробовать отрезолвить имена в UUID.
|
||||
- Для ресурса `nubes_vc_vdc` параметр `organization_uid` (CFS param 30, refSvc 19) **уже гарантированно отрезолвлен в UUID** до создания операции (на этапе `ModifyPlan` и в начале `Create`).
|
||||
- Остальные параметры `vc_vdc` (провайдер сети, профиль Provider VDC, CPU, RAM, резервирование, storage_config) являются скалярами/числами/JSON-строками и не содержат `refSvcId`.
|
||||
- Соответственно, данные запроса `?fields=cfsParams` для `vc_vdc` фактически не требуются.
|
||||
|
||||
## 3. План изменений в провайдере (что будем менять)
|
||||
|
||||
### Целевой файл: `provider/internal/core/client.go`
|
||||
В функции `createInstanceWithContext` (и при необходимости в `runInstanceOperationUniversalByCodeWithTimeout` / `updateResourceWithTimeout`) заменяется жёсткое падение на условный graceful fallback:
|
||||
|
||||
```go
|
||||
opDetailsResp, _, err := c.doRequest(ctx, "GET", fmt.Sprintf("/instanceOperations/%s?fields=cfsParams", opUid), nil)
|
||||
if err != nil {
|
||||
// Проверяем, есть ли среди переданных строковых параметров не-UUID значения,
|
||||
// требующие резолвинга через refSvcId.
|
||||
// Если все строковые параметры уже UUID или числа/литералы — логируем предупреждение и продолжаем.
|
||||
c.logWarn(ctx, "не удалось получить cfsParams для операции %s (%v), продолжаем отправку параметров", opUid, err)
|
||||
} else {
|
||||
var opDetails universalOpResponse
|
||||
if err := json.Unmarshal(opDetailsResp, &opDetails); err == nil {
|
||||
params, err = c.resolveRefSvcParamValues(ctx, opDetails.InstanceOperation.CfsParams, params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Критерии безопасности:
|
||||
1. Fallback условный: если бэкенд упал, но параметры уже валидны / являются UUID — выполнение продолжается.
|
||||
2. Логируется явный warning с идентификатором операции `opUid` и телом ошибки.
|
||||
3. Валидация значений параметров не теряется — её по-прежнему выполняет бэкенд на этапе `GET /validate-cfs`.
|
||||
@@ -1,62 +0,0 @@
|
||||
# Отчет о работе: Исправление ошибки 500 при модификации VM
|
||||
|
||||
## 1. Проблема
|
||||
При попытке изменить ресурс `nubes_vm_instance` (например, изменить `vm_cpu`), Terraform получает ошибку 500 от API:
|
||||
```
|
||||
Status 500: {"ERROR":"Invalid call of the function [checkParam], 4th Argument [instanceOperationCfsParamUid] is of invalid type, Cannot cast String [] to a value of type [guid]","DETAIL":"the function is located at [/app/api/v1/resources/instance_operation_cfs_param_ls.cfc]"}
|
||||
```
|
||||
|
||||
## 2. Анализ причины
|
||||
Сообщение `Cannot cast String [] to a value of type [guid]` указывает на то, что бэкенд получает массив строк там, где ожидает одиночный GUID. Обычно это происходит в REST API, когда клиент отправляет несколько значений для одного ключа, или когда отправляется `POST` запрос для создания сущности, которая уже существует, и система дублирует параметр в список.
|
||||
|
||||
В отличие от создания (Create), операция модификации (Modify/Update) в Nubes API при инициализации (`GET /instanceOperations/...`) уже содержит текущие значения параметров (`CfsParams`).
|
||||
|
||||
Если мы безусловно используем метод `POST` для отправки параметров (как это было сделано в ресурсе Postgres), мы создаем дубликат параметра. По всей видимости, движок API (ColdFusion?) объединяет старое и новое значение в массив, что ломает валидацию `checkParam`.
|
||||
|
||||
## 3. Выполненные действия (Solution Attempt)
|
||||
|
||||
Я модифицировал файл `internal/provider/vm_resource.go`, полностью переписав функцию `submitVMOperationParams`.
|
||||
|
||||
**Суть изменений:**
|
||||
1. **Динамический маппинг**: Перед отправкой параметров провайдер теперь запрашивает детали операции (`GetInstanceOperation`).
|
||||
2. **Определение UID**: Мы строим карту существующих параметров: `ParamName -> { ID, UID, CurrentValue }`.
|
||||
3. **Гибридная логика PUT/POST**:
|
||||
* Если параметр **уже существует** в операции (есть `instanceOperationCfsParamUid`) -> Мы используем метод **`PUT`**.
|
||||
* URL: `/instanceOperationCfsParams`
|
||||
* Payload: включает `instanceOperationCfsParamUid`.
|
||||
* Если параметр **новый** (нет UID) -> Мы используем метод **`POST`**.
|
||||
* URL: `/instanceOperationCfsParams`
|
||||
* Payload: включает только `instanceOperationUid` и `svcOperationCfsParamId`.
|
||||
|
||||
### Фрагмент кода (internal/provider/vm_resource.go):
|
||||
```go
|
||||
if info.Uid != "" {
|
||||
// Update existing parameter -> PUT
|
||||
method = "PUT"
|
||||
payload = map[string]interface{}{
|
||||
"instanceOperationCfsParamUid": info.Uid,
|
||||
"svcOperationCfsParamId": info.Id,
|
||||
"instanceOperationUid": operationUid,
|
||||
"paramValue": np.Value,
|
||||
}
|
||||
} else {
|
||||
// Create new parameter -> POST
|
||||
method = "POST"
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Текущий статус
|
||||
Код компилируется и выполняется. Был обновлен провайдер (`terraform-provider-nubes`).
|
||||
Однако, при последнем запуске (`terraform apply`) ошибка 500 воспроизвелась снова с тем же текстом.
|
||||
|
||||
Это означает, что либо:
|
||||
1. Логика `PUT` тоже вызывает дублирование (возможно, API не поддерживает PUT для этого эндпоинта так, как мы ожидаем).
|
||||
2. Или ошибка `instanceOperationCfsParamUid is String[]` возникает из-за того, что я передаю этот UID в теле JSON, но API может ожидать его в URL или query params (хотя для PUT принято в теле или URL).
|
||||
|
||||
## 5. Планируемые дальнейшие шаги (остановлены)
|
||||
Для полного решения требовалось бы:
|
||||
1. Проверить работу `PUT` через curl с разными форматами payload.
|
||||
2. Попробовать передавать UID параметра в рамках URL (REST-style): `/instanceOperationCfsParams/{uid}`.
|
||||
|
||||
**Статус:** Работа остановлена по требованию пользователя. Код зафиксирован в текущем состоянии (Гибридный PUT/POST).
|
||||
@@ -1,217 +0,0 @@
|
||||
# Forensic Analysis: API Model to Ordinary YAML
|
||||
|
||||
## Цель
|
||||
|
||||
Исследовать существующую цепочку:
|
||||
|
||||
```text
|
||||
API model
|
||||
↓
|
||||
ordinary generator
|
||||
↓
|
||||
API YAML
|
||||
```
|
||||
|
||||
Цель этапа — получить подтверждённую картину движения данных и установить, что сохраняется, преобразуется или теряется до формирования API YAML.
|
||||
|
||||
Этот документ предназначен для передачи Opus перед анализом.
|
||||
|
||||
## Строгие ограничения
|
||||
|
||||
На этом этапе запрещено:
|
||||
|
||||
- изменять файлы;
|
||||
- писать код;
|
||||
- менять ordinary generator;
|
||||
- проектировать `ModifierSpec`;
|
||||
- проектировать `modifiers.yaml`;
|
||||
- проектировать `delete_rule`;
|
||||
- проектировать output layout или orchestration;
|
||||
- обсуждать inverse и dependency ordering;
|
||||
- придумывать API identifiers;
|
||||
- придумывать `parameter_path`;
|
||||
- придумывать HTTP method или payload structure;
|
||||
- считать API YAML полным источником данных без доказательства.
|
||||
|
||||
Если факт невозможно установить, его нужно обозначить как `unknown` или как требующий проверки фактическим API. Нельзя закрывать неизвестность архитектурным предположением.
|
||||
|
||||
## Что исследовать
|
||||
|
||||
### 1. Исходная API-модель
|
||||
|
||||
Установить:
|
||||
|
||||
- где находится canonical API model;
|
||||
- в каком формате она представлена;
|
||||
- как представлены services и operations;
|
||||
- как представлены параметры;
|
||||
- как представлены nested objects и arrays;
|
||||
- как представлены типы параметров;
|
||||
- существует ли стабильный operation ID;
|
||||
- существует ли стабильный parameter ID;
|
||||
- какие данные доступны до запуска ordinary generator.
|
||||
|
||||
### 2. Ordinary generator
|
||||
|
||||
Установить:
|
||||
|
||||
- какой input получает generator;
|
||||
- где он читает API-модель;
|
||||
- какие внутренние структуры строит;
|
||||
- какие преобразования выполняет;
|
||||
- какие поля нормализует или переименовывает;
|
||||
- какие поля вычисляет;
|
||||
- какие поля отбрасывает;
|
||||
- где формируется API YAML;
|
||||
- где именно могут происходить потери данных.
|
||||
|
||||
Обязательно различать:
|
||||
|
||||
```text
|
||||
данные отсутствуют уже в API
|
||||
```
|
||||
|
||||
и:
|
||||
|
||||
```text
|
||||
данные присутствуют в API, но теряются ordinary generator
|
||||
```
|
||||
|
||||
### 3. API YAML
|
||||
|
||||
Установить:
|
||||
|
||||
- какие поля сохраняются;
|
||||
- какие поля представлены иначе, чем в API-модели;
|
||||
- сохраняются ли nested objects и arrays;
|
||||
- сохраняются ли типы;
|
||||
- сохраняются ли operation identity и parameter identity;
|
||||
- сохраняются ли HTTP method и path, если они есть в исходной модели;
|
||||
- какие данные доступны будущему modifier layer;
|
||||
- какие данные потенциально доступны только в исходной API-модели.
|
||||
|
||||
## Обязательные трассировки
|
||||
|
||||
### `vc_org`
|
||||
|
||||
Отдельно проследить `vIPConfigure` и `count`:
|
||||
|
||||
```text
|
||||
API model
|
||||
→ generator input
|
||||
→ internal generator representation
|
||||
→ generator transformation
|
||||
→ API YAML
|
||||
```
|
||||
|
||||
Для каждого этапа указать:
|
||||
|
||||
- присутствует ли `vIPConfigure`;
|
||||
- присутствует ли `count`;
|
||||
- в каком типе они представлены;
|
||||
- в какой структуре находятся;
|
||||
- изменяются ли их имена или типы;
|
||||
- теряются ли они;
|
||||
- если теряются, в какой точке.
|
||||
|
||||
Не считать заранее известной структуру `vIPConfigure` или `count`.
|
||||
|
||||
### `vc_nsxt`
|
||||
|
||||
Отдельно проследить `ipSpaceName` и связанные параметры по той же цепочке:
|
||||
|
||||
```text
|
||||
API model
|
||||
→ generator input
|
||||
→ internal generator representation
|
||||
→ generator transformation
|
||||
→ API YAML
|
||||
```
|
||||
|
||||
Установить:
|
||||
|
||||
- где появляется `ipSpaceName`;
|
||||
- к какой operation или структуре он относится;
|
||||
- в каком типе представлен;
|
||||
- является ли обычным полем, nested field или частью массива;
|
||||
- какие связанные параметры находятся рядом;
|
||||
- сохраняется ли он в API YAML;
|
||||
- изменяются ли его значение или тип;
|
||||
- теряются ли связанные поля.
|
||||
|
||||
## Требования к доказательности
|
||||
|
||||
Каждый вывод разделять на:
|
||||
|
||||
- **Подтверждённый факт** — непосредственно виден из кода, структуры данных, фактического API input, generator input или API YAML.
|
||||
- **Неизвестное** — информация отсутствует или неоднозначна.
|
||||
- **Предположение** — не использовать как основание для архитектуры; только явно перечислять как неподтверждённое.
|
||||
|
||||
Для каждого важного вывода указывать конкретное основание: файл, функцию, структуру, входной документ или фрагмент YAML/JSON. Если точное место невозможно назвать, это указать как ограничение анализа.
|
||||
|
||||
## Формат итогового отчёта
|
||||
|
||||
Отчёт должен содержать только следующие разделы:
|
||||
|
||||
1. **Подтверждённые факты**
|
||||
2. **Исходная API-модель**
|
||||
3. **Как ordinary generator читает модель**
|
||||
4. **Как формируется API YAML**
|
||||
5. **Цепочка данных для `vc_org.vIPConfigure.count`**
|
||||
6. **Цепочка данных для `vc_nsxt.ipSpaceName` и связанных параметров**
|
||||
7. **Что сохраняется**
|
||||
8. **Что преобразуется или нормализуется**
|
||||
9. **Что теряется**
|
||||
10. **Где происходят потери**
|
||||
11. **Какие данные доступны в API YAML**
|
||||
12. **Какие данные доступны только в API-модели**
|
||||
13. **Неизвестные места**
|
||||
14. **Что необходимо проверить фактическим API**
|
||||
|
||||
## Критерий завершения
|
||||
|
||||
Forensic analysis завершён только тогда, когда для каждого потенциально необходимого modifier-элемента можно проследить происхождение:
|
||||
|
||||
```text
|
||||
API model
|
||||
→ generator input
|
||||
→ internal representation
|
||||
→ transformation
|
||||
→ API YAML
|
||||
```
|
||||
|
||||
без неизвестных промежуточных преобразований.
|
||||
|
||||
Для `vc_org` и `vc_nsxt` должны быть подтверждены:
|
||||
|
||||
```text
|
||||
operation identity
|
||||
parameter identity
|
||||
parameter structure
|
||||
parameter type
|
||||
API model representation
|
||||
generator representation
|
||||
YAML representation
|
||||
transformation
|
||||
loss or absence
|
||||
```
|
||||
|
||||
Если на существенном этапе остаётся `???`, исследование не завершено. Этот пункт нужно зафиксировать как `unknown`, а не проектировать решение.
|
||||
|
||||
## Главный принцип
|
||||
|
||||
Сначала:
|
||||
|
||||
```text
|
||||
исследовать
|
||||
↓
|
||||
зафиксировать факты
|
||||
↓
|
||||
зафиксировать неизвестное
|
||||
↓
|
||||
отличить отсутствие данных от потери данных
|
||||
```
|
||||
|
||||
Только после отдельного согласования forensic report можно переходить к проектированию `ModifierSpec`, `modifiers.yaml`, `delete_rule`, modifier generator, output boundary и orchestration.
|
||||
|
||||
На текущем этапе никаких решений по этим компонентам принимать нельзя.
|
||||
@@ -1,234 +0,0 @@
|
||||
# Forensic Analysis: API Model to Ordinary YAML
|
||||
|
||||
**Анализ от Gemini 3.8 flash.**
|
||||
|
||||
Исследование проведено строго в границах требований [docs/FORENSIC_ANALYSIS_BRIEF_2026-09-23.md](FORENSIC_ANALYSIS_BRIEF_2026-09-23.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Подтверждённые факты
|
||||
|
||||
- **Цепочка генерации YAML:** Инструмент `yaml-generator` ([TOOLS/yaml-generator/main.go](../TOOLS/yaml-generator/main.go)) опрашивает live HTTP REST Gateway API Nubes по сети и сериализует результат в YAML-файлы спецификаций (`generated/{stand}/resources_yaml/{service_id}_{name}.yaml`), используя структуры контракта [TOOLS/lib/types.go](../TOOLS/lib/types.go).
|
||||
- **Спецификации сервисов в репозитории:** Канонические сгенерированные спеки сервисов физически размещены в [generated/dev/resources_yaml/](../generated/dev/resources_yaml/). В частности, [19_vc_org.yaml](../generated/dev/resources_yaml/19_vc_org.yaml) и [22_vc_nsxt.yaml](../generated/dev/resources_yaml/22_vc_nsxt.yaml).
|
||||
- **В `vc_org.yaml`:**
|
||||
- Операция `modify` имеет числовой ID `207`, `kind: instance`, `action: modify`.
|
||||
- Параметр `vIPConfigure` присутствует как параметр операции `modify` с числовым ID `662`, типом `data_type: array-map-fixed`, `required: true`.
|
||||
- Поле `count` присутствует внутри `sub_params` параметра `vIPConfigure` с числовым ID `40`, `data_type: integer > 0`, `required: true`, `is_modifiable: false`.
|
||||
- Поле `name` присутствует внутри `sub_params` параметра `vIPConfigure` с числовым ID `39`, `data_type: string`, `required: true`, `is_modifiable: false`.
|
||||
- **В `vc_nsxt.yaml`:**
|
||||
- Операция `modify` имеет числовой ID `111`, `kind: instance`, `action: modify`.
|
||||
- Параметр `ipSpaceName` присутствует в операции `modify` с числовым ID `372`, `data_type: string`, `required: false`, `sort: 50`.
|
||||
- С ним рядом в операции `modify` присутствуют:
|
||||
- `needEnableAVI` (ID `368`, `data_type: boolean`, `required: false`, `value_list: ["false", "true"]`);
|
||||
- `virtualServicesCount` (ID `369`, `data_type: integer > 0`, `required: false`, `minvalue: 1`, `maxvalue: 4`);
|
||||
- `qosProfile` (ID `856`, `data_type: string`, `required: false`);
|
||||
- `routedNetConfiguration` (ID `1112`, `data_type: map-fixed`, `required: true`, с вложенными подполями `ipAddrPool`, `mainDns`, `secondDns`).
|
||||
- **Генератор ресурсов (Ordinary Resource Generator):**
|
||||
- Расположен в [TOOLS/resource-generator/main.go](../TOOLS/resource-generator/main.go).
|
||||
- При `op.Kind == "instance"` (что установлено для `modify` в [generated/dev/resources_yaml/19_vc_org.yaml](../generated/dev/resources_yaml/19_vc_org.yaml) и [generated/dev/resources_yaml/22_vc_nsxt.yaml](../generated/dev/resources_yaml/22_vc_nsxt.yaml)) генератор объединяет параметры `create` и `modify` в схему одного общего ресурса `nubes_vc_org` / `nubes_vc_nsxt`.
|
||||
- Параметры, отсутствующие в операции `create`, но присутствующие в операции `modify` (как `vIPConfigure` в `vc_org`), не включаются в жизненный цикл `create`, а при отсутствии отдельной разметки `kind: modifier` в YAML генератор не создаёт под них отдельного ресурса модификатора.
|
||||
|
||||
---
|
||||
|
||||
## 2. Исходная API-модель
|
||||
|
||||
- **Источник и формат:** Модель получается HTTP-клиентом ([TOOLS/yaml-generator/internal/client/client.go](../TOOLS/yaml-generator/internal/client/client.go#L44-L62)) по протоколу HTTP GET в формате JSON.
|
||||
- **Эндпоинты API:**
|
||||
- Метаданные сервиса и список операций: `GET /services/{svcId}` (возвращает `types.ServiceResponse`).
|
||||
- Метаданные конкретной операции: `GET /instanceOperations/default/{svcOperationId}` (возвращает `types.ServiceOperationResponse`).
|
||||
- **Идентификаторы операций и параметров:**
|
||||
- Операция идентифицируется стабильным числовым `svcOperationId` (int) и строковым именем `operation` (например, `"modify"`, `"create"`).
|
||||
- Параметры идентифицируются стабильным числовым `svcOperationCfsParamId` (int) и строковым кодом `svcOperationCfsParam` (например, `"vIPConfigure"`, `"ipSpaceName"`).
|
||||
- **Вложенные объекты и массивы (`dataDescriptor`):**
|
||||
- В ответе эндпоинта `/instanceOperations/default/{id}` сложная структура передаётся в поле `dataDescriptor: map[string]CfsSubParam`.
|
||||
- Каждое подполе имеет свой числовой `svcOperationCfsSubparamId`, строковый ключ (код подполя), `dataType`, `isRequired`, `isModifiableDefinition`, `defaultValue`, `valueList` (строка через запятую или массив).
|
||||
|
||||
---
|
||||
|
||||
## 3. Как ordinary generator читает модель
|
||||
|
||||
- **Входной поток:**
|
||||
`yaml-generator` вызывает `cli.GetService(svc.ID)` и перебирает список `info.Operations`.
|
||||
- **Чтение операций и параметров:**
|
||||
Для каждой операции вызывается `cli.GetServiceOperation(op.SvcOperationID)` ([TOOLS/yaml-generator/internal/client/client.go](../TOOLS/yaml-generator/internal/client/client.go#L182-L240)).
|
||||
- **Преобразования и нормализация:**
|
||||
- Имена сервисов и операций нормализуются в snake_case функцией `normalize.Identifier` ([TOOLS/yaml-generator/internal/normalize/normalize.go](../TOOLS/yaml-generator/internal/normalize/normalize.go#L17-L50)).
|
||||
- Классификация операции: функция `classifyOperation` делит операции на `instance` (для `create`, `modify`, `delete`, `suspend`, `resume`), `subresource` (если есть символ подчеркивания) или `action`.
|
||||
- Разворачивание `dataDescriptor`: генератор обходит `map[string]CfsSubParam`, преобразует подполя в срез `types.ParamSpec` и детерминированно сортирует по `subParams[i].ID`.
|
||||
- Сортировка верхнеуровневых параметров по `params[i].ID`.
|
||||
- Сортировка операций по имени и ID.
|
||||
- **Что отбрасывается / не сохраняется в YAML:**
|
||||
- Конкретные HTTP method и URL-пути эндпоинтов API платформы (они зашиты в код клиента, в спек YAML не пишутся).
|
||||
- Вспомогательные поля `CfsParam`, не имеющие тега `yaml:` в [TOOLS/lib/types.go](../TOOLS/lib/types.go#L70-L101), если они не сериализуются или приходят пустыми: `IsModifiable`, `IsSensitive` (указаны с `omitempty`). Поле `dataDescriptor` как мапа отбрасывается — сохраняется преобразованный срез `sub_params`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Как формируется API YAML
|
||||
|
||||
- Сериализация структуры `types.ServiceSpec` в YAML выполняется через библиотеку `gopkg.in/yaml.v3` ([TOOLS/yaml-generator/main.go](../TOOLS/yaml-generator/main.go#L107-L114)).
|
||||
- В результирующий файл пишутся:
|
||||
- Метаданные сервиса (`name`, `service_id`, `service_display_name`, `service_short_name`, `service_man`).
|
||||
- Стандартная секция `lifecycle` и `outputs`.
|
||||
- Секция `operations` со списком операций и всеми их параметрами (`id`, `code`, `data_type`, `required`, `default`, `value_list`, `descr`, `man`, `sort`, `sub_params`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Цепочка данных для `vc_org.vIPConfigure.count`
|
||||
|
||||
1. **API Model:**
|
||||
- Эндпоинт `/instanceOperations/default/207` возвращает параметр с `svcOperationCfsParamId: 662`, `svcOperationCfsParam: "vIPConfigure"`, `dataType: "array-map-fixed"`.
|
||||
- Внутри него поле `dataDescriptor` содержит ключ `"count"`:
|
||||
- `svcOperationCfsSubparamId: 40`,
|
||||
- `dataType: "integer > 0"`,
|
||||
- `isRequired: true`,
|
||||
- `defaultValue: ""`,
|
||||
- `descr: "Пример: \`1\`"`.
|
||||
2. **Generator Input:**
|
||||
- Читается в структуру `types.CfsParam` с мапой `DataDescriptor map[string]CfsSubParam` ([TOOLS/yaml-generator/internal/types/types.go](../TOOLS/yaml-generator/internal/types/types.go#L49-L72)).
|
||||
3. **Internal Generator Representation:**
|
||||
- В [TOOLS/yaml-generator/internal/client/client.go](../TOOLS/yaml-generator/internal/client/client.go#L210-L232) мапа `dataDescriptor` разворачивается в `types.ParamSpec.SubParams`. Поле `count` становится элементом среза `SubParams` с `ID: 40`, `Code: "count"`, `DataType: "integer > 0"`.
|
||||
4. **Generator Transformation:**
|
||||
- Сортируется по ID (`sort.Slice(subParams, ...)`).
|
||||
5. **API YAML:**
|
||||
- Записывается в [generated/dev/resources_yaml/19_vc_org.yaml](../generated/dev/resources_yaml/19_vc_org.yaml#L131-L150):
|
||||
```yaml
|
||||
- id: 662
|
||||
code: vIPConfigure
|
||||
data_type: array-map-fixed
|
||||
required: true
|
||||
sort: 10
|
||||
sub_params:
|
||||
- id: 39
|
||||
code: name
|
||||
data_type: string
|
||||
required: true
|
||||
default: ""
|
||||
is_modifiable: false
|
||||
- id: 40
|
||||
code: count
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
default: ""
|
||||
descr: 'Пример: `1`'
|
||||
is_modifiable: false
|
||||
```
|
||||
- **Потерь в YAML нет:** `vIPConfigure` и `count` полностью и без искажений сохранены в каноническом YAML спецификации.
|
||||
|
||||
---
|
||||
|
||||
## 6. Цепочка данных для `vc_nsxt.ipSpaceName` и связанных параметров
|
||||
|
||||
1. **API Model:**
|
||||
- Эндпоинт `/instanceOperations/default/111` возвращает операцию `modify` сервиса 22.
|
||||
- Параметр `ipSpaceName` возвращается с:
|
||||
- `svcOperationCfsParamId: 372`,
|
||||
- `svcOperationCfsParam: "ipSpaceName"`,
|
||||
- `dataType: "string"`,
|
||||
- `isRequired: false`,
|
||||
- `descr: "Имя ip Space для внешнего IP"`,
|
||||
- `man: "Необходимо указывать, если включён параметр \`Выделить VIP для SNAT\`"`,
|
||||
- `sort: 50`.
|
||||
- В HAR-дампах ([HAR/edge_.har](../HAR/edge_.har#L7985)) на живом инстансе в рантайме возвращается `valueList: ["no-needed", ...]`.
|
||||
2. **Generator Input:**
|
||||
- Читается в структуру `types.CfsParam` ([TOOLS/yaml-generator/internal/types/types.go](../TOOLS/yaml-generator/internal/types/types.go#L49-L72)).
|
||||
3. **Internal Generator Representation:**
|
||||
- Преобразуется в `types.ParamSpec` со значениями `ID: 372`, `Code: "ipSpaceName"`, `DataType: "string"`.
|
||||
4. **Generator Transformation:**
|
||||
- Нормализуются defaults и value_list через `normalizeDefault` и `normalizeValueList`.
|
||||
5. **API YAML:**
|
||||
- Записывается в [generated/dev/resources_yaml/22_vc_nsxt.yaml](../generated/dev/resources_yaml/22_vc_nsxt.yaml#L149-L155):
|
||||
```yaml
|
||||
- id: 372
|
||||
code: ipSpaceName
|
||||
data_type: string
|
||||
required: false
|
||||
descr: Имя ip Space для внешнего IP
|
||||
man: Необходимо указывать, если включён параметр `Выделить VIP для SNAT`
|
||||
sort: 50
|
||||
```
|
||||
- Рядом в той же операции сохранены: `needEnableAVI` (ID 368), `virtualServicesCount` (ID 369), `qosProfile` (ID 856), `routedNetConfiguration` (ID 1112 с sub_params).
|
||||
- **Особенность по `value_list`:** в статическом `22_vc_nsxt.yaml` поле `value_list` для `ipSpaceName` отсутствует (`null` в ответе static-дефолтов эндпоинта `/instanceOperations/default/111`), хотя в рантайме на конкретном инстансе `valueList` динамически содержит `["no-needed", ...]`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Что сохраняется
|
||||
|
||||
- Полная идентичность сущностей: `service_id`, `svcOperationId` (как `id` операции), `svcOperationCfsParamId` (как `id` параметра), `svcOperationCfsSubparamId` (как `id` в `sub_params`).
|
||||
- Строковые коды: `operation`, коды параметров (`code`).
|
||||
- Исходные типы платформы: `dataType` (`string`, `boolean`, `integer > 0`, `array-map-fixed`, `map-fixed`).
|
||||
- Вся структура вложенности (`dataDescriptor` → `sub_params`).
|
||||
- Флаги `required`, валидационные regex, min/max, описания (`descr`, `man`), порядок (`sort`).
|
||||
|
||||
---
|
||||
|
||||
## 8. Что преобразуется или нормализуется
|
||||
|
||||
- Имена операций и сервисов приводятся к ASCII snake_case через `normalize.Identifier`.
|
||||
- `dataDescriptor` из мапы ключей преобразуется в упорядоченный срез `sub_params` с сортировкой по числовому `id`.
|
||||
- Значения `valueList` и `default` приводятся к строковым представлениям (убираются пробелы, пустые значения приводятся к `nil`).
|
||||
|
||||
---
|
||||
|
||||
## 9. Что теряется
|
||||
|
||||
- HTTP-метод и путь обращения к API (в YAML отсутствуют; генератор считает их внешним знанием рантайма).
|
||||
- Динамические значения списков выбора (`valueList`): эндпоинт дефолтов `/instanceOperations/default/{id}` возвращает пустой `valueList` для полей, зависящих от конкретного тенанта/инстанса (например, доступные `ipSpaceName` для конкретного VDC/Org).
|
||||
|
||||
---
|
||||
|
||||
## 10. Где происходят потери
|
||||
|
||||
- Потери HTTP-метаданных (метод, URL) происходят на этапе маршалинга структуры `types.ServiceSpec` в YAML ([TOOLS/yaml-generator/main.go](../TOOLS/yaml-generator/main.go#L94-L105)), так как они изначально отсутствуют в контракте [TOOLS/lib/types.go](../TOOLS/lib/types.go).
|
||||
- Отсутствие runtime `valueList` обусловлено вызовом шаблонного эндпоинта `/instanceOperations/default/{id}` вместо запроса контекста живого инстанса.
|
||||
|
||||
---
|
||||
|
||||
## 11. Какие данные доступны в API YAML
|
||||
|
||||
- Полный перечень всех сервисов, операций (`create`, `modify`, `delete`, `suspend`, `resume`, сабресурсов) и их параметров.
|
||||
- Точные типы платформы (`data_type`) и иерархия подполей (`sub_params`).
|
||||
- Идентификаторы `id` (CFS param IDs) и символические коды (`code`).
|
||||
- Метаданные валидации (обязательность, регулярные выражения, ограничения диапазонов).
|
||||
|
||||
---
|
||||
|
||||
## 12. Какие данные доступны только в API-модели
|
||||
|
||||
- Динамические списки допустимых значений (`valueList`), вычисляемые бэкендом для конкретного состояния конкретного инстанса (например, список реально существующих ipSpaces организации при вызове modify на Edge).
|
||||
- Внутренние служебные поля платформы CFS, отфильтрованные моделью генератора (`contractId`, `contragentId`, `nestedRefData`, `config`, `statePath`, `expression`).
|
||||
|
||||
---
|
||||
|
||||
## 13. Неизвестные места
|
||||
|
||||
- Неизвестно, возвращает ли платформа Nubes какую-либо схему валидации для эндпоинтов отката/деаллокации (например, принимает ли `vc_org.modify` пустой массив `vIPConfigure: []` для полного снятия или требует только уменьшения `count: 0`), так как в дефолтной модели операции 207 описан только общий формат `vIPConfigure`.
|
||||
|
||||
---
|
||||
|
||||
## 14. Что необходимо проверить фактическим API
|
||||
|
||||
- Поведение `vc_org` (операция 207) при передаче `vIPConfigure: []` против `vIPConfigure: [{"name": "...", "count": 0}]` при попытке полной деаллокации IP-пространства.
|
||||
- Поведение `vc_nsxt` (операция 111) при передаче `ipSpaceName: "no-needed"` на различных окружениях (Dev/Test/Prod).
|
||||
|
||||
---
|
||||
|
||||
## Мнение Opus: финальное ревью отчёта
|
||||
|
||||
Отчёт признан годным и принят как вход для дальнейшего архитектурного этапа.
|
||||
|
||||
### Достаточно для дальнейшей работы
|
||||
|
||||
- Цепочка `API model → yaml-generator → API YAML` прослежена по коду, а не по догадкам: `classifyOperation`, `params.Merge`, разворачивание `dataDescriptor` в `sub_params`.
|
||||
- Обе обязательные трассировки (`vc_org.vIPConfigure.count`, `vc_nsxt.ipSpaceName`) доведены до YAML с подтверждением «потерь нет».
|
||||
- Зафиксирован ключевой факт: динамический `valueList` есть только в рантайме живого инстанса, а в API YAML его нет. Это прямое ограничение для будущего modifier-слоя.
|
||||
- Неизвестное поведение payload деаллокации (`vIPConfigure: []` против `count: 0`) помечено как `unknown`, а не закрыто предположением.
|
||||
|
||||
### Следствия перед архитектурным этапом
|
||||
|
||||
- Под ярлыком «Ordinary Resource Generator» в отчёте упоминаются два разных инструмента: `yaml-generator` пишет спеки, а `resource-generator` создаёт Go-ресурсы. Для проектирования `ModifierSpec` это две разные точки вмешательства.
|
||||
- `vIPConfigure` и `ipSpaceName` присутствуют только в `modify` и отсутствуют в `create`. В текущей схеме они сливаются в общий ресурс и не имеют отдельного жизненного цикла. Это корень задачи модификаторов.
|
||||
- Runtime-`valueList` придётся получать не из дефолтного эндпоинта, а из контекста инстанса. Это вопрос рантайма провайдера, а не генератора.
|
||||
|
||||
### Итоговая оценка
|
||||
|
||||
Ошибок, которые ломали бы выводы отчёта, не выявлено. Отчёт можно использовать как подтверждённую основу для дальнейшего проектирования.
|
||||
@@ -1,69 +0,0 @@
|
||||
# HAR-разбор: SNAT / ipSpace / модификации (dev)
|
||||
|
||||
Дата: 2026-09-22. Источник: `/home/naeel/TF/tf_provider/HAR/*.har` (записи UI на dev-стенде, 2026-09-20).
|
||||
Релевантные файлы: `edge_.har` (SNAT/Edge), `ipSpace0.har`, `org_enough_.har`, `org_not_enough_.har`, `org0.har`.
|
||||
|
||||
## Поток modify в реальном API
|
||||
|
||||
1. `POST /api/v1/svc/instanceOperations` — `{"instanceUid":"...","operation":"modify"}` → возвращает `instanceOperationUid`.
|
||||
2. `POST /api/v1/svc/instanceOperationCfsParams` — по одному запросу на параметр:
|
||||
`{"paramValue":"...","instanceOperationUid":"...","svcOperationCfsParamId":NNN}`.
|
||||
3. `GET /svc/instanceOperations/{id}/validate-cfs`
|
||||
4. `POST /svc/instanceOperations/{id}/run`
|
||||
5. Поллинг `GET /svc/instanceOperations/{id}`.
|
||||
|
||||
## Найденные payload-и
|
||||
|
||||
| Операция | param id | код | значение из HAR |
|
||||
|---|---|---|---|
|
||||
| edge modify | 368 | `needEnableAVI` | `false` / `true` |
|
||||
| edge modify | 369 | `virtualServicesCount` | `1` / `2` |
|
||||
| edge modify | 856 | `qosProfile` | `QoS-100Mbit` |
|
||||
| edge modify | **372** | **`ipSpaceName`** | **`no-needed`** / `""` |
|
||||
| edge modify | 1112 | `routedNetConfiguration` | `{"mainDns":"81.22.46.22","secondDns":"185.247.187.77","ipAddrPool":"10.10.102.0/24"}` |
|
||||
| org modify | **662** | **`vIPConfigure`** | `[{"name":"internet-ipv4-v1","count":"3"}]` |
|
||||
|
||||
## Ответы на открытые вопросы
|
||||
|
||||
1. **Тумблера «Выделить VIP для SNAT» в API НЕТ.** SNAT управляется целиком через `ipSpaceName` (param 372).
|
||||
Его `valueList` (из метаданных в HAR): `no-needed, internet-antiddos-v1, internet-no-antiddos-v1, ...` — то есть `no-needed` это легальное значение «SNAT не нужен».
|
||||
- Включить SNAT: `ipSpaceName = <имя ipSpace из org>`.
|
||||
- Выключить: `ipSpaceName = "no-needed"`.
|
||||
2. ✅ **Каноническое «SNAT выключен» = `no-needed`.** Подтверждено: в UI (Edge → Modify → поле «ip Space для VIP», параметр `ipSpaceName`) текущее значение показывается как `no-needed`. Reverse для SNAT = `modify` с `ipSpaceName="no-needed"` → delete SNAT-модификатора можно реализовать не как no-op. (`""` из `ipSpace0.har` — не каноническое, а промежуточное состояние.)
|
||||
3. 🟡 **Де-аллокация IP в org — попытка зафиксирована (`org2.har`, 2026-09-22):** UI отправил `modify` с
|
||||
`vIPConfigure=[{"name":"internet-ipv4-v1","count":"2"}]` (count уменьшен с 3 до 2).
|
||||
HTTP-ошибки НЕТ, но операция осталась в `isPending:true` — не выполнилась (согласуется с ограничением ниже).
|
||||
**Вывод:** payload де-аллокации = ТА ЖЕ структура `vIPConfigure`, только меньше `count` (не отдельная операция).
|
||||
Точная семантика «удалить совсем» (`count=0` или опустить элемент) не подтверждена.
|
||||
🔴 **Ограничение (подтверждено):** уменьшить/удалить ipSpace в `vcOrg` **нельзя, пока существуют дочерние инстансы** (VDC/Edge/кластер).
|
||||
Следствие: reverse возможен только ПОСЛЕ уничтожения детей → порядок destroy критичен:
|
||||
`кластер → SNAT-модификатор (no-needed) → org IP de-alloc → edge → vdc → org`.
|
||||
Чтобы снять payload «удалить совсем», нужен чистый org без детей (или плановый teardown).
|
||||
|
||||
## Побочные факты
|
||||
|
||||
- У `ipSpaceName` (372) в API есть `valueList`, но в нашем YAML его **нет** → проверить, тянет ли генератор `valueList` (возможно, он динамический: имена ipSpace конкретной org).
|
||||
- Имя ipSpace в живом примере — `internet-ipv4-v1` (не произвольное).
|
||||
- `qosProfile` (856) UI всегда шлёт как `QoS-100Mbit`.
|
||||
- `routedNetConfiguration` передаётся JSON-строкой.
|
||||
- В состоянии org: `"vip":{"no-needed":{},"internet-ipv4-v1":{"count":4}}` — `no-needed` фигурирует и в стейте.
|
||||
|
||||
## Наблюдения на возможно сломанном Edge (2026-09-22, nsx_WZ03709-saas-wmfop5be)
|
||||
|
||||
⚠️ ВАЖНО: этот Edge, судя по всему, в сломанном состоянии (devops-проблема).
|
||||
Ошибки ниже **НЕ считать универсальными правилами API** — перепроверить на здоровом Edge.
|
||||
|
||||
1. `modify` 14:40:52 → «ipSpace '' не найден на https://sandbox.nubes.ru» — при пустом `ipSpaceName` бэкенд отклонил запрос. ❓ Возможно, следствие сломанного Edge, не правило.
|
||||
2. `modify` 14:43:44 → «Insufficient rule blocks» при попытке снять «Включить ALB». ❓ Возможно, застрявшие VS/SE Group, не правило.
|
||||
3. `delete` (2 раза) → FORBIDDEN «Cannot delete SE Group assignment … since there are Virtual Services». ❓ Возможно, застрявшие VS, не правило.
|
||||
|
||||
**Что остаётся надёжным (из API-метаданных, НЕ из этих ошибок):**
|
||||
- `valueList` у `ipSpaceName` содержит `no-needed` (+ имена ipSpace) — из описания параметра.
|
||||
- UI показывает `no-needed` как текущее значение при выключенном SNAT.
|
||||
|
||||
## Что ещё нужно выяснить из UI (открытые вопросы)
|
||||
|
||||
1. 🔴 **Де-аллокация IP в org — пока НЕ снять:** UI/бэкенд не даёт удалить ipSpace, пока есть дочерние инстансы (подтверждено 2026-09-22). Нужен чистый org или плановый teardown. Гипотеза payload — `vIPConfigure=[]` (unverified).
|
||||
2. ✅ **Имя ipSpace — выбор ИЗ СПИСКА** (подтверждено UI). Свободного ввода нет → список динамический (текущие ipSpace org + `no-needed`).
|
||||
Следствие для провайдера: `ip_space_name` в SNAT-модификаторе должен браться из **computed-вывода org-модификатора**, а не быть свободной строкой.
|
||||
3. 🟡 Полное удаление ipSpace и поведение при destroy Edge с включённым SNAT — на будущее (блокировано п.1).
|
||||
@@ -1,221 +0,0 @@
|
||||
# Инструкция по добавлению нового сервиса в Terraform-провайдер Nubes
|
||||
|
||||
## 1. Где прописывать
|
||||
|
||||
Единственная точка входа — `devops/config/services_list.txt` (или профильный `profiles/{stand}/services_list.txt`).
|
||||
|
||||
Формат строки:
|
||||
```
|
||||
{service_id} {service_name} # Описание (опционально)
|
||||
```
|
||||
|
||||
Пример:
|
||||
```
|
||||
90 postgres # Управляемая база данных PostgreSQL
|
||||
```
|
||||
|
||||
- **service_id** — число, ID сервиса из API Nubes (`/services/{id}`)
|
||||
- **service_name** — snake_case, латиница. Будет использоваться как имя ресурса `nubes_{name}`
|
||||
|
||||
Исключение сервиса — закомментировать строку `#`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Что происходит после добавления в список
|
||||
|
||||
Пайплайн (3 шага):
|
||||
|
||||
```
|
||||
services_list.txt
|
||||
→ 01_generate_yamls.sh → запрос к API → resources_yaml/{id}_{name}.yaml
|
||||
→ 02_generate_resources_and_docs_v2.sh → internal/resources_gen/{name}_resource.go + docs
|
||||
→ 03_build_and_upload_provider.sh → сборка + S3
|
||||
```
|
||||
|
||||
**Никаких ручных правок YAML или сгенерированного Go-кода.** Всё из API.
|
||||
|
||||
---
|
||||
|
||||
## 3. Структура YAML (что генерируется)
|
||||
|
||||
```yaml
|
||||
name: postgres # snake_case, из services_list.txt
|
||||
service_id: 90 # ID сервиса
|
||||
service_display_name: PostgreSQL # человекочитаемое имя
|
||||
service_short_name: postgres # краткое имя из API
|
||||
service_man: "описание..." # MAN-руководство (HTML)
|
||||
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: true # есть ли операция suspend
|
||||
adopt_existing_on_create_default: false
|
||||
|
||||
outputs:
|
||||
params: # выходные параметры (всегда одинаковые)
|
||||
- code: state_params; type: map
|
||||
- code: state_out; type: map
|
||||
- code: vault_secrets; type: map; sensitive: true
|
||||
- code: vault_url; type: string
|
||||
...
|
||||
|
||||
operations:
|
||||
- name: create # имя операции из API (snake_case)
|
||||
id: 19 # svcOperationId
|
||||
kind: instance # instance | subresource | action
|
||||
action: create # create | modify | delete | suspend | ...
|
||||
man: "руководство" # описание операции
|
||||
params:
|
||||
- id: 788 # svcOperationCfsParamId
|
||||
code: clusterConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 20
|
||||
is_modifiable: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Типы параметров (data_type)
|
||||
|
||||
| Тип | Пример | Когда использовать |
|
||||
|---|---|---|
|
||||
| `string` | `resourceRealm`, `domain`, `dbName` | Имена, домены, UUID, строки |
|
||||
| `integer > 0` | `resourceCPU`, `resourceMemory` | Числовые значения больше нуля |
|
||||
| `integer >= 0` | `autoScaleTechWindow` | Числовые, допускающие 0 |
|
||||
| `boolean` | `enablePgPoolerMaster`, `allowNoSsl` | Флаги вкл/выкл |
|
||||
| `uuid` | `s3Uid`, `vdcUid` | Ссылки на другие сервисы (ref) |
|
||||
| `map-fixed` | `clusterConfiguration` | **Сложные K8s-сервисы**: JSON-объект с фиксированным набором полей |
|
||||
| `array-map-fixed` | `postgresConf` | Массив JSON-объектов (доп. конфигурации) |
|
||||
| `json` | `jsonEnv`, `jsonParameters` | Произвольный JSON |
|
||||
| `map` | `state_params` | Только в outputs |
|
||||
| `yaml` | — | YAML-строка (редко) |
|
||||
|
||||
---
|
||||
|
||||
## 5. Виды операций (kind)
|
||||
|
||||
### instance — управление жизненным циклом сервиса
|
||||
|
||||
| action | Terraform | Пример |
|
||||
|---|---|---|
|
||||
| `create` | `resource "nubes_X" "Y" {}` | Создание сервиса |
|
||||
| `delete` | `terraform destroy` | Удаление |
|
||||
| `modify` | изменение параметров → `apply` | Модификация |
|
||||
| `suspend` | `suspend_on_destroy = true` | Остановка (без удаления) |
|
||||
| `resume` | `adopt_existing_on_create = true` | Запуск остановленного |
|
||||
|
||||
**Правило**: если у сервиса есть `suspend` И `resume` → `suspend_on_destroy_default = true`.
|
||||
|
||||
### subresource — вложенные объекты
|
||||
|
||||
Стандартные subresource'ы:
|
||||
- **user** → `nubes_{service}_user` (create/delete)
|
||||
- Параметры: `username` (string, required), `role` (string, required, value_list)
|
||||
- **database** → `nubes_{service}_database` (create/delete)
|
||||
- Параметры: `dbName` (string, required, regex), `dbOwner` (string, required)
|
||||
- **topic** → Kafka
|
||||
- **backup** → S3, PostgreSQL
|
||||
- **vdc** → vcOrg
|
||||
|
||||
**Правило**: subresource всегда ссылается на родительский ресурс через `{service}_id`.
|
||||
|
||||
### action — разовые операции
|
||||
|
||||
| action | Когда |
|
||||
|---|---|
|
||||
| `reconcile` | Синхронизация состояния (у 20 сервисов) |
|
||||
| `redeploy` | Переразвёртывание (приложения: flask, nodejs, lucee, ...) |
|
||||
| `restart` | Перезапуск (postgres, mariadb, ...) |
|
||||
| `recovery` | Восстановление из бэкапа |
|
||||
|
||||
**Правило**: action-ресурсы используют trigger-поле (`run_id`/`nonce`) для идемпотентности.
|
||||
|
||||
---
|
||||
|
||||
## 6. Валидация параметров (автоматически из API)
|
||||
|
||||
| Механизм | Параметров | Пример |
|
||||
|---|---|---|
|
||||
| `regex` | 29 | `dbName: ^[A-Za-z0-9]+$` |
|
||||
| `value_list` | 38 | `role: [app_user, ddl_user]` |
|
||||
| `minlength`/`maxlength` | 87/74 | `username: min 2, max 62` |
|
||||
| `minvalue`/`maxvalue` | — | `resourceCPU > 0` |
|
||||
| `default` | 120 | `deleteS3Bucket: true` |
|
||||
| `is_modifiable` | 106 | Можно менять после create |
|
||||
| `is_sensitive` | 2 | `vault_secrets` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Что запрещено
|
||||
|
||||
- ❌ **Править сгенерированные YAML вручную** — источник истины только API
|
||||
- ❌ **Править `internal/resources_gen/*.go` вручную** — перезапишется при следующей генерации
|
||||
- ❌ **Использовать кириллицу в `service_name`** — только латиница, snake_case
|
||||
- ❌ **Менять `embed.go`** — генерируется автоматически
|
||||
- ❌ **Пропускать сервис через комментарий без причины** — лучше явно указать причину в комментарии: `# 24 DEPRECATED ...`
|
||||
|
||||
---
|
||||
|
||||
## 8. Быстрый старт: добавляем новый сервис
|
||||
|
||||
```bash
|
||||
# 1. Добавить строку в services_list.txt
|
||||
echo "200 my_new_service # Моя новая услуга" >> devops/config/services_list.txt
|
||||
|
||||
# 2. Сгенерировать YAML (test-стенд)
|
||||
devops/01_generate_yamls.sh --profile devops/profiles/test
|
||||
|
||||
# 3. Сгенерировать Go-код + доки
|
||||
devops/02_generate_resources_and_docs_v2.sh --profile devops/profiles/test
|
||||
|
||||
# 4. Проверить что появился файл
|
||||
ls devops/profiles/test/generated/resources_yaml/200_my_new_service.yaml
|
||||
ls devops/profiles/test/generated/go/200_my_new_service_resource.go
|
||||
|
||||
# 5. Собрать и задеплоить провайдер
|
||||
devops/03_build_and_upload_provider.sh --profile devops/profiles/test
|
||||
|
||||
# 6. Написать тестовый манифест в TEST_STAND/my_new_service/main.tf
|
||||
# 7. terraform init && terraform plan && terraform apply
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Как писать тестовый манифест
|
||||
|
||||
### Простой сервис (flat-параметры)
|
||||
|
||||
```hcl
|
||||
resource "nubes_s3bucket" "test" {
|
||||
resource_name = "Мой бакет"
|
||||
s3_user_uid = var.s3_uid
|
||||
bucket_name = "test-bucket"
|
||||
}
|
||||
```
|
||||
|
||||
### Сложный сервис (map-fixed)
|
||||
|
||||
```hcl
|
||||
resource "nubes_postgres" "test" {
|
||||
resource_name = "pg-test"
|
||||
|
||||
cluster_configuration = jsonencode({
|
||||
platform = "k8s-3.ext.nubes.ru"
|
||||
instances = 1
|
||||
memory = 512
|
||||
cpu = 500
|
||||
disk = "1"
|
||||
})
|
||||
|
||||
startup_configuration = jsonencode({
|
||||
appVersion = "17"
|
||||
})
|
||||
|
||||
access_configuration = jsonencode({
|
||||
needExternalAddressMaster = false
|
||||
})
|
||||
|
||||
# ... остальные блоки
|
||||
}
|
||||
```
|
||||
|
||||
**JSON-ключи внутри map-fixed** — camelCase-версии старых плоских названий параметров. Точные имена полей смотри в сгенерированном YAML (поле `code` у параметров в `operations.params`) или в `generated/docs/{service}_params_create.md`.
|
||||
@@ -1,282 +0,0 @@
|
||||
# Инструкция для DevOps облака: имплементация нового managed-сервиса
|
||||
|
||||
> На основе анализа 43 сервисов Nubes Cloud (июль 2026).
|
||||
> Цель: единый стандарт, чтобы любой новый сервис был консистентен с существующими.
|
||||
|
||||
---
|
||||
|
||||
## 1. Классификация сервиса
|
||||
|
||||
Выбери один из двух классов ДО начала проектирования параметров:
|
||||
|
||||
| Класс | Признак | Размещение | Примеры |
|
||||
|---|---|---|---|
|
||||
| **Простой** | Не требует оркестрации K8s | VM, сеть, хранилище | s3bucket, vc_vm, dnszone, vcexternalip, harbor |
|
||||
| **Сложный (K8s)** | Разворачивается в Kubernetes через оператор | Pods на кластере | postgres, redis, kafka, clickhouse, flask, nextcloud |
|
||||
|
||||
**Правило**: если сервис крутится в K8s → используй **map-fixed** блоки (раздел 3). Если нет → плоские параметры (раздел 2).
|
||||
|
||||
---
|
||||
|
||||
## 2. Простой сервис: плоские параметры
|
||||
|
||||
### Обязательный минимум
|
||||
|
||||
Каждый сервис ДОЛЖЕН иметь эти параметры в create:
|
||||
|
||||
| code | data_type | required | Назначение |
|
||||
|---|---|---|---|
|
||||
| `resourceRealm` | `string` | true | Платформа/K8s-кластер для развёртывания |
|
||||
| `resourceName` | `string` | true | Человекочитаемое имя инстанса (displayName) |
|
||||
|
||||
### Стандартные ресурсные параметры
|
||||
|
||||
Добавляй по необходимости:
|
||||
|
||||
| code | data_type | Назначение |
|
||||
|---|---|---|
|
||||
| `resourceInstances` | `integer > 0` | Количество реплик/нод (default: 1) |
|
||||
| `resourceMemory` | `integer > 0` | Память в MB |
|
||||
| `resourceCPU` | `integer > 0` | CPU в милликорах (1000 = 1 vCPU) |
|
||||
| `resourceDisk` | `string` | Диск в GB |
|
||||
|
||||
### Прочие частые параметры
|
||||
|
||||
| code | data_type | Где используется |
|
||||
|---|---|---|
|
||||
| `domain` | `string` | Сервисы с доменным именем (9 из 43) |
|
||||
| `ipSpaceName` | `string` | Сервисы с внешним IP |
|
||||
| `appConfiguration` | `map-fixed` | Приложения (nextcloud, superset, harbor, ...) |
|
||||
| `jsonEnv` | `json` | Переменные окружения (flask, nodejs) |
|
||||
| `storageConfig` | `map-fixed` | Хранилище (kafka, clickhouse) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Сложный сервис (K8s): map-fixed блоки
|
||||
|
||||
**Правило**: группируй параметры в логические блоки. Используй этот стандартный набор:
|
||||
|
||||
### 3.1. startupConfiguration (sort: 10)
|
||||
**Назначение**: версия ПО, образ, всё что задаётся до старта.
|
||||
```
|
||||
appVersion, image, imageTag, ...
|
||||
```
|
||||
**required**: true. **is_modifiable**: false (не меняется после создания).
|
||||
|
||||
### 3.2. clusterConfiguration (sort: 20)
|
||||
**Назначение**: размер кластера, ресурсы.
|
||||
```
|
||||
platform (= resourceRealm), instances, memory, cpu, disk
|
||||
```
|
||||
**required**: true. **is_modifiable**: true.
|
||||
|
||||
### 3.3. accessConfiguration (sort: 30)
|
||||
**Назначение**: сетевой доступ.
|
||||
```
|
||||
needExternalAddressMaster, ipSpaceNameMaster,
|
||||
needExternalAddressSlave, ipSpaceNameSlave,
|
||||
allowNoSsl
|
||||
```
|
||||
**required**: true. **is_modifiable**: true.
|
||||
|
||||
### 3.4. {service}Configuration (sort: 40)
|
||||
**Назначение**: специфичные для сервиса настройки.
|
||||
```
|
||||
enablePgPoolerMaster, enablePgPoolerSlave, s3Uid, jsonParameters, ...
|
||||
```
|
||||
**required**: true. **is_modifiable**: true.
|
||||
|
||||
### 3.5. {service}Conf (sort: 50)
|
||||
**Назначение**: массив дополнительных конфигураций.
|
||||
Тип: `array-map-fixed`.
|
||||
**required**: false. **is_modifiable**: true.
|
||||
|
||||
### 3.6. backupConfiguration (sort: 60)
|
||||
**Назначение**: политика резервного копирования.
|
||||
```
|
||||
schedule (cron), numToRetain, ...
|
||||
```
|
||||
**required**: true. **is_modifiable**: true.
|
||||
|
||||
### 3.7. autoscaleConfiguration (sort: 70)
|
||||
**Назначение**: автоскейлинг.
|
||||
```
|
||||
autoScale (boolean), percentage, techWindow, quotaGb
|
||||
```
|
||||
**required**: true. **is_modifiable**: true.
|
||||
|
||||
### Пример структуры для нового K8s-сервиса
|
||||
|
||||
```
|
||||
create params (sort order):
|
||||
10: startupConfiguration map-fixed required
|
||||
20: clusterConfiguration map-fixed required modifiable
|
||||
30: accessConfiguration map-fixed required modifiable
|
||||
40: {name}Configuration map-fixed required modifiable
|
||||
50: {name}Conf array-map optional modifiable
|
||||
60: backupConfiguration map-fixed required modifiable
|
||||
70: autoscaleConfiguration map-fixed required modifiable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Именование параметров
|
||||
|
||||
### ⛔ Жёсткие правила
|
||||
|
||||
- **camelCase** для ВСЕХ кодов параметров: `resourceRealm`, `clusterConfiguration`, `dbName`
|
||||
- **Никакого snake_case**: ❌ `resource_realm`, ✅ `resourceRealm`
|
||||
- **Никакого хаотичного нейминга**: если параметр про память — везде `resourceMemory`, не `memoryQuota` или `memLimit`
|
||||
- **Префиксы**: общие параметры с префиксом `resource*` (resourceRealm, resourceCPU, resourceMemory, resourceDisk, resourceInstances)
|
||||
|
||||
### Стандартный словарь
|
||||
|
||||
| Концепт | Код параметра |
|
||||
|---|---|
|
||||
| Платформа/K8s-кластер | `resourceRealm` |
|
||||
| Количество реплик | `resourceInstances` |
|
||||
| Память (MB) | `resourceMemory` |
|
||||
| CPU (millicore) | `resourceCPU` |
|
||||
| Диск (GB) | `resourceDisk` |
|
||||
| Версия ПО | `appVersion` |
|
||||
| Домен | `domain` |
|
||||
| S3-ссылка | `s3Uid` |
|
||||
| IP-space | `ipSpaceName` |
|
||||
| Внешний IP для master | `needExternalAddressMaster` |
|
||||
| Внешний IP для slave | `needExternalAddressSlave` |
|
||||
| PgBouncer master | `enablePgPoolerMaster` |
|
||||
| PgBouncer slave | `enablePgPoolerSlave` |
|
||||
| Отключить SSL | `allowNoSsl` |
|
||||
| Автоскейлинг | `autoScale` |
|
||||
| Cron бэкапа | `backupSchedule` |
|
||||
|
||||
---
|
||||
|
||||
## 5. Операции
|
||||
|
||||
### Обязательные (каждый сервис)
|
||||
|
||||
| operation | kind | action |
|
||||
|---|---|---|
|
||||
| `create` | instance | create |
|
||||
| `delete` | instance | delete |
|
||||
|
||||
### Настоятельно рекомендуемые
|
||||
|
||||
| operation | kind | action | Зачем |
|
||||
|---|---|---|---|
|
||||
| `modify` | instance | modify | Изменение параметров без удаления |
|
||||
| `suspend` | instance | suspend | Остановка без удаления (биллинг!) |
|
||||
| `resume` | instance | resume | Запуск после suspend |
|
||||
|
||||
**Правило**: если реализовал `suspend` → ОБЯЗАТЕЛЬНО реализовать `resume`. И наоборот.
|
||||
|
||||
### Опциональные
|
||||
|
||||
| operation | kind | action | У кого есть |
|
||||
|---|---|---|---|
|
||||
| `restart` | action | restart | postgres, mariadb, redis, kafka |
|
||||
| `recovery` | action | recovery | postgres, clickhouse |
|
||||
| `reconcile` | action | reconcile | 20 сервисов (универсальная синхронизация) |
|
||||
| `redeploy` | action | redeploy | flask, nodejs, lucee, nifi, superset |
|
||||
|
||||
---
|
||||
|
||||
## 6. Subresource'ы
|
||||
|
||||
### Стандартные
|
||||
|
||||
| subresource | operations | Параметры | У скольких сервисов |
|
||||
|---|---|---|---|
|
||||
| **user** | create_user, delete_user | `username` (string, regex), `role` (string, value_list) | 16 |
|
||||
| **database** | create_database, delete_database | `dbName` (string, regex), `dbOwner` (string) | 6 |
|
||||
|
||||
### Специфичные
|
||||
|
||||
| subresource | Где |
|
||||
|---|---|
|
||||
| `topic` | kafka (3 операции) |
|
||||
| `backup` | s3, postgres |
|
||||
| `vdc` | vcOrg |
|
||||
| `sub_user` | openwhisk |
|
||||
|
||||
### Правила subresource'ов
|
||||
|
||||
- **Именование операций**: `create_{subresource}`, `delete_{subresource}` (snake_case глагол + имя)
|
||||
- **Именование subresource**: одно слово, snake_case: `user`, `database`, `topic`
|
||||
- **Ссылка на родителя**: обязательный UUID-параметр, ссылающийся на родительский инстанс
|
||||
- **Параметр `role` для user**: ОБЯЗАТЕЛЬНО `value_list` с вариантами (например `[app_user, ddl_user]`)
|
||||
- **Параметр `dbName` для database**: ОБЯЗАТЕЛЬНО `regex: ^[A-Za-z0-9]+$`
|
||||
|
||||
---
|
||||
|
||||
## 7. Валидация параметров
|
||||
|
||||
### Обязательно (где применимо)
|
||||
|
||||
| Механизм | Когда | Пример |
|
||||
|---|---|---|
|
||||
| `value_list` | Ограниченный набор значений | `role: [app_user, ddl_user]` |
|
||||
| `regex` | Имена, идентификаторы | `dbName: ^[A-Za-z0-9]+$` |
|
||||
| `minlength` | Минимальная длина строки | `username: min 2` |
|
||||
| `maxlength` | Максимальная длина строки | `username: max 62` |
|
||||
| `minvalue` | Минимальное число | `resourceCPU: > 0` |
|
||||
| `maxvalue` | Максимальное число | — |
|
||||
| `default` | Значение по умолчанию | `deleteS3Bucket: true` |
|
||||
|
||||
### ⛔ Запрещено
|
||||
|
||||
- Параметр без `descr` (описание) — ВСЕГДА заполнять
|
||||
- Булевы параметры без `default` — если не указан, поведение неопределено
|
||||
- `required: true` для параметра с `default` — бессмысленно
|
||||
|
||||
---
|
||||
|
||||
## 8. Модифицируемость (is_modifiable)
|
||||
|
||||
### Правило
|
||||
|
||||
| Категория параметра | is_modifiable |
|
||||
|---|---|
|
||||
| Имя, версия ПО, платформа (startup) | **false** |
|
||||
| Ресурсы (CPU, память, диск, реплики) | **true** |
|
||||
| Доступ (IP, SSL, pooler) | **true** |
|
||||
| Бэкапы, автоскейлинг | **true** |
|
||||
| Всё что в modify-операции | **true** |
|
||||
|
||||
106 из 435 параметров (24%) имеют `is_modifiable: true`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Outputs
|
||||
|
||||
**Не трогать.** Стандартный набор выходных параметров един для всех сервисов:
|
||||
|
||||
```
|
||||
state_params map
|
||||
state_out map
|
||||
state_params_flat map
|
||||
state_out_flat map
|
||||
vault_secrets map sensitive
|
||||
vault_url string
|
||||
vault_user_path string
|
||||
vault_fields list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Чек-лист перед сдачей сервиса
|
||||
|
||||
- [ ] `resourceRealm` есть в create (required)
|
||||
- [ ] `resourceName` есть в create (required)
|
||||
- [ ] Все коды параметров — camelCase
|
||||
- [ ] Для K8s-сервиса: 6 стандартных map-fixed блоков с правильными sort
|
||||
- [ ] `suspend` + `resume` либо есть оба, либо нет ни одного
|
||||
- [ ] `modify` содержит все `is_modifiable: true` параметры из create
|
||||
- [ ] У всех параметров заполнен `descr`
|
||||
- [ ] Булевы параметры имеют `default`
|
||||
- [ ] `username` для user-subresource имеет regex и minlength/maxlength
|
||||
- [ ] `role` для user-subresource имеет value_list
|
||||
- [ ] `dbName` для database-subresource имеет regex
|
||||
- [ ] MAN (service_man) заполнен: описание, параметры, примеры
|
||||
- [ ] MAN для каждой операции (operation.man) заполнен
|
||||
@@ -1,373 +0,0 @@
|
||||
<!-- ⛔ LEGACY: deck-api.ngcloud.ru ЗАКРЫВАЕТСЯ. Актуальный API: lk-api-gateway.ngcloud.ru/api/v1/svc -->
|
||||
# Матрица состояний ресурсов в облаке ngcloud
|
||||
|
||||
## Основные параметры состояния
|
||||
|
||||
### 1. В структуре инстанса (instance object)
|
||||
|
||||
Каждый ресурс имеет следующие флаги:
|
||||
|
||||
| Параметр | Тип | Возможные значения | Описание |
|
||||
|----------|-----|------------------|---------|
|
||||
| `explainedStatus` | string | `running`, (другие?) | Основной объяснительный статус |
|
||||
| `isCreated` | boolean | `true`, `false` | Был ли инстанс создан |
|
||||
| `isDeleted` | boolean | `true`, `false` | Отмечен ли как удаленный |
|
||||
| `isSuspended` | boolean | `true`, `false` | Приостановлен ли |
|
||||
| `operationIsInProgress` | boolean | `true`, `false` | Идет ли корректная операция |
|
||||
| `operationIsPending` | boolean | `true`, `false` | Есть ли ожидающие операции |
|
||||
| `uptime` | number | >= 0 | Время работы в секундах (0, если не создан) |
|
||||
|
||||
### 2. Структура state (instanceState object)
|
||||
|
||||
```json
|
||||
{
|
||||
"state": {
|
||||
"instanceStateUid": "uuid",
|
||||
"version": 1, // версия состояния
|
||||
"dtState": "timestamp", // дата состояния
|
||||
"isTest": boolean
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Операции (operations array)
|
||||
|
||||
```json
|
||||
{
|
||||
"operation": "create|modify|delete|suspend|resume",
|
||||
"dtSubmit": "timestamp",
|
||||
"dtStart": null, // null если еще не началась
|
||||
"dtFinish": null, // null если еще не закончилась
|
||||
"isSuccessful": null|true|false, // null если не выполнялась
|
||||
"isInProgress": boolean,
|
||||
"isPending": boolean,
|
||||
"submitResult": "201|..." // HTTP статус результата отправки
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## МАТРИЦА СОСТОЯНИЙ
|
||||
|
||||
### ✅ СОСТОЯНИЕ: АКТИВНЫЙ РАБОЧИЙ ИНСТАНС (RUNNING)
|
||||
```
|
||||
isCreated=true
|
||||
isDeleted=false
|
||||
isSuspended=false
|
||||
explainedStatus="running"
|
||||
operationIsInProgress=false
|
||||
operationIsPending=false
|
||||
uptime > 0
|
||||
state.version >= 1
|
||||
```
|
||||
**Значение:** Ресурс создан, работает, нет выполняющихся операций
|
||||
|
||||
---
|
||||
|
||||
### 🔄 СОСТОЯНИЕ: СОЗДАНИЕ В ПРОЦЕССЕ (CREATING)
|
||||
```
|
||||
isCreated=true (или может быть false на ранних стадиях)
|
||||
isDeleted=false
|
||||
isSuspended=false
|
||||
operationIsInProgress=true
|
||||
operationIsPending=false
|
||||
state.version = 1
|
||||
operations[0].operation = "create"
|
||||
operations[0].isSuccessful = null (еще не завершено)
|
||||
operations[0].dtStart != null
|
||||
operations[0].dtFinish = null
|
||||
```
|
||||
**Значение:** Операция создания выполняется
|
||||
|
||||
---
|
||||
|
||||
### ⏸️ СОСТОЯНИЕ: ПРИОСТАНОВЛЕН (SUSPENDED)
|
||||
```
|
||||
isCreated=true
|
||||
isDeleted=false
|
||||
isSuspended=true
|
||||
explainedStatus="suspended" (или может быть другое)
|
||||
operationIsInProgress=false
|
||||
```
|
||||
**Значение:** Ресурс создан, но приостановлен (власт нет - suspend)
|
||||
|
||||
---
|
||||
|
||||
### ⏸️ СОСТОЯНИЕ: ПРИОСТАНОВКА В ПРОЦЕССЕ (SUSPENDING)
|
||||
```
|
||||
isCreated=true
|
||||
isSuspended=false (еще не приостановлен)
|
||||
operationIsInProgress=true
|
||||
operations[last].operation = "suspend"
|
||||
operations[last].isSuccessful = null
|
||||
```
|
||||
**Значение:** Операция приостановки выполняется
|
||||
|
||||
---
|
||||
|
||||
### ▶️ СОСТОЯНИЕ: ВОЗОБНОВЛЕНИЕ В ПРОЦЕССЕ (RESUMING)
|
||||
```
|
||||
isCreated=true
|
||||
isSuspended=true (еще приостановлен)
|
||||
operationIsInProgress=true
|
||||
operations[last].operation = "resume"
|
||||
operations[last].isSuccessful = null
|
||||
```
|
||||
**Значение:** Операция возобновления выполняется
|
||||
|
||||
---
|
||||
|
||||
### ❌ СОСТОЯНИЕ: НЕ СОЗДАН (NOT CREATED)
|
||||
```
|
||||
isCreated=false
|
||||
isDeleted=false
|
||||
isSuspended=false
|
||||
state = null или пуст
|
||||
uptime = 0
|
||||
version = null или отсутствует
|
||||
```
|
||||
**Значение:** Ресурс был определен в коде, но никогда не создавался в облаке
|
||||
|
||||
---
|
||||
|
||||
### 🗑️ СОСТОЯНИЕ: УДАЛЕН (DELETED)
|
||||
```
|
||||
isDeleted=true
|
||||
isCreated=true (был когда-то создан)
|
||||
state.version может быть или не быть
|
||||
```
|
||||
**Значение:** Ресурс был удален, может оставаться в истории
|
||||
|
||||
---
|
||||
|
||||
### 🔄 СОСТОЯНИЕ: УДАЛЕНИЕ В ПРОЦЕССЕ (DELETING)
|
||||
```
|
||||
isCreated=true
|
||||
isDeleted=false (еще не отмечен как удаленный)
|
||||
operationIsInProgress=true
|
||||
operations[last].operation = "delete"
|
||||
operations[last].isSuccessful = null
|
||||
```
|
||||
**Значение:** Операция удаления выполняется
|
||||
|
||||
---
|
||||
|
||||
### 🔧 СОСТОЯНИЕ: ИЗМЕНЕНИЕ В ПРОЦЕССЕ (MODIFYING)
|
||||
```
|
||||
isCreated=true
|
||||
isDeleted=false
|
||||
operationIsInProgress=true
|
||||
operations[last].operation = "modify"
|
||||
operations[last].isSuccessful = null
|
||||
```
|
||||
**Значение:** Конфигурация ресурса изменяется
|
||||
|
||||
---
|
||||
|
||||
### ⏳ СОСТОЯНИЕ: ОПЕРАЦИЯ ОЖИДАЕТ (PENDING)
|
||||
```
|
||||
operationIsPending=true
|
||||
operationIsInProgress=false
|
||||
operations[last].dtStart = null (еще не началась, но создана)
|
||||
operations[last].isSuccessful = null
|
||||
```
|
||||
**Значение:** Операция создана, но еще не началась (в очереди)
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ СОСТОЯНИЕ: ОШИБКА ОПЕРАЦИИ (OPERATION_FAILED)
|
||||
```
|
||||
operations[last].isSuccessful=false
|
||||
operations[last].dtFinish != null
|
||||
operations[last].errorLog != null
|
||||
operationIsInProgress=false
|
||||
```
|
||||
**Значение:** Последняя операция завершилась с ошибкой
|
||||
|
||||
---
|
||||
|
||||
### 🔀 СОСТОЯНИЕ: ГИБРИДНОЕ/ПЕРЕХОДНОЕ (HYBRID)
|
||||
|
||||
**Противоречивые комбинации указывают на переходное состояние:**
|
||||
- `operationIsInProgress=true` + `operations[last].dtStart=null` → операция только создана, еще не началась
|
||||
- `isCreated=true` + `operationIsPending=true` → есть ожидающая операция
|
||||
- `state.version=null` + `isCreated=true` → некорректное состояние
|
||||
|
||||
---
|
||||
|
||||
## АЛГОРИТМ ОПРЕДЕЛЕНИЯ СОСТОЯНИЯ
|
||||
|
||||
### На уровне API (GET /api/v1/index.cfm/instances/{uid})
|
||||
|
||||
```python
|
||||
def get_instance_state(instance_data):
|
||||
"""
|
||||
instance_data = {
|
||||
'isCreated': bool,
|
||||
'isDeleted': bool,
|
||||
'isSuspended': bool,
|
||||
'explainedStatus': str,
|
||||
'operationIsInProgress': bool,
|
||||
'operationIsPending': bool,
|
||||
'uptime': float,
|
||||
'state': {...} or null,
|
||||
'operations': [...]
|
||||
}
|
||||
"""
|
||||
|
||||
# Шаг 1: Проверка удаления
|
||||
if instance_data['isDeleted']:
|
||||
return 'DELETED'
|
||||
|
||||
# Шаг 2: Проверка создания
|
||||
if not instance_data['isCreated']:
|
||||
return 'NOT_CREATED'
|
||||
|
||||
# Шаг 3: Проверка операции в процессе
|
||||
if instance_data['operationIsInProgress']:
|
||||
last_op = instance_data['operations'][-1] if instance_data['operations'] else None
|
||||
if last_op:
|
||||
operation_type = last_op.get('operation', 'unknown')
|
||||
return f'{operation_type.upper()}_IN_PROGRESS'
|
||||
|
||||
# Шаг 4: Проверка ожидающей операции
|
||||
if instance_data['operationIsPending']:
|
||||
last_op = instance_data['operations'][-1] if instance_data['operations'] else None
|
||||
if last_op and not last_op.get('dtStart'):
|
||||
return 'OPERATION_PENDING'
|
||||
|
||||
# Шаг 5: Проверка приостановки
|
||||
if instance_data['isSuspended']:
|
||||
return 'SUSPENDED'
|
||||
|
||||
# Шаг 6: Проверка основного статуса
|
||||
if instance_data['explainedStatus'] == 'running':
|
||||
return 'RUNNING'
|
||||
|
||||
# Шаг 7: Проверка ошибок в операциях
|
||||
if instance_data['operations']:
|
||||
last_op = instance_data['operations'][-1]
|
||||
if last_op.get('isSuccessful') == False:
|
||||
return 'OPERATION_FAILED'
|
||||
|
||||
return 'UNKNOWN'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## РЕКОМЕНДУЕМЫЕ ЗАПРОСЫ К API
|
||||
|
||||
### Получить список ресурсов определенного типа
|
||||
|
||||
```bash
|
||||
GET /api/v1/index.cfm/instances?fields=instanceConfigDtCreated,instanceUid,displayName,svc,uptime,explainedStatus,svcExtendedName,updaterLogin,updaterShortname,operationIsInProgress,operationIsPending,monitoringUrl&search={resource_name}&isAuxiliary=false&isDeleted=false
|
||||
```
|
||||
|
||||
### Получить полную информацию о ресурсе
|
||||
|
||||
```bash
|
||||
GET /api/v1/index.cfm/instances/{instance_uid}?fields=instanceConfigDtCreated,instanceUid,displayName,descr,svc,state,operations,availableOperations,uptime,isDeleted,updaterLogin,updaterShortname,explainedStatus,man,dependencies,dependentInstances,svcExtendedName
|
||||
```
|
||||
|
||||
### Выполнить операцию
|
||||
|
||||
```bash
|
||||
POST /api/v1/index.cfm/instanceOperations
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"instanceUid": "uuid",
|
||||
"operation": "create|modify|delete|suspend|resume"
|
||||
}
|
||||
```
|
||||
|
||||
### Получить статус операции
|
||||
|
||||
```bash
|
||||
GET /api/v1/index.cfm/instanceOperations/{operation_uid}?fields=instanceOperationUid,instanceUid,state,stages,cfsParams,isSuccessful,dtCreated,dtUpdated,dtStart,dtFinish,operation,svcOperationId,svc,displayName,submitResult,duration,errorLog,updaterShortname,man,nestedRefData,isInProgress,isPending,dtSubmit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ПРИМЕРЫ ИЗ HAR
|
||||
|
||||
### Пример 1: RUNNING инстанс
|
||||
```json
|
||||
{
|
||||
"displayName": "Newly Added",
|
||||
"isCreated": true,
|
||||
"isDeleted": false,
|
||||
"isSuspended": false,
|
||||
"explainedStatus": "running",
|
||||
"operationIsInProgress": false,
|
||||
"operationIsPending": false,
|
||||
"uptime": 1102.739073,
|
||||
"state": {
|
||||
"instanceStateUid": "48bfdbc5-e8fc-4286-8098-d6ab7f6de50e",
|
||||
"version": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Пример 2: CREATE_IN_PROGRESS инстанс
|
||||
```json
|
||||
{
|
||||
"displayName": "ttst00",
|
||||
"isCreated": true,
|
||||
"isDeleted": false,
|
||||
"isSuspended": false,
|
||||
"explainedStatus": "running",
|
||||
"operationIsInProgress": false,
|
||||
"operationIsPending": true,
|
||||
"uptime": 0,
|
||||
"operations": [
|
||||
{
|
||||
"operation": "modify",
|
||||
"dtSubmit": "2026-01-22T19:30:21.790+0300",
|
||||
"dtStart": null,
|
||||
"dtFinish": null,
|
||||
"isSuccessful": null,
|
||||
"isInProgress": false,
|
||||
"isPending": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Пример 3: DELETE_IN_PROGRESS инстанс
|
||||
```json
|
||||
{
|
||||
"displayName": "ttt0-terraform",
|
||||
"isCreated": true,
|
||||
"isDeleted": false,
|
||||
"isSuspended": false,
|
||||
"operations": [
|
||||
{
|
||||
"operation": "delete",
|
||||
"dtSubmit": "2026-01-22T19:28:24.720+0300",
|
||||
"dtStart": null,
|
||||
"dtFinish": null,
|
||||
"isSuccessful": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## СВОДНАЯ ТАБЛИЦА ВОЗМОЖНЫХ СОСТОЯНИЙ
|
||||
|
||||
| Состояние | isCreated | isDeleted | isSuspended | operationIsInProgress | Описание |
|
||||
|-----------|-----------|-----------|-------------|----------------------|---------|
|
||||
| NOT_CREATED | false | false | false | false | Еще не создан |
|
||||
| CREATING | true | false | false | true | Создание выполняется |
|
||||
| RUNNING | true | false | false | false | Работает нормально |
|
||||
| MODIFYING | true | false | false | true | Конфигурация изменяется |
|
||||
| SUSPENDING | true | false | false | true | Приостановка выполняется |
|
||||
| SUSPENDED | true | false | true | false | Приостановлен |
|
||||
| RESUMING | true | false | true | true | Возобновление выполняется |
|
||||
| DELETING | true | false | false | true | Удаление выполняется |
|
||||
| DELETED | true | true | - | - | Удален |
|
||||
| OPERATION_PENDING | true | false | - | false | Есть ожидающая операция |
|
||||
| OPERATION_FAILED | true | false | - | false | Операция завершилась ошибкой |
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
# Архитектура документации Nubes Terraform Provider
|
||||
|
||||
## Источники истины
|
||||
|
||||
| Уровень | Источник | Роль |
|
||||
|---|---|---|
|
||||
| 1 | **API (YAML-спеки)** | Единственный источник правды. Параметры, типы, defaults, constraints, operations — только из YAML |
|
||||
| 2 | **MAN (service_man)** | Дополнительный контекст. Может устареть или содержать ошибки. Используется для улучшения формулировок, но НЕ переопределяет YAML |
|
||||
| 3 | **LLM (gpt-oss-120b)** | Обрабатывает .md файлы: улучшает читаемость, добавляет логику, переводит HTML→Markdown. Меняет ТОЛЬКО текст описаний, НЕ параметры |
|
||||
|
||||
## Пайплайн генерации
|
||||
|
||||
```
|
||||
YAML-спеки
|
||||
│
|
||||
▼
|
||||
docs-generator (Go) ─── создаёт структуру: Name.md, Name_example.md, Name_params_create.md, ...
|
||||
│ HCL-блоки, HTML-таблицы параметров, navigation, index.md
|
||||
▼
|
||||
LLM (gpt-oss-120b) ─── улучшает описания: читаемый Markdown, логичные формулировки
|
||||
│ вход: YAML + MAN + текущие .md
|
||||
│ выход: переписанные .md (структура и параметры неизменны)
|
||||
▼
|
||||
mkdocs-material ─────── собирает статический сайт
|
||||
│
|
||||
▼
|
||||
S3 (terraform-registry) ── хостинг через registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> <!-- ⛔ LEGACY: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->
|
||||
```
|
||||
|
||||
## Структура страниц (на каждый сервис)
|
||||
|
||||
| Файл | Содержание |
|
||||
|---|---|
|
||||
| `Name.md` | Главная: краткое описание + MAN в `??? note` (mkdocs-native admonition) |
|
||||
| `Name_example.md` | HCL-пример с полным манифестом |
|
||||
| `Name_params_create.md` | Таблицы Create-параметров + вложенные sub_params |
|
||||
| `Name_params_modify.md` | Таблицы Modify-параметров |
|
||||
| `Name_outputs.md` | Выходные параметры + реальные ключи из облака |
|
||||
| `Name_ops.md` | Список операций (create/delete/modify/suspend/resume) |
|
||||
| `Name_params.md` | Лендинг: ссылки на create/modify params |
|
||||
| `Name_subresource.md` | Для каждого subresource: параметры |
|
||||
| `Name_subresource_example.md` | HCL-пример subresource |
|
||||
|
||||
## Формат MAN (service_man) — как рендерится
|
||||
|
||||
`service_man` из YAML содержит **смесь Markdown и HTML**: заголовки `#`/`##`, списки `-`, bold `**`, горизонтальные линии `---`, а также `<br/>` и HTML-entities.
|
||||
|
||||
### Конвертация: `htmlToMarkdown()`
|
||||
|
||||
```go
|
||||
// 1. <br/> → \n
|
||||
// 2. <h1>/<h2>/<h3> → # / ## / ###
|
||||
// 3. <strong>/<b> → **...**
|
||||
// 4. <em>/<i> → *...*
|
||||
// 5. <code> → `...`
|
||||
// 6. <a href> → [...](...)
|
||||
// 7. <ul><li> → - ...
|
||||
// 8. Strip remaining HTML tags
|
||||
// 9. Unescape HTML entities (" → ")
|
||||
// 10. Collapse 3+ blank lines → 2
|
||||
```
|
||||
|
||||
### Рендеринг: `??? note` admonition (НЕ `<details>`!)
|
||||
|
||||
**Важно:** `<details>` и `<div markdown="1">` НЕ работают в mkdocs — Markdown внутри них не рендерится.
|
||||
|
||||
Вместо этого используется **нативный mkdocs admonition** `??? note`:
|
||||
|
||||
```markdown
|
||||
??? note "Справка (MAN)"
|
||||
|
||||
# Инструкция по развертыванию
|
||||
|
||||
---
|
||||
## 1. Общая информация
|
||||
Текст параграфа.
|
||||
|
||||
- **bold** — описание
|
||||
- `code` — пример
|
||||
```
|
||||
|
||||
**Критические требования:**
|
||||
1. Пустая строка после `??? note "..."` — обязательно
|
||||
2. Все строки контента с отступом ровно 4 пробела — включая пустые
|
||||
3. `pymdownx.details` в `markdown_extensions` (уже есть)
|
||||
|
||||
Результат: `<details class="note"><summary>Справка (MAN)</summary><h1>...</h1><hr/><h2>...</h2>...</details>`
|
||||
|
||||
## Принципы дизайна (CSS)
|
||||
|
||||
- `max-width: 1800px` — лёгкое ограничение (на 2560px поля ~380px)
|
||||
- 🔵 Синий — переменные верхнего уровня (структуры)
|
||||
- 🟢 Зелёный — поля внутри структур (sub_params)
|
||||
- ID — мелкий, серый (техническая информация)
|
||||
- Default — заметный (юзеру важно что будет если не указать)
|
||||
- Description/Constraints — мелкий серый (доп. информация)
|
||||
- Без переносов в коде, description — с переносами
|
||||
|
||||
## Сервисы с подробным MAN (для LLM-обработки)
|
||||
|
||||
| Сервис | MAN (символов) |
|
||||
|---|---|
|
||||
| rabbitmq | 9811 |
|
||||
| mongodb | 9296 |
|
||||
| postgres | 8252 |
|
||||
| gitea | 6639 |
|
||||
| clickhouse | 6010 |
|
||||
| flask | 5569 |
|
||||
| kafka | 5210 |
|
||||
| vapp | 4659 |
|
||||
| akhq | 3838 |
|
||||
|
||||
## LLM-промпт (на один сервис)
|
||||
|
||||
LLM получает ВСЕ .md файлы сервиса + ключевые поля из YAML + MAN и переписывает их.
|
||||
|
||||
### Формат входа
|
||||
```
|
||||
Сервис: <service_name>
|
||||
YAML (ключевое): параметры, типы, defaults, constraints
|
||||
MAN: <service_man>
|
||||
Файлы:
|
||||
=== Name.md ===
|
||||
<содержимое>
|
||||
...
|
||||
```
|
||||
|
||||
### Формат выхода
|
||||
```json
|
||||
{
|
||||
"Name.md": "полный текст",
|
||||
"Name_example.md": "полный текст",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Правила для LLM
|
||||
1. **YAML = истина. MAN = контекст.** Если противоречат → верить YAML.
|
||||
2. HTML-таблицы: менять ТОЛЬКО текст внутри `<td>`, НЕ трогать структуру тегов.
|
||||
3. HCL-блоки: НЕ трогать.
|
||||
4. Navigation-строки: НЕ трогать.
|
||||
5. Имена ресурсов (nubes_*): НЕ менять.
|
||||
6. MAN-секцию: перевести из HTML в читаемый Markdown.
|
||||
7. Описания: сделать грамотными, логичными, на русском. Добавить контекст из MAN где уместно.
|
||||
8. **Юзер в первую очередь смотрит на: имя переменной, required/default, что делает параметр.** Это должно быть максимально понятно.
|
||||
|
||||
## Сборка mkdocs: слияние статического и динамического nav
|
||||
|
||||
mkdocs-material не имеет встроенного `!include` для `nav:`. Решение (рекомендовано): расширить Python pre-build шаг в `04_build_and_publish_docs.sh`:
|
||||
|
||||
1. `WriteNavFragment()` генерирует `_nav_fragment.yml` с `resources_nav:` (категории + ресурсы)
|
||||
2. Python-блок читает `_nav_fragment.yml`, парсит `mkdocs.yml`, вставляет ресурсы в секцию `Ресурсы` внутри `nav:`
|
||||
3. Одновременно копирует `30_registry/` в `docs_dir` (чтобы guides не ломались при смене `docs_dir`)
|
||||
4. Результат пишется в `.mkdocs.tmp.yml` → `mkdocs build -f .mkdocs.tmp.yml`
|
||||
|
||||
Альтернативы (отвергнуты):
|
||||
- docs-generator пишет полный mkdocs.yml (слишком хрупко)
|
||||
- mkdocs-awesome-pages (не решает проблему merge static+dynamic)
|
||||
|
||||
## Аудит соответствия YAML ↔ Доки (2026-08-10)
|
||||
|
||||
**Метод:** сравнение всех 37 YAML-спеков со сгенерированными `_params_create.md` и `_params_modify.md`.
|
||||
|
||||
### Итоги
|
||||
|
||||
| Метрика | YAML | Доки | Статус |
|
||||
|---------|------|------|--------|
|
||||
| Сервисов | 37 | 37 | ✅ |
|
||||
| CREATE params (top-level) | 204 | 204 | ✅ 1:1 |
|
||||
| MODIFY params (top-level) | 102 | 101 | ⚠️ -1 |
|
||||
| Sub-params (nested) | — | 199 | ✅ развёрнуты |
|
||||
|
||||
### Расхождения
|
||||
|
||||
| # | Сервис | Проблема | Причина | Действие |
|
||||
|---|--------|----------|---------|----------|
|
||||
| 1 | vc_vm_v2 | Нет страниц | Закомментирован в `services_list.txt` (`# нет в TEST UI`) | Не баг |
|
||||
| 2 | s3, s3bucket, dummy, vc_nsxt, vcexternalip, vc_vm_v3 | 8 пустых типов | Поле `type` не заполнено в YAML | Косметика |
|
||||
|
||||
### Вывод
|
||||
|
||||
Все параметры из YAML **полностью** присутствуют в документации. CamelCase-имена корректно конвертируются в snake_case через `ToSnake()`. Единственный «missing» сервис (vc_vm_v2) исключён из генерации намеренно. 8 пустых типов — пробелы в исходных YAML-спеках, не влияют на корректность.
|
||||
@@ -1,638 +0,0 @@
|
||||
# План миграции в Nubes Managed Kubernetes — Инструкции для агента
|
||||
|
||||
**Создан:** 2026-03-13 (Opus 4.6)
|
||||
**Исполнитель:** Sonnet 4.6
|
||||
**Статус:** Ожидает исполнения
|
||||
|
||||
> **ВАЖНО:** Этот документ — пошаговый план. Каждый пункт содержит:
|
||||
> - ЧТО делать (цель)
|
||||
> - ГДЕ делать (файлы)
|
||||
> - КАК делать (конкретные инструкции)
|
||||
> - ОГРАНИЧЕНИЯ (что нельзя трогать)
|
||||
|
||||
---
|
||||
|
||||
## Контекст
|
||||
|
||||
Terraform-провайдер Nubes Cloud сейчас работает на self-managed K8s (registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> <!-- ⛔ LEGACY: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->).
|
||||
Цель: подготовить инфраструктуру к передаче в Nubes Managed Kubernetes под управление их DevOps.
|
||||
|
||||
Nubes (nubes.ru) — российский cloud-провайдер, собственный DC Tier III (Москва), имеет Managed K8s, Harbor, S3.
|
||||
|
||||
**Репозиторий:** `/home/naeel/remote_dev/terraform`
|
||||
|
||||
**Обязательно прочитать перед работой:**
|
||||
- `REPO_CONTENTS.md` — карта репозитория
|
||||
- `.github/copilot-instructions.md` — правила работы (IMMUTABILITY POLICY)
|
||||
- `docs/CODEBASE_ANALYSIS_AND_ROADMAP.md` — анализ кодовой базы
|
||||
|
||||
---
|
||||
|
||||
## Группа A: Подготовительные задачи (делать ПЕРВЫМИ)
|
||||
|
||||
### A1. Helm Chart для всех K8s-манифестов
|
||||
|
||||
**Цель:** Конвертировать raw YAML манифесты в Helm chart.
|
||||
|
||||
**Исходные файлы (ТОЛЬКО ЧИТАТЬ, НЕ МЕНЯТЬ):**
|
||||
- `k8s/registry-deployment-new.yaml`
|
||||
- `k8s/registry-ingress-new.yaml`
|
||||
- `operator/manifests/00-namespace.yaml`
|
||||
- `operator/manifests/00-rbac.yaml`
|
||||
- `operator/manifests/02-crd.yaml`
|
||||
- `operator/manifests/03-build-script.yaml`
|
||||
- `operator/manifests/04-operator-deployment.yaml`
|
||||
- `operator/manifests/05-registry-server.yaml`
|
||||
- `operator/manifests/06-registry-server-docs.yaml`
|
||||
|
||||
**Создать:**
|
||||
```
|
||||
charts/
|
||||
terraform-registry/
|
||||
Chart.yaml
|
||||
values.yaml
|
||||
values-dev.yaml
|
||||
values-prod.yaml
|
||||
templates/
|
||||
_helpers.tpl
|
||||
namespace.yaml
|
||||
rbac.yaml
|
||||
crd.yaml
|
||||
build-script-configmap.yaml
|
||||
operator-deployment.yaml
|
||||
registry-server-deployment.yaml
|
||||
registry-server-service.yaml
|
||||
docs-server-deployment.yaml (optional)
|
||||
ingress.yaml
|
||||
pdb.yaml
|
||||
networkpolicy.yaml
|
||||
```
|
||||
|
||||
**Требования к `values.yaml`:**
|
||||
```yaml
|
||||
global:
|
||||
registryHostname: "registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->" # Переопределяется при миграции
|
||||
namespace: "terraform-registry"
|
||||
|
||||
registry:
|
||||
image:
|
||||
repository: "naeel/terraform-registry-server" # → Harbor при миграции
|
||||
tag: "latest"
|
||||
pullPolicy: IfNotPresent
|
||||
replicas: 1 # → 2 в prod
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
service:
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
healthcheck:
|
||||
enabled: true
|
||||
path: /healthz
|
||||
port: 8080
|
||||
|
||||
operator:
|
||||
image:
|
||||
repository: "naeel/terraform-registry-operator"
|
||||
tag: "latest"
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 128Mi
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: "nginx"
|
||||
host: "registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->" # Переопределяется
|
||||
tls:
|
||||
enabled: true
|
||||
issuer: "letsencrypt-prod" # НЕ ТРОГАТЬ LetsEncrypt issuer!
|
||||
secretName: "registry-tls"
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
||||
|
||||
s3:
|
||||
endpoint: "s3.msk-1.ngcloud.ru"
|
||||
bucket: "terraform-registry"
|
||||
useSSL: true
|
||||
# credentials через existingSecret
|
||||
existingSecret: "s3-credentials"
|
||||
accessKeyField: "access-key"
|
||||
secretKeyField: "secret-key"
|
||||
|
||||
pdb:
|
||||
enabled: false # → true в prod
|
||||
minAvailable: 1
|
||||
|
||||
networkPolicy:
|
||||
enabled: false # → true при миграции
|
||||
```
|
||||
|
||||
**Требования к `values-dev.yaml`:**
|
||||
```yaml
|
||||
global:
|
||||
registryHostname: "registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->"
|
||||
registry:
|
||||
replicas: 1
|
||||
pdb:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
**Требования к `values-prod.yaml`:**
|
||||
```yaml
|
||||
global:
|
||||
registryHostname: "registry.nubes.ru" # Целевой домен
|
||||
registry:
|
||||
image:
|
||||
repository: "pearlharbor.registryk8s.services.ngcloud.ru/terraform/registry-server"
|
||||
replicas: 2
|
||||
operator:
|
||||
image:
|
||||
repository: "pearlharbor.registryk8s.services.ngcloud.ru/terraform/registry-operator"
|
||||
pdb:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
networkPolicy:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
**Ограничения:**
|
||||
- НЕ менять исходные YAML в `k8s/` и `operator/manifests/` (они могут ещё использоваться)
|
||||
- НЕ трогать CRD-ресурсы с LetsEncrypt issuerRef
|
||||
- НЕ запускать `helm install/upgrade` — только создать файлы
|
||||
- Helm chart — НОВЫЕ файлы в `charts/` (APPEND ONLY)
|
||||
|
||||
---
|
||||
|
||||
### A2. Externalize hardcoded values
|
||||
|
||||
**Цель:** Убрать все hardcoded пути и домены, заменить на env vars.
|
||||
|
||||
**Файл 1: `internal/core/client.go` строка ~18**
|
||||
```go
|
||||
// СЕЙЧАС:
|
||||
f, err := os.OpenFile("/home/naeel/terra/debug_nubes.log", ...)
|
||||
|
||||
// НОВЫЙ КОД (добавить новую функцию в КОНЕЦ файла):
|
||||
func debugLogPath() string {
|
||||
if p := os.Getenv("NUBES_DEBUG_LOG"); p != "" {
|
||||
return p
|
||||
}
|
||||
return filepath.Join(os.TempDir(), "nubes_debug.log")
|
||||
}
|
||||
```
|
||||
|
||||
**Ограничение:** НЕ менять строку 18 напрямую. Добавить функцию `debugLogPath()` в КОНЕЦ файла. Спросить оператора перед заменой вызова.
|
||||
|
||||
**Файл 2: `internal/provider/provider.go` строка ~103**
|
||||
```go
|
||||
// СЕЙЧАС:
|
||||
InsecureSkipVerify: true,
|
||||
|
||||
// НУЖНО: Сделать конфигурируемым через provider schema + env var
|
||||
```
|
||||
|
||||
**Инструкция:**
|
||||
1. Добавить атрибут `insecure` в schema провайдера (Optional, bool, default false)
|
||||
2. Добавить чтение env var `NUBES_INSECURE`
|
||||
3. InsecureSkipVerify = config_value || env_value || false
|
||||
4. Код добавлять В КОНЕЦ секции Configure(), не рефакторить существующий
|
||||
|
||||
**Файл 3: `universal_rebuild/internal/provider/provider.go`**
|
||||
- Проверить аналогичную проблему с InsecureSkipVerify
|
||||
- Применить тот же паттерн
|
||||
|
||||
**Ограничения:**
|
||||
- НЕ менять сигнатуры существующих функций
|
||||
- Новый код — APPEND ONLY
|
||||
- InsecureSkipVerify=false по умолчанию (breaking change для текущих юзеров — СПРОСИТЬ оператора)
|
||||
|
||||
---
|
||||
|
||||
### A3. Health endpoints для registry-server
|
||||
|
||||
**Цель:** Добавить `/healthz`, `/readyz`, `/metrics` endpoints.
|
||||
|
||||
**Файл:** `registry-server-build/main.go`
|
||||
|
||||
**Инструкция:**
|
||||
1. Прочитать текущий `main.go` полностью
|
||||
2. Добавить в КОНЕЦ файла (новые handler-функции):
|
||||
```go
|
||||
func healthzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
func readyzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Проверить доступность S3
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
}
|
||||
```
|
||||
3. Зарегистрировать handlers в main() — СПРОСИТЬ оператора перед добавлением в mux
|
||||
|
||||
**Ограничения:**
|
||||
- НЕ менять существующие handlers
|
||||
- НЕ запускать docker build
|
||||
- Новые функции — APPEND ONLY
|
||||
|
||||
---
|
||||
|
||||
### A4. Operaционная документация
|
||||
|
||||
**Цель:** Создать набор операционных документов для DevOps Nubes.
|
||||
|
||||
**Создать файлы:**
|
||||
|
||||
**`docs/ops/RUNBOOK.md`:**
|
||||
```markdown
|
||||
# Runbook: Terraform Provider Registry
|
||||
|
||||
## Предпосылки
|
||||
- Kubernetes cluster ≥ 1.27
|
||||
- Helm ≥ 3.12
|
||||
- Доступ к S3 (s3.msk-1.ngcloud.ru)
|
||||
- Harbor registry (для образов)
|
||||
|
||||
## Установка
|
||||
helm install terraform-registry ./charts/terraform-registry \
|
||||
-f charts/terraform-registry/values-prod.yaml \
|
||||
-n terraform-registry --create-namespace
|
||||
|
||||
## Обновление версии
|
||||
1. Собрать новый образ (CI pipeline)
|
||||
2. Обновить tag в values
|
||||
3. helm upgrade terraform-registry ./charts/terraform-registry -f values-prod.yaml
|
||||
|
||||
## Проверка здоровья
|
||||
kubectl -n terraform-registry get pods
|
||||
curl https://<REGISTRY_HOST>/healthz
|
||||
curl https://<REGISTRY_HOST>/.well-known/terraform.json
|
||||
|
||||
## Компоненты
|
||||
- Registry Server — HTTP-сервер протокола Terraform Registry
|
||||
- Operator — K8s controller для сборки provider binaries
|
||||
- S3 — хранилище артефактов (бинарники + документация)
|
||||
```
|
||||
|
||||
**`docs/ops/TROUBLESHOOTING.md`:**
|
||||
```markdown
|
||||
# Troubleshooting
|
||||
|
||||
## Registry Server не отвечает
|
||||
1. kubectl -n terraform-registry get pods -l app=registry-server
|
||||
2. kubectl -n terraform-registry logs -l app=registry-server --tail=100
|
||||
3. Проверить ingress: kubectl get ingress -n terraform-registry
|
||||
4. Проверить S3: curl -s https://s3.msk-1.ngcloud.ru (bucket access)
|
||||
|
||||
## Provider binary не скачивается
|
||||
1. Проверить наличие в S3: s3cmd ls s3://terraform-registry/terraform-providers/...
|
||||
2. Проверить SHA256SUMS сигнатуру
|
||||
3. Проверить GPG ключ
|
||||
|
||||
## Operator не создаёт build job
|
||||
1. kubectl -n terraform-registry get terraformproviderrelease
|
||||
2. kubectl -n terraform-registry describe terraformproviderrelease <name>
|
||||
3. kubectl -n terraform-registry get jobs
|
||||
4. Проверить RBAC: operator ServiceAccount должен иметь права на jobs и secrets
|
||||
|
||||
## TLS / Certificate проблемы
|
||||
- Проверить cert-manager: kubectl get certificates -n terraform-registry
|
||||
- ⚠️ НЕ пересоздавать certificates с LetsEncrypt issuer (rate limits!)
|
||||
- Для отладки использовать self-signed issuer
|
||||
```
|
||||
|
||||
**`docs/ops/MONITORING.md`:**
|
||||
```markdown
|
||||
# Мониторинг
|
||||
|
||||
## Ключевые метрики
|
||||
- registry_http_requests_total — кол-во запросов к registry
|
||||
- registry_http_request_duration_seconds — latency
|
||||
- registry_s3_operations_total — операции с S3
|
||||
- registry_s3_errors_total — ошибки S3
|
||||
|
||||
## Алерты (Prometheus)
|
||||
- RegistryDown: up == 0 (>2 min)
|
||||
- RegistryHighLatency: p99 > 5s (>5 min)
|
||||
- RegistryS3Errors: rate > 0.1/s (>5 min)
|
||||
- RegistryPodRestart: увеличение restart count
|
||||
|
||||
## Grafana Dashboard
|
||||
- Import dashboard ID: (создать при установке мониторинга)
|
||||
```
|
||||
|
||||
**`docs/ops/UPGRADE.md`:**
|
||||
```markdown
|
||||
# Процедура обновления
|
||||
|
||||
## Provider version update (без downtime)
|
||||
1. CI собирает новый provider binary
|
||||
2. Создать TerraformProviderRelease CR с новой версией
|
||||
3. Operator создаёт build job → артефакты в S3
|
||||
4. Старые версии остаются доступны (immutable artifacts)
|
||||
|
||||
## Registry Server update (rolling)
|
||||
1. Обновить image tag в Helm values
|
||||
2. helm upgrade --set registry.image.tag=<new> terraform-registry ./charts/...
|
||||
3. Проверить: kubectl rollout status deployment/registry-server -n terraform-registry
|
||||
4. Rollback: helm rollback terraform-registry 1
|
||||
|
||||
## Operator update
|
||||
1. Обновить operator image tag
|
||||
2. helm upgrade ...
|
||||
3. Проверить CRD compatibility: kubectl get crd terraformproviderreleases.terra.core.nubes.ru
|
||||
```
|
||||
|
||||
**`docs/ops/ROLLBACK.md`:**
|
||||
```markdown
|
||||
# Процедура отката
|
||||
|
||||
## Helm rollback
|
||||
helm rollback terraform-registry <revision>
|
||||
helm history terraform-registry -n terraform-registry
|
||||
|
||||
## Emergency: Direct image rollback
|
||||
kubectl -n terraform-registry set image deployment/registry-server \
|
||||
registry-server=<HARBOR>/terraform/registry-server:<PREV_TAG>
|
||||
|
||||
## S3 artifacts (immutable — откат не нужен)
|
||||
Все версии provider binary хранятся бессрочно.
|
||||
Удаление только вручную через s3cmd.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Группа B: Инфраструктурная подготовка
|
||||
|
||||
### B1. Dockerfile оптимизация
|
||||
|
||||
**Цель:** Убедиться что Dockerfile для registry-server и operator готовы к Harbor.
|
||||
|
||||
**Инструкция:**
|
||||
1. Прочитать `registry-server-build/` и `operator/build/`
|
||||
2. Проверить что Dockerfile использует multi-stage build
|
||||
3. Проверить что нет hardcoded путей
|
||||
4. Убедиться что base image — official (golang:1.24 + alpine/scratch)
|
||||
5. НЕ запускать docker build — только проверить файлы
|
||||
|
||||
**Создать (если отсутствует):** `registry-server-build/.dockerignore`, `operator/.dockerignore`
|
||||
|
||||
---
|
||||
|
||||
### B2. CI Pipeline definition
|
||||
|
||||
**Цель:** Создать файл CI pipeline (GitLab CI / Tekton) для автосборки.
|
||||
|
||||
**Создать:** `devops/ci/pipeline.yaml`
|
||||
|
||||
```yaml
|
||||
# GitLab CI - пример (адаптировать под конкретный CI Nubes)
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- sign
|
||||
- publish
|
||||
|
||||
variables:
|
||||
HARBOR_HOST: "pearlharbor.registryk8s.services.ngcloud.ru"
|
||||
S3_BUCKET: "terraform-registry"
|
||||
PROVIDER_NAME: "nubes"
|
||||
PROVIDER_NAMESPACE: "nubes"
|
||||
|
||||
test:
|
||||
stage: test
|
||||
image: golang:1.24
|
||||
script:
|
||||
- cd universal_rebuild
|
||||
- go test ./...
|
||||
- go vet ./...
|
||||
|
||||
build-provider:
|
||||
stage: build
|
||||
image: golang:1.24
|
||||
script:
|
||||
- cd universal_rebuild
|
||||
- GOOS=linux GOARCH=amd64 go build -o bin/terraform-provider-${PROVIDER_NAME}_linux_amd64
|
||||
- GOOS=darwin GOARCH=amd64 go build -o bin/terraform-provider-${PROVIDER_NAME}_darwin_amd64
|
||||
- GOOS=windows GOARCH=amd64 go build -o bin/terraform-provider-${PROVIDER_NAME}_windows_amd64.exe
|
||||
artifacts:
|
||||
paths: [universal_rebuild/bin/]
|
||||
|
||||
build-images:
|
||||
stage: build
|
||||
script:
|
||||
- docker build -t ${HARBOR_HOST}/terraform/registry-server:${CI_COMMIT_TAG} registry-server-build/
|
||||
- docker build -t ${HARBOR_HOST}/terraform/registry-operator:${CI_COMMIT_TAG} operator/
|
||||
- docker push ${HARBOR_HOST}/terraform/registry-server:${CI_COMMIT_TAG}
|
||||
- docker push ${HARBOR_HOST}/terraform/registry-operator:${CI_COMMIT_TAG}
|
||||
|
||||
sign:
|
||||
stage: sign
|
||||
script:
|
||||
- cd universal_rebuild/bin
|
||||
- sha256sum terraform-provider-* > SHA256SUMS
|
||||
- gpg --import $GPG_PRIVATE_KEY
|
||||
- gpg --detach-sign SHA256SUMS
|
||||
|
||||
publish-to-s3:
|
||||
stage: publish
|
||||
script:
|
||||
- s3cmd put bin/* s3://${S3_BUCKET}/terraform-providers/...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### B3. NetworkPolicy template
|
||||
|
||||
**Цель:** Подготовить NetworkPolicy для изоляции namespace.
|
||||
|
||||
**Включить в Helm chart:** `charts/terraform-registry/templates/networkpolicy.yaml`
|
||||
|
||||
```yaml
|
||||
{{- if .Values.networkPolicy.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ include "terraform-registry.fullname" . }}-netpol
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: ingress-nginx
|
||||
ports:
|
||||
- port: {{ .Values.registry.service.targetPort }}
|
||||
protocol: TCP
|
||||
egress:
|
||||
- to: []
|
||||
ports:
|
||||
- port: 443 # S3, Vault
|
||||
protocol: TCP
|
||||
- port: 53 # DNS
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Группа C: Domain Migration Plan
|
||||
|
||||
### C1. Domain migration (порядок действий)
|
||||
|
||||
**Это НЕ код — это инструкция для DevOps. Записать в `docs/ops/DOMAIN_MIGRATION.md`:**
|
||||
|
||||
```markdown
|
||||
# Миграция домена registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> → registry.nubes.ru
|
||||
|
||||
## Фаза 1: Dual-domain (параллельная работа)
|
||||
1. Настроить Ingress с двумя hosts: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> + registry.nubes.ru
|
||||
2. Оба домена указывают на один Registry Server
|
||||
3. Обновить provider main.go: Address → registry.nubes.ru
|
||||
4. Старый адрес registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> продолжает работать
|
||||
|
||||
## Фаза 2: Миграция клиентов
|
||||
1. Документировать новый registry address для пользователей
|
||||
2. .terraformrc mirror config для переходного периода:
|
||||
provider_installation {
|
||||
direct {
|
||||
exclude = ["registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->/*/*"]
|
||||
}
|
||||
network_mirror {
|
||||
url = "https://registry.nubes.ru/v1/providers/"
|
||||
}
|
||||
}
|
||||
|
||||
## Фаза 3: Редирект
|
||||
1. registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> Ingress → 301 redirect на registry.nubes.ru
|
||||
2. Мониторинг: отслеживать запросы на старый домен
|
||||
|
||||
## Фаза 4: Деком (через 6+ месяцев)
|
||||
1. Убрать registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> из Ingress
|
||||
2. DNS → удалить A/CNAME запись
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Группа D: Code Quality (из CODEBASE_ANALYSIS_AND_ROADMAP.md)
|
||||
|
||||
### D1. Deprecated-маркеры для legacy CreateGenericInstance
|
||||
|
||||
**Файл:** `internal/core/client.go`
|
||||
|
||||
**Инструкция:** Добавить КОММЕНТАРИИ (не код) перед каждым методом V1-V5:
|
||||
```go
|
||||
// Deprecated: Use CreateGenericInstanceUniversalV5 instead.
|
||||
// This method is kept for backward compatibility and will be removed in v3.0.
|
||||
func (c *UniversalClient) CreateGenericInstance(...) ...
|
||||
```
|
||||
|
||||
**Методы для пометки:**
|
||||
- `CreateGenericInstance` (V1)
|
||||
- `CreateGenericInstanceUniversal` (V2)
|
||||
- `CreateGenericInstanceUniversalV2` (V3)
|
||||
- `CreateGenericInstanceUniversalV3` (V4)
|
||||
- `CreateGenericInstanceUniversalV4` (V5)
|
||||
|
||||
**Ограничения:** ТОЛЬКО комментарии. НЕ менять код методов. НЕ удалять.
|
||||
|
||||
---
|
||||
|
||||
### D2. .gitignore для secrets
|
||||
|
||||
**Файл:** `.gitignore` (корень репозитория)
|
||||
|
||||
**Добавить в КОНЕЦ файла:**
|
||||
```gitignore
|
||||
# Secrets (should be in Vault, not in git)
|
||||
secrets/*.asc
|
||||
secrets/*.token
|
||||
secrets/*.key
|
||||
!secrets/.gitkeep
|
||||
```
|
||||
|
||||
**Создать:** `secrets/.gitkeep` (пустой файл, чтобы директория осталась в git)
|
||||
|
||||
---
|
||||
|
||||
### D3. Unit tests для core layer
|
||||
|
||||
**Цель:** Создать минимальный набор тестов.
|
||||
|
||||
**Создать файлы:**
|
||||
- `universal_rebuild/internal/core/client_test.go`
|
||||
- `universal_rebuild/internal/resources_core/crud_test.go`
|
||||
|
||||
**Минимальные тесты для `client_test.go`:**
|
||||
- `TestNormalizeValue_EmptyString`
|
||||
- `TestNormalizeValue_NullString`
|
||||
- `TestNormalizeValue_MapType`
|
||||
- `TestNormalizeValue_ArrayType`
|
||||
- `TestNormalizeValue_TrimSpace`
|
||||
|
||||
**Минимальные тесты для `crud_test.go`:**
|
||||
- `TestIsStatusSuspended`
|
||||
- `TestIsStatusNonAdoptable`
|
||||
- `TestDeleteBehaviorDefault`
|
||||
|
||||
**Ограничения:**
|
||||
- Тесты — НОВЫЕ файлы (не менять существующие)
|
||||
- Использовать стандартный `testing` пакет Go
|
||||
- НЕ запускать тесты (`go test`) без разрешения оператора
|
||||
|
||||
---
|
||||
|
||||
## Порядок выполнения
|
||||
|
||||
```
|
||||
ФАЗА 0 (быстрые wins):
|
||||
D1 → Deprecated комментарии [5 мин]
|
||||
D2 → .gitignore для secrets [2 мин]
|
||||
A2 → debugLogPath() function [10 мин]
|
||||
|
||||
ФАЗА 1 (Helm chart):
|
||||
A1 → Полный Helm chart [30-60 мин]
|
||||
|
||||
ФАЗА 2 (Ops docs):
|
||||
A4 → RUNBOOK, TROUBLESHOOTING и др. [20 мин]
|
||||
C1 → DOMAIN_MIGRATION.md [10 мин]
|
||||
|
||||
ФАЗА 3 (Code quality):
|
||||
D3 → Unit tests [30 мин]
|
||||
A3 → Health endpoints [15 мин]
|
||||
|
||||
ФАЗА 4 (CI/CD):
|
||||
B2 → CI pipeline definition [15 мин]
|
||||
B1 → Dockerfile audit [10 мин]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Правила для агента (напоминание)
|
||||
|
||||
1. **IMMUTABILITY POLICY** — НЕ менять существующий рабочий код
|
||||
2. **APPEND ONLY** — новый код только в конец файла
|
||||
3. **Комментарии — ЭТО НЕ ПРАВКА КОДА**, их можно и нужно добавлять
|
||||
4. **НЕ запускать** docker build, kubectl apply, helm install
|
||||
5. **НЕ трогать** ресурсы с LetsEncrypt issuerRef
|
||||
6. **Спрашивать разрешения** перед изменением сигнатур функций
|
||||
7. **Secrets в коде** — КАТЕГОРИЧЕСКИ нет
|
||||
8. **Перед любой работой** — прочитать `REPO_CONTENTS.md`
|
||||
9. **Файлы в `internal/resources_gen/`** — НЕ МЕНЯТЬ ВРУЧНУЮ (только через генератор)
|
||||
10. **Минимальные ресурсы** — при создании K8s ресурсов ставить минимальный CPU/memory
|
||||
@@ -1,122 +0,0 @@
|
||||
# Ответ Opus: анализ решения IaC-развёртывания Штурвала (модификаторы + скрытые зависимости)
|
||||
|
||||
**Дата:** 2026-09-23
|
||||
**Связанный промпт:** `docs/prompts/prompt_for_opus_iac_shturval_modify.md`
|
||||
**Связанный анализ:** `docs/SHTURVAL_IAC_MODIFY_ANALYSIS_2026-09-23.md`
|
||||
**Статус:** документирование ответа. Конкретный план НЕ составляется.
|
||||
|
||||
> ⚠️ **ВАЖНАЯ ПОПРАВКА (2026-09-23, позже).** Ответ Опуса ниже строился на НЕВЕРНОЙ посылке «`vIPConfigure` — накопительный API». Это опровергнуто тестом `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md`: `vIPConfigure` ведёт себя как **replace-состояние** — идемпотентно (1→1), работает в обе стороны (вверх/вниз/до 0), `count` читается из `state.params`. Соответственно «блокеры» (a) Read счётчика и (b) адресное освобождение **сняты как ложные**. Остаётся только (c) Read цепочки `providerVdc → providerGateway → ipSpace`. НЕ использовать прежнюю формулировку «накопительный API несовместим с декларативной моделью» как источник истины.
|
||||
|
||||
---
|
||||
|
||||
## 1. Суть ответа (главный вывод)
|
||||
|
||||
Форма IaC-ресурсов фиксируется **уже сейчас**, потому что она диктуется моделью Terraform (декларативность, идемпотентность, inverse), а не спеками платформы.
|
||||
|
||||
НО есть **три блокера от платформы**, без которых идемпотентность и Delete принципиально недостижимы на стороне провайдера.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ответ Opus по пунктам
|
||||
|
||||
### Пункт 1 — накопительный `vIPConfigure` → идемпотентный ресурс
|
||||
|
||||
- Ресурс отдельный (`nubes_org_vip_allocation`) с `depends_on` на оргу, НЕ операция внутри орги.
|
||||
- **Ключ идемпотентности — желаемое состояние, а не дельта.** Юзер задаёт целевой `count` на `name`; провайдер сам считает `target − current` и модифицирует только разницу.
|
||||
- **Create:** Read текущего числа vIP → выделить `target − current`. Если API не отдаёт «сколько уже есть» — нужен серверный счётчик/тег, иначе идемпотентность недостижима.
|
||||
- **Read:** читать родителя (оргу), извлекать фактическое число IP по `name` в state. Если API не различает «кем/зачем выделено» — Read вернёт общий пул, drift неизбежен.
|
||||
- **Update:** та же дельта-логика (target изменился → доначислить/освободить).
|
||||
- **Delete (inverse):** `modify` с обратным знаком до `count=0` по этому `name`. Требует адресного освобождения конкретных IP. Если освобождение — тоже накопительный modify без адресации, inverse корректно сделать нельзя.
|
||||
|
||||
**Риск (ОПРОВЕРГНУТ позже):** это утверждение строилось на ложной посылке «накопительный API». Факт: `vIPConfigure` — replace-состояние, дельта `target − current` по факту не нужна — достаточно слать целевой `count`, платформа сама выставляет его (идемпотентно). См. `ORG_IP_MODIFIER_TEST_2026-09-22.md`.
|
||||
|
||||
### Пункт 2 — `ipSpaceName` (цепочка providerVdc → providerGateway → ipSpace)
|
||||
|
||||
- Это **выводимое значение из инфраструктуры, НЕ пользовательский ввод** → data-source, а не аргумент ресурса.
|
||||
- Правильно: `data "nubes_ip_space" { org/vdc = ... }`, который проходит цепочку providerVdc → providerGateway → ipSpace и возвращает `name`. Ресурс берёт значение по ссылке.
|
||||
- **Граница «данные vs логика»:** в реестре хранить **тип поля и его источник** (что это computed-from-parent, а не user-input). Сама цепочка обхода — логика data-source, не данные реестра.
|
||||
- НЕ вычислять из state родителя вручную в ресурсе (скрытая связанность, ломается при >1 T0). Data-source явно выражает зависимость в графе tf.
|
||||
- Пока платформа «подкладывает» значение сама — data-source должен уметь то же читать. Если API этой цепочки нет на чтение — **блокер**.
|
||||
|
||||
### Пункт 3 — не завязываться на «один T0»
|
||||
|
||||
- Закладывать **явный селектор шлюза** уже сейчас: `provider_gateway` / `t0_id` как аргумент (или ключ data-source), даже если сегодня один и выводится автоматически (optional + computed default).
|
||||
- vIP-аллокация и SNAT привязывать к **конкретному gateway id**, а не к «дефолтному в орге».
|
||||
- **Что сломается при >1 T0, если не заложить:** `ipSpaceName` станет неоднозначным (несколько ipSpace), vIP-аллокация не будет знать, к какому шлюзу. Придётся менять схему (добавлять обязательный селектор) → breaking change.
|
||||
- **Заложив optional-селектор сейчас:** при росте T0 меняется только default-резолвинг, схема остаётся совместимой.
|
||||
|
||||
### Пункт 4 — ждать спеки или фиксировать форму сейчас
|
||||
|
||||
- **Форму ресурсов можно и нужно фиксировать сейчас** — она диктуется моделью Terraform, а не спеками.
|
||||
- **Не блокер (делаем сейчас):** раздельные ресурсы + `depends_on`; целевое состояние вместо дельты; селектор шлюза; data-source для `ipSpaceName`; inverse через обратный modify.
|
||||
- **Блокер (нужно от платформы) — только один подтверждённый:**
|
||||
- c) API чтения цепочки providerVdc → providerGateway → ipSpace (иначе data-source невозможен).
|
||||
- **Сняты как ложные (опровергнуты тестом 2026-09-22):**
|
||||
- a) чтение текущего числа vIP — УЖЕ работает через `state.params.vIPConfigure`;
|
||||
- b) адресное освобождение IP — УЖЕ работает: `count` меньше/`0` задаётся тем же `modify`, в обе стороны.
|
||||
- **Вывод:** проектируем форму сейчас, блокер только (c). Новые спеки повлияют на **резолвинг значений**, не на форму ресурсов — если форма построена на «целевое состояние + селектор + data-source».
|
||||
|
||||
### Пункт 5 — минимально-инвазивный порядок внедрения
|
||||
|
||||
**От платформы (до кодинга ресурсов) — обязательно:**
|
||||
- Read цепочки → `ipSpaceName` (для data-source).
|
||||
- Подтверждение, что генератор умеет строить схему из объединения `create`+`modify` полей (иначе `vIPConfigure`/`ipSpaceName` вообще не попадут в схему).
|
||||
|
||||
> ⚠️ Read счётчика vIP и адресное освобождение — НЕ блокеры (уже подтверждено тестом). Исключены.
|
||||
|
||||
**На стороне провайдера — можно сейчас, не дожидаясь:**
|
||||
- Раздельные ресурсы vip-allocation / nsxt-snat с `depends_on`.
|
||||
- Логика «target − current = дельта» (заглушка current, пока нет Read).
|
||||
- Data-source-скелет для `ipSpaceName` (с TODO на реальный обход цепочки).
|
||||
- Optional+computed селектор шлюза.
|
||||
- Inverse-контракт (Delete = обратный modify до нуля).
|
||||
|
||||
**Главный неустранимый на нашей стороне блокер:** ❌ СНЯТ — строился на ложной посылке «накопительный API». Реальный остаточный блокер — только (c) чтение цепочки providerVdc → providerGateway → ipSpace (для data-source `ipSpaceName`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Что нового vs то, что уже собирались делать
|
||||
|
||||
### Совпадает со старыми планами (НЕ новое)
|
||||
|
||||
- Отдельный ресурс под модификацию + `depends_on` — было (`PLAN_modifier_redesign.md`, «resource association»).
|
||||
- Inverse через обратный modify (`count→0`) — было (`inverse_rollback_analysis_2026-09-23.md`).
|
||||
- Идемпотентность (skip run, если live уже целевое) — было.
|
||||
- «Не ждать спеки для формы, а фиксировать сейчас» — по сути было.
|
||||
|
||||
### Реально новое у Опуса
|
||||
|
||||
1. **«Целевое состояние, а не дельта»** — строгий принцип: юзер задаёт целевой `count`, провайдер сам считает `target − current`. Старые планы просто «досылали заданные поля», не формализовали желаемое состояние.
|
||||
2. **`ipSpaceName` — data-source, не аргумент ресурса** — сдвиг от «юзер вписывает значение» к «computed-from-parent». Раньше виделось как ввод.
|
||||
3. **Селектор шлюза (`t0_id`/`provider_gateway`) как optional+computed сейчас** — в старых планах про «один T0» вообще не было (пришло только из реплики Виталия).
|
||||
4. **Чёткая граница «данные vs логика»** — в реестре только «тип поля + что computed-from-parent», цепочка обхода — логика data-source.
|
||||
5. **Блокеры от платформы** — из трёх заявленных Опуса два (Read счётчика, адресное освобождение) **ложны** (опровергнуты тестом), остаётся один реальный: Read цепочки providerVdc→providerGateway→ipSpace.
|
||||
|
||||
### Главное отличие одной фразой
|
||||
|
||||
Старые планы отвечали на «**как сделать модификатор в tf**». Опус отвечает на «**как сделать его идемпотентным и IaC-честным**» — но его центральный вывод «ядро проблемы в платформе (накопительный API)» **оказался ошибочным**, т.к. исходная посылка «накопительный» неверна (см. поправку в шапке). Реальный остаток — только `ipSpaceName` (цепочка providerVdc→providerGateway→ipSpace) и селектор шлюза.
|
||||
|
||||
---
|
||||
|
||||
## 4. Спорный/непроверенный момент — РАЗРЕШЁН
|
||||
|
||||
Посылка Опуса «накопительный `vIPConfigure` без Read-счётчика и адресного освобождения несовместим с декларативной моделью» **опровергнута** тестом `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md`:
|
||||
|
||||
- повторный `modify` с тем же `count` не аккумулирует IP (1→1) → идемпотентно;
|
||||
- `count` меняется в обе стороны (2→1→0) через тот же `modify` → «адресное освобождение» не нужно, достаточно задать меньший/нулевой `count`;
|
||||
- `count=0` принимается (несмотря на `minvalue:1` в схеме), элемент ipSpace остаётся в `state.params`;
|
||||
- Read уже есть: `state.params.vIPConfigure = [{"name":"internet-ipv4-v1","count":N}]`.
|
||||
|
||||
Единственный реально непроверенный момент: полное удаление ipSpace (`[]` / отсутствие элемента) — тест этого не покрывал. Для IaC-задачи «обнулить» достаточно, полное удаление — опционально.
|
||||
|
||||
---
|
||||
|
||||
## 5. Резюме (с поправкой)
|
||||
|
||||
- Форма ресурсов — проектируем сейчас, она не зависит от спеков.
|
||||
- `vIPConfigure` — **НЕ блокер**: идемпотентно, обе стороны, `count` читается/задаётся из `state.params` (тест 2026-09-22).
|
||||
- Единственный подтверждённый блокер: **Read цепочки `providerVdc → providerGateway → ipSpace`** для data-source `ipSpaceName` (c).
|
||||
- Непроверено: полное удаление ipSpace (`[]`), но для задачи достаточно `count=0`.
|
||||
- Конкретный план внедрения пока НЕ составляется (по решению пользователя).
|
||||
|
||||
**Следующий возможный шаг (только по запросу):** проверить (c) чтение цепочки ipSpace живым API.
|
||||
@@ -1,35 +0,0 @@
|
||||
# Проверка модификатора vc_org ip_space (modify 207) — 2026-09-22
|
||||
|
||||
Организация: `NarodOrg` (`9890a8a0-040b-4d56-8018-c31519c35a30`), realm `sandbox.nubes.ru`.
|
||||
Стенд: `DEV_STAND/FullPipe`, провайдер `nubes-dev` 2.0.9.
|
||||
|
||||
## Что делали
|
||||
|
||||
1. Переименовали `org_ips.tf` → `terraform apply` — ошибок нет (ресурс ушёл из state; `Delete` модификатора — no-op).
|
||||
2. Вернули файл → `apply` — ошибок нет, **число IP осталось 1** (повторный `modify` с тем же `count=1` НЕ задвоил).
|
||||
3. `org_ip_count = 2` → `apply` — стало 2.
|
||||
4. `org_ip_count = 1` → `apply` — стало 1.
|
||||
5. `org_ip_count = 0` → `apply` — стало 0.
|
||||
|
||||
## Подтверждено по API
|
||||
|
||||
`GET /instances/9890a8a0-040b-4d56-8018-c31519c35a30`:
|
||||
|
||||
- `state.params.vIPConfigure = [{"name":"internet-ipv4-v1","count":0}]`
|
||||
- последняя операция `modify` — `isSuccessful: true`, `isPending: false`, `isInProgress: false`.
|
||||
|
||||
## Выводы (обновляют прежние гипотезы)
|
||||
|
||||
1. **modify работает в обе стороны** (count вверх/вниз/до 0) на здоровой орге, даже при живых дочерних инстансах (VDC/Edge).
|
||||
2. **modify идемпотентен** — повторный `modify` с тем же `count` не аккумулирует IP (1 → 1).
|
||||
3. **`count=0` принимается**, хотя в схеме операции 207 у `count` стоит `minvalue: 1` / `integer > 0` — валидация не отвергает 0. `count=0` = ноль выделенных IP (элемент ipSpace остаётся в state).
|
||||
4. **`Delete` модификатора — no-op** подтверждён (шаг 1), но это восполнимо: повторный `apply` с нужным `count` корректно восстанавливает состояние.
|
||||
|
||||
## Опровергнуто
|
||||
|
||||
- Утверждение из `docs/HAR_SNAT_MODIFY_FINDINGS.md` «уменьшить/удалить ipSpace нельзя, пока существуют дочерние инстансы» — **не подтвердилось на здоровой орге** (в HAR был сломанный Edge; это и было помечено как неподтверждённое наблюдение).
|
||||
- Опасение из Opus-ревью о «двойном выделении при replace/destroy→apply» — в части повторного `modify` с тем же `count` **не воспроизвелось** (идемпотентно).
|
||||
|
||||
## Открытый вопрос
|
||||
|
||||
- Полное удаление ipSpace (пустой массив `[]` / отсутствие элемента) не тестировалось — `count=0` оставляет элемент `{"name":"internet-ipv4-v1","count":0}` в state.
|
||||
@@ -1,169 +0,0 @@
|
||||
# Штурвал через IaC: анализ проблемы `modify` и скрытых платформенных зависимостей
|
||||
|
||||
**Дата:** 2026-09-23
|
||||
**Контекст:** дискуссия в Telegram про запуск цепочки Штурвал полностью через Terraform.
|
||||
|
||||
---
|
||||
|
||||
## 1. Исходная задача
|
||||
|
||||
Клиенту нужен IaC (Infrastructure as Code): вся инфраструктура описывается одним конфигом, команда `terraform apply` разворачивает её целиком, `plan`/`destroy` дают полную картину. Никаких обязательных ручных шагов посередине.
|
||||
|
||||
Цепочка для стенда Штурвал:
|
||||
|
||||
```text
|
||||
vcOrg -> create
|
||||
vcVdc -> create
|
||||
vcNsxt -> create
|
||||
------------------------------
|
||||
vcOrg -> modify (аллоцировать внешние IP в организацию)
|
||||
vcNsxt -> modify (включить SNAT, указать внешний IP из vcOrg)
|
||||
------------------------------
|
||||
k8sShturval -> create
|
||||
```
|
||||
|
||||
Ключевой конфликт: `create` у Terraform работает штатно, а операции `modify` в текущем провайдере никак не выражаются — Terraform не умеет «создать ресурс, а через несколько шагов поменять в нём же параметр».
|
||||
|
||||
---
|
||||
|
||||
## 2. Почему `modify` не выражается в текущем провайдере
|
||||
|
||||
### 2.1. Генератор строит схему только из `create`
|
||||
|
||||
Провайдер генерируется из YAML-спеков (`generated/{stand}/resources_yaml/*.yaml`). Схема ресурса (какие поля можно писать в `.tf`) строится **только из операции `create`**. Параметры, которые есть только в `modify`, в схему не попадают.
|
||||
|
||||
Подтверждено по файлам:
|
||||
|
||||
- `generated/dev/resources_yaml/19_vc_org.yaml`:
|
||||
- `create` (id 136) → только `resourceRealm` (418), `organizationType` (556), `orgSuffix` (1125);
|
||||
- `vIPConfigure` (выделение внешних IP) есть **только** в `modify` (id 207), с sub-полями `name` (39) и `count` (40).
|
||||
- `generated/dev/resources_yaml/22_vc_nsxt.yaml`:
|
||||
- `create` (id 10) → `vdcUid`, `needEnableAVI` (340), `virtualServicesCount` (341), `qosProfile` (825), `routedNetConfiguration` (1110) и др.;
|
||||
- `ipSpaceName` (372) есть **только** в `modify` (id 111).
|
||||
|
||||
Вывод: `vIPConfigure` (vc_org) и `ipSpaceName` (vc_nsxt) живут только в `modify`, в схеме tf-ресурсов их нет. Поэтому «прописать параметр в tf и сделать apply» падает ещё на `plan` (атрибут не известен провайдеру).
|
||||
|
||||
### 2.2. Эти параметры — не «настройки», а отложенные действия
|
||||
|
||||
- `vIPConfigure=[{name,count}]` — задаёт желаемое число внешних IP целиком. **Не накопительная** (повторный вызов с тем же `count` не аккумулирует, подтверждено `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md`), работает в обе стороны (вверх/вниз/до `count=0`). Это **декларативное значение** в смысле «желаемое количество IP по данному ipSpace».
|
||||
- `ipSpaceName` — включение SNAT на конкретный ipSpace, который возникает **только после** того, как на орге выделены IP.
|
||||
- Эти операции требуют порядка (org.modify → затем nsxt.modify) и зависят от живого состояния инстанса, а не от дефолтов формы.
|
||||
|
||||
### 2.3. Схема в state ≠ реальное состояние
|
||||
|
||||
Вписывать недостающие параметры «насильно» в tfstate нельзя и бесполезно:
|
||||
|
||||
1. Terraform валидирует атрибуты по **схеме провайдера**, а не по state — неизвестный атрибут будет отброшен/вызовет ошибку.
|
||||
2. Записывать в state «SNAT включён», когда этого нет на площадке, — значит получить ложный `plan` (чистый) при сломанной инфраструктуре.
|
||||
3. Ручная правка tfstate/`state push` ломает целостность (серийник, конфликты на следующем apply).
|
||||
|
||||
Работает только косвенно: `terraform_data`/`null_resource` + `local-exec` → в state попадает **факт** «операция выполнена» (маркер с `triggers`), но не **состояние** SNAT/IP. Порядок задаётся через `depends_on`, но дрейф по самим параметрам `plan` не видит.
|
||||
|
||||
---
|
||||
|
||||
## 3. Каноничное решение: отдельный ресурс (resource association)
|
||||
|
||||
Это принятая в Terraform практика — «resource association / separate resource». Классические примеры:
|
||||
|
||||
- `aws_security_group` + `aws_security_group_rule`
|
||||
- `aws_vpc` + `aws_route_table_association`
|
||||
- `google_project` + `google_project_iam_member`
|
||||
|
||||
Базовый ресурс создаётся отдельно, а донастройка/привязка — отдельным ресурсом с `depends_on`. Граф сам выстраивает порядок, `destroy` разворачивает его корректно.
|
||||
|
||||
### 3.1. Прецедент из Cloud Director (VCD)
|
||||
|
||||
В репозитории лежит сторонний шаблон — `/home/naeel/TF/tf_provider/!/` (network.tf.tmpl, vmware_org.tf, vdc.tf), показывающий, как та же цепочка делается провайдером VMware Cloud Director:
|
||||
|
||||
- `resource "vcd_nsxt_alb_settings"` — включение ALB, `count = var.alb_enable ? 1 : 0`, `depends_on = [vcd_nsxt_edgegateway...]`;
|
||||
- `resource "vcd_nsxt_alb_edgegateway_service_engine_group"` — выделение SE, `reserved_virtual_services = var.alb_segroup_count`;
|
||||
- `resource "vcd_network_routed_v2"` — routed-сеть, `edge_gateway_id`, `dns1/dns2/static_ip_pool`;
|
||||
- `resource "vcd_ip_space_custom_quota"` — квота IP на **оргу**, `depends_on = [edge]`.
|
||||
|
||||
Приём «включить/выключить» = `count`. Обратная операция (выключить ALB / снять квоту) получается **удалением ресурса** — inverse логика не нужна.
|
||||
|
||||
Маппинг на наши сервисы:
|
||||
|
||||
| Nubes API | Канон VCD |
|
||||
|---|---|
|
||||
| `needEnableAVI` | `vcd_nsxt_alb_settings` (+ `count`) |
|
||||
| `virtualServicesCount` | `reserved_virtual_services` в SE-группе |
|
||||
| `routedNetConfiguration` (mainDns/secondDns/ipAddrPool) | `vcd_network_routed_v2` (`dns1/dns2/static_ip_pool`) |
|
||||
| `vIPConfigure` (IP на оргу) | `vcd_ip_space_custom_quota` (на оргу) |
|
||||
|
||||
### 3.2. Чем наш случай сложнее канона
|
||||
|
||||
В классическом паттерне ребёнок — **отдельный объект API** со своим CRUD (правило, association, attachment). Его можно создать/прочитать/удалить.
|
||||
|
||||
У нас отдельного объекта нет — есть **операция `modify` над родителем**. Поэтому требуются:
|
||||
|
||||
1. `Read` — не свой объект, а чтение состояния родителя;
|
||||
2. `Delete` — не удаление, а **обратный modify** (inverse);
|
||||
3. `Create/Update` — вызов той же операции с параметрами;
|
||||
4. идемпотентность (не дёргать `run`, если live уже целевое) и live-сверку.
|
||||
|
||||
Именно поэтому «просто завести поля из modify в схему» не работает — нужна полноценная механика, а не одна правка.
|
||||
|
||||
---
|
||||
|
||||
## 4. Более глубокая проблема: скрытые платформенные зависимости
|
||||
|
||||
Это главное из всей дискуссии (реплики Виталия Зайцева, 18:30–18:34).
|
||||
|
||||
### 4.1. `ipSpaceName` нельзя ввести вручную — он выводится
|
||||
|
||||
```text
|
||||
имя ipSpace → зависит от providerGateway
|
||||
providerGateway → зависит от providerVdc
|
||||
providerVdc → никто не знает изначально
|
||||
```
|
||||
|
||||
Пользователь **не может** заполнить `ipSpaceName`, потому что это значение выводится из внутренней топологии (providerVdc → providerGateway → ipSpace), а не из того, что он видел в ЛК. Это не «поле, которое забыли отдать через API», а **вычисляемое от скрытых зависимостей** значение.
|
||||
|
||||
### 4.2. Текущий костыль платформы
|
||||
|
||||
«При создании орги/vdc/edge, если организация ничего не знает про недостающие параметры — они подкладываются». То есть одноразовая подстановка при создании пустой орги, чтобы избавить пользователя от «мучительных приседаний» в ЛК.
|
||||
|
||||
### 4.3. Ограничение модели: один T0
|
||||
|
||||
«Другая проблема — что будет, если в облаке появится больше 1 T0». Пока принято допущение на уровне кода: **в организации всё одно подключение**. Решение осознанно отложено («пара лет спокойствия»), но для IaC это риск: текущее решение завязано на «в орге всегда один провайдер-шлюз».
|
||||
|
||||
### 4.4. Ожидание изменений спеков
|
||||
|
||||
«Мне надо увидеть, как спеки поменяются, чтобы понять, что исправлять… Надеюсь, появится сначала в sandbox.nubes.ru, а не в ngcloud». То есть платформа меняется, форма ресурсов зависит от **новых спеков**, и строить модификатор сейчас = работать по устаревшим спекам.
|
||||
|
||||
---
|
||||
|
||||
## 5. Итог: где правда
|
||||
|
||||
1. **Ручной ЛК и скрипт не подходят** — клиент требует IaC (Георгий прав). Это не «костыль против красоты», это невыполнение требования.
|
||||
|
||||
2. **Отдельный ресурс под модификацию — необходимое, но не достаточное условие.** Он закрывает «как expressить modify», но не закрывает «откуда юзер возьмёт значения».
|
||||
|
||||
3. **Главная блокировка — не Terraform, а платформа.** `ipSpaceName` (и подобные) выводятся из `providerVdc → providerGateway → ipSpace`, которые юзер не знает. Пока платформа не отдаёт эти значения в спеках (или провайдер не резолвит их data-источником), честный IaC не собрать — ни модификаторами, ни скриптом, ни руками.
|
||||
|
||||
4. **«Ломается агностичность» — верно, но это не порок, а цена.** Ресурсы-модификаторы доменные и «ручные», как в VCD. Без них IaC невозможен, прецедент — перед глазами (`!/network.tf.tmpl`).
|
||||
|
||||
5. **Состояние дел:** платформа в движении (ждут новые спеки). Правильная последовательность — дождаться, что придёт в спеках (snandbox), а затем решать форму ресурса; не строить по старым спекам.
|
||||
|
||||
---
|
||||
|
||||
## 6. Возможные пути (по убыванию «честности» перед IaC)
|
||||
|
||||
| Вариант | Что делает | Вердикт |
|
||||
|---|---|---|
|
||||
| **A. Полноценные ресурсы-модификаторы + data-источники** | отдельный tf-ресурс на modify + data-source, резолвящий `ipSpace`. Полный IaC. | правильно, но только после новых спеков |
|
||||
| **B. Data-source через `http`/`external` + `jsondecode`** | DevOps сам дёргает API и подставляет динамические списки, без правки провайдера | рабочая «дожималка», не полный IaC |
|
||||
| **C. `terraform_data`/`null_resource` + `local-exec`** | модификации скриптом, факт в state, порядок через `depends_on` | полумера, состояние SNAT/IP вне state |
|
||||
| **D. Прессеты/дефолтное окружение** | готовый набор компонентов, экспорт через провайдер | снижает боль на старте, IaC не заменяет |
|
||||
| **E. Ручной ЛК / скрипт вне tf** | модификации руками | не подходит (требование клиента) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Моё мнение
|
||||
|
||||
**Коротко:** для настоящего IaC нужны обе вещи одновременно — **отдельный ресурс под `modify`** и **механизм получения динамических значений** (`ipSpace` и пр.). Пока платформа не отдаёт второе через API/спеки, все «быстрые» способы (скрипт, руками, пресеты) закрывают только симптом, а не требование клиента.
|
||||
|
||||
**Рекомендация:** не городить модификатор сейчас по устаревшим спекам. Дождаться изменений спеков (сначала sandbox), параллельно — обсчитать два blockers: (1) как провайдер будет резолвить `providerVdc → providerGateway → ipSpace` без ручного ввода; (2) допущение «один T0». После этого проектировать форму ресурсов.
|
||||
|
||||
**Что точно не делать:** вписывать параметры «насильно» в tfstate; ждать, что «прописал поле в tf → apply» заработает без правки провайдера. (`vIPConfigure` при этом НЕ накопительный — см. §2.2, тест 2026-09-22.)
|
||||
@@ -1,196 +0,0 @@
|
||||
<!-- ⛔ LEGACY: deck-api.ngcloud.ru ЗАКРЫВАЕТСЯ. Актуальный API: lk-api-gateway.ngcloud.ru/api/v1/svc -->
|
||||
# Instance State Transitions - Матрица состояний инстансов ngcloud
|
||||
|
||||
Полное руководство по определению состояния инстансов через API ngcloud на основе комбинаций флагов и истории операций.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Таблица состояний
|
||||
|
||||
| **Состояние** | `isCreated` | `isDeleted` | `isSuspended` | `operationIsInProgress` | `operationIsPending` | `isSuccessful` | Описание |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **NOT_CREATED** | `false` | `false` | `false` | `false` | `false` | - | Ресурс в TF, но в облаке его нет |
|
||||
| **CREATING** | `false` | `false` | `false` | `true` | `false` | - | Идет создание инстанса |
|
||||
| **CREATION_FAILED** | `false` | `false` | `false` | `false` | `false` | `false` | ❌ Попытка создания завершилась ошибкой |
|
||||
| **RUNNING** | `true` | `false` | `false` | `false` | `false` | `true` | ✅ Нормальное работающее состояние |
|
||||
| **RUNNING_PENDING** | `true` | `false` | `false` | `false` | `true` | - | ✅ Работает, но в очереди ждет операция |
|
||||
|
||||
---
|
||||
|
||||
## 📊 Полная таблица состояний
|
||||
|
||||
| **Состояние** | `isCreated` | `isDeleted` | `isSuspended` | `operationIsInProgress` | `operationIsPending` | `isSuccessful` | Описание |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **NOT_CREATED** | `false` | `false` | `false` | `false` | `false` | - | Ресурс в TF, но в облаке его нет |
|
||||
| **CREATING** | `false` | `false` | `false` | `true` | `false` | - | Идет создание инстанса |
|
||||
| **CREATION_FAILED** | `false` | `false` | `false` | `false` | `false` | `false` | ❌ Попытка создания завершилась ошибкой |
|
||||
| **RUNNING** | `true` | `false` | `false` | `false` | `false` | `true` | ✅ Нормальное работающее состояние |
|
||||
| **RUNNING_PENDING** | `true` | `false` | `false` | `false` | `true` | - | ✅ Работает, но в очереди ждет операция |
|
||||
| **MODIFYING** | `true` | `false` | `false` | `true` | `false` | - | Идет изменение конфигурации |
|
||||
| **MODIFICATION_FAILED** | `true` | `false` | `false` | `false` | `false` | `false` | ❌ Ошибка при изменении |
|
||||
| **SUSPENDING** | `true` | `false` | `false` | `true` | `false` | - | Идет приостановка |
|
||||
| **SUSPEND_FAILED** | `true` | `false` | `false` | `false` | `false` | `false` | ❌ Ошибка при приостановке |
|
||||
| **SUSPENDED** | `true` | `false` | `true` | `false` | `false` | `true` | ⏸️ Приостановлен |
|
||||
| **RESUMING** | `true` | `false` | `true` | `true` | `false` | - | Идет возобновление |
|
||||
| **RESUME_FAILED** | `true` | `false` | `true` | `false` | `false` | `false` | ❌ Ошибка при возобновлении |
|
||||
| **DELETING** | `true` | `false` | `false` | `true` | `false` | - | Идет удаление |
|
||||
| **DELETION_FAILED** | `true` | `false` | `false` | `false` | `false` | `false` | ❌ Ошибка при удалении |
|
||||
| **DELETED** | `true` | `true` | - | - | - | - | ❌ Удален |
|
||||
| **ORPHANED** | `false` | `false` | - | - | - | `false` | ⚠️ История ошибок |
|
||||
|
||||
---
|
||||
|
||||
## 🔗 API Запросы для определения состояния
|
||||
|
||||
### Основной запрос к инстансу
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: Bearer $(cat /home/naeel/remote_dev/terraform/secrets/prod.token)" \
|
||||
"https://deck-api.ngcloud.ru/api/v1/index.cfm/instances/{instanceUid}?fields=isCreated,isDeleted,isSuspended,operationIsInProgress,operationIsPending,operations,availableOperations,explainedStatus,uptime"
|
||||
```
|
||||
|
||||
### Ответ API при CREATION_FAILED
|
||||
|
||||
```json
|
||||
{
|
||||
"instance": {
|
||||
"instanceUid": "vcOrg-2402",
|
||||
"displayName": "Организация в Cloud Director",
|
||||
"isCreated": false,
|
||||
"isDeleted": false,
|
||||
"isSuspended": false,
|
||||
"operationIsInProgress": false,
|
||||
"operationIsPending": false,
|
||||
"operations": [
|
||||
{
|
||||
"operation": "create",
|
||||
"isSuccessful": false,
|
||||
"errorLog": "Connection timeout to Cloud Director API",
|
||||
"dtFinish": "2026-02-24T18:51:30+0300",
|
||||
"duration": 80.5
|
||||
}
|
||||
],
|
||||
"availableOperations": [
|
||||
{"operation": "create"},
|
||||
{"operation": "delete"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐍 Python функция определения состояния
|
||||
|
||||
```python
|
||||
def get_instance_state(instance_data):
|
||||
"""Определить состояние инстанса на основе ответа API"""
|
||||
|
||||
ic = instance_data.get('isCreated', False)
|
||||
id = instance_data.get('isDeleted', False)
|
||||
is_susp = instance_data.get('isSuspended', False)
|
||||
is_in_prog = instance_data.get('operationIsInProgress', False)
|
||||
is_pend = instance_data.get('operationIsPending', False)
|
||||
ops = instance_data.get('operations', [])
|
||||
|
||||
# ПЕРВЫЙ ПРИОРИТЕТ: Проверить ошибки в последней операции
|
||||
if ops and len(ops) > 0:
|
||||
last_op = ops[0]
|
||||
if last_op.get('isSuccessful') == False: # явное False
|
||||
op_type = last_op.get('operation', 'unknown').upper()
|
||||
return {
|
||||
'state': f'{op_type}_FAILED',
|
||||
'error': last_op.get('errorLog'),
|
||||
'attempt_at': last_op.get('dtFinish'),
|
||||
'can_retry': True
|
||||
}
|
||||
|
||||
# Операции в процессе
|
||||
if is_in_prog:
|
||||
return {'state': 'OPERATING'}
|
||||
|
||||
# Ожидающие операции
|
||||
if is_pend:
|
||||
return {'state': 'RUNNING_PENDING'}
|
||||
|
||||
# Не создан
|
||||
if not ic:
|
||||
return {'state': 'NOT_CREATED'}
|
||||
|
||||
# Удален
|
||||
if id:
|
||||
return {'state': 'DELETED'}
|
||||
|
||||
# Приостановлен
|
||||
if is_susp:
|
||||
return {'state': 'SUSPENDED'}
|
||||
|
||||
# Работает
|
||||
return {
|
||||
'state': 'RUNNING',
|
||||
'uptime': instance_data.get('uptime')
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Логика для Terraform Provider
|
||||
|
||||
### terraform apply
|
||||
|
||||
- **NOT_CREATED** → CreateInstance
|
||||
- **RUNNING** + config_changed → ModifyInstance
|
||||
- **CREATION_FAILED**, **MODIFICATION_FAILED** → Retry
|
||||
- **OPERATING_*** → Wait
|
||||
|
||||
### terraform destroy
|
||||
|
||||
- **NOT_CREATED** → Skip
|
||||
- **DELETED** → RemoveFromState
|
||||
- **DELETION_FAILED** → Retry
|
||||
- **Остальные** → DeleteInstance
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Критические комбинации
|
||||
|
||||
### 1. Инстанс не видно, но ошибка создания
|
||||
|
||||
```
|
||||
isCreated=false + operations[0].isSuccessful=false + operation=create
|
||||
= CREATION_FAILED
|
||||
```
|
||||
|
||||
**Действие:**
|
||||
- Проверить `errorLog` для деталей
|
||||
- Использовать `availableOperations` для определения возможных действий
|
||||
- Перепопытаться через create или удалить
|
||||
|
||||
### 2. Операция зависла
|
||||
|
||||
```
|
||||
operationIsInProgress=true + duration > 3600 сек
|
||||
= STUCK_OPERATION
|
||||
```
|
||||
|
||||
**Действие:** Требует ручного вмешательства
|
||||
|
||||
### 3. Ошибка удаления (инстанс остался)
|
||||
|
||||
```
|
||||
isDeleted=false + operations[0].operation=delete + isSuccessful=false
|
||||
= DELETION_FAILED
|
||||
```
|
||||
|
||||
**Действие:**
|
||||
- Инстанс остается в облаке
|
||||
- Перепопытаться delete или обратиться в поддержку
|
||||
|
||||
---
|
||||
|
||||
## 📖 Ссылки
|
||||
|
||||
- API: https://deck-api.ngcloud.ru/api/v1/
|
||||
- Операции: create, modify, delete, suspend, resume
|
||||
- Токен: /home/naeel/remote_dev/terraform/secrets/prod.token
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
# Анализ 43 YAML-файлов из нового API Gateway
|
||||
|
||||
> Дата: 2026-07-02
|
||||
> Источник: `devops/profiles/test/generated/resources_yaml/`
|
||||
> Gateway: `https://lk-api-gateway-test.ngcloud.ru/api/v1/svc`
|
||||
|
||||
---
|
||||
|
||||
## Идеология построения сервисов
|
||||
|
||||
### Два класса сервисов
|
||||
|
||||
| Класс | Признак | Примеры | Кол-во |
|
||||
|---|---|---|---|
|
||||
| **Простые (flat)** | Плоские параметры: `resourceRealm`, `resourceCPU`, `domain`, ... | s3bucket, vc_vm, dnszone, harbor, gitea, rabbitmq, ... | **31** |
|
||||
| **Сложные (map-fixed)** | Структурированные JSON-блоки: `clusterConfiguration`, `startupConfiguration`, ... | postgres, redis, mariadb, clickhouse, kafka, nextcloud, flask, lucee, nodejs, valoTenant, dummy, template | **12** |
|
||||
|
||||
**Закономерность**: map-fixed используют сервисы, разворачиваемые в Kubernetes (БД, приложения). Простые flat — всё остальное (VM, сеть, DNS, S3).
|
||||
|
||||
---
|
||||
|
||||
## Типы данных (435 параметров всего)
|
||||
|
||||
| Тип | Кол-во | Где |
|
||||
|---|---|---|
|
||||
| `string` | 146 | Имена, домены, UUID |
|
||||
| `integer > 0` | 101 | CPU, memory, disk |
|
||||
| `map-fixed` | 93 | K8s-сервисы (12 шт.) |
|
||||
| `boolean` | 21 | Флаги |
|
||||
| `array-map-fixed` | 17 | Массивы объектов (7 сервисов) |
|
||||
| `uuid` | 14 | ref-параметры |
|
||||
| `json` | 11 | Произвольный JSON |
|
||||
| `map` | 14 | Только в outputs |
|
||||
| `yaml` | 2 | Редко |
|
||||
| `array` | 1 | Редко |
|
||||
| `integer >= 0` | 2 | Числовые, допускающие 0 |
|
||||
|
||||
---
|
||||
|
||||
## Обязательность
|
||||
|
||||
- **required: 340** (78%)
|
||||
- **optional: 95** (22%)
|
||||
- **default: 120** параметров имеют значение по умолчанию
|
||||
|
||||
Большинство параметров обязательные, но многие с дефолтами.
|
||||
|
||||
---
|
||||
|
||||
## Типы операций
|
||||
|
||||
| Kind | Кол-во | Паттерн |
|
||||
|---|---|---|
|
||||
| `instance` | 164 | create/delete + modify/suspend/resume (у 27 из 43) |
|
||||
| `action` | 32 | reconcile (20), redeploy (7), restart (3), recovery (2) |
|
||||
| `subresource` | 31 | user (16), database (6), topic (3), backup (2), vdc (2) |
|
||||
|
||||
---
|
||||
|
||||
## Subresource-паттерн
|
||||
|
||||
Самый частый — `user` (16 сервисов). Создаётся отдельный `nubes_{service}_user` ресурс с параметрами `username` + `role`. Аналогично `database` (6 сервисов) с `dbName` + `dbOwner`.
|
||||
|
||||
| subresource | Кол-во | Параметры |
|
||||
|---|---|---|
|
||||
| `user` | 16 | `username` (string, regex), `role` (string, value_list) |
|
||||
| `database` | 6 | `dbName` (string, regex), `dbOwner` (string) |
|
||||
| `topic` | 3 | Kafka |
|
||||
| `backup` | 2 | S3, PostgreSQL |
|
||||
| `vdc` | 2 | vcOrg |
|
||||
| `sub_user` | 2 | openwhisk |
|
||||
|
||||
---
|
||||
|
||||
## Топ параметров create
|
||||
|
||||
| code | Кол-во | Назначение |
|
||||
|---|---|---|
|
||||
| `resourceRealm` | 19 | Платформа/K8s-кластер |
|
||||
| `resourceMemory` | 11 | Память |
|
||||
| `resourceCPU` | 11 | CPU |
|
||||
| `startupConfiguration` | 10 | map-fixed (K8s) |
|
||||
| `clusterConfiguration` | 10 | map-fixed (K8s) |
|
||||
| `accessConfiguration` | 10 | map-fixed (K8s) |
|
||||
| `resourceInstances` | 10 | Кол-во реплик |
|
||||
| `domain` | 9 | Домен |
|
||||
| `resourceDisk` | 8 | Диск |
|
||||
| `ipSpaceName` | 4 | IP-space |
|
||||
| `appConfiguration` | 4 | Конфигурация приложения |
|
||||
| `jsonEnv` | 4 | Переменные окружения |
|
||||
| `backupConfiguration` | 3 | Бэкапы |
|
||||
| `autoscaleConfiguration` | 3 | Автоскейлинг |
|
||||
| `ipSpaceNameMaster` | 3 | IP для master |
|
||||
| `vdcUid` | 3 | Ссылка на VDC |
|
||||
| `vappName` | 2 | Имя vApp |
|
||||
| `storageConfig` | 2 | Конфигурация хранилища |
|
||||
|
||||
---
|
||||
|
||||
## Action-операции
|
||||
|
||||
| action | Кол-во | У каких сервисов |
|
||||
|---|---|---|
|
||||
| `reconcile` | 20 | Универсальная синхронизация (почти все) |
|
||||
| `redeploy` | 7 | flask, nodejs, lucee, nifi, superset, nextcloud, gitea |
|
||||
| `restart` | 3 | postgres, mariadb, redis |
|
||||
| `recovery` | 2 | postgres, clickhouse |
|
||||
|
||||
---
|
||||
|
||||
## Жизненный цикл
|
||||
|
||||
- **27 из 43** сервисов поддерживают `suspend`
|
||||
- **27 из 43** сервисов поддерживают `resume`
|
||||
- Все кто имеет suspend — имеют и resume (и наоборот)
|
||||
|
||||
---
|
||||
|
||||
## Валидация параметров
|
||||
|
||||
| Механизм | Кол-во | Пример |
|
||||
|---|---|---|
|
||||
| `regex` | 29 | `dbName: ^[A-Za-z0-9]+$` |
|
||||
| `value_list` | 38 | `role: [app_user, ddl_user]` |
|
||||
| `min/max value/length` | 87/74 | Числовые и строковые ограничения |
|
||||
| `default` | 120 | `deleteS3Bucket: true` |
|
||||
| `is_modifiable` | 106 (24%) | Можно менять после создания |
|
||||
| `is_sensitive` | 2 | `vault_secrets` |
|
||||
|
||||
---
|
||||
|
||||
## Сервисы с map-fixed (12)
|
||||
|
||||
`mariadb`, `clickhouse`, `valo_tenant`, `k8s_sthutrval_cluster`, `dummy`, `template`, `nextcloud`, `flask`, `postgres`, `redis`, `lucee`, `nodejs`
|
||||
|
||||
## Сервисы с array-map-fixed (7)
|
||||
|
||||
`mariadb`, `clickhouse`, `k8s_sthutrval_cluster`, `vc_org`, `dummy`, `vc_vdc`, `postgres`
|
||||
|
||||
---
|
||||
|
||||
## Сервисы без операций (4)
|
||||
|
||||
`openwhisk`, `gitea_complex`, `vmpostgre`, `template` — только заглушки/заготовки.
|
||||
|
||||
---
|
||||
|
||||
## Полный список сервисов и их операций
|
||||
|
||||
```
|
||||
100_openwhisk | (нет операций)
|
||||
110_dnszone | instance
|
||||
111_dnsrecord | instance, action
|
||||
112_tenant | instance
|
||||
113_vc_complex | instance
|
||||
114_gitea_complex | (нет операций)
|
||||
115_mariadb | instance, subresource, action
|
||||
116_kafka | instance, subresource
|
||||
117_nifi | instance
|
||||
119_akhq | instance
|
||||
120_clickhouse | instance, subresource, action
|
||||
12_s3 | instance, subresource, action
|
||||
13_s3bucket | instance
|
||||
149_valo_tenant | instance
|
||||
150_k8s_sthutrval_cluster | instance, subresource, action
|
||||
19_vc_org | instance, subresource, action
|
||||
1_dummy | instance, action
|
||||
20_vc_org_saas | instance
|
||||
21_vc_vdc | instance, action
|
||||
22_vc_nsxt | instance, action
|
||||
23_vc_vm | instance
|
||||
25_vcexternalip | instance
|
||||
26_vapp | instance, action
|
||||
27_vc_vm_v2 | instance
|
||||
28_vc_vm_v3 | instance, action
|
||||
29_vc_vdc_group | instance, subresource, action
|
||||
2_template | instance, action
|
||||
32_vmpostgre | (нет операций)
|
||||
50_nextcloud | instance, subresource, action
|
||||
81_superset | instance
|
||||
82_harbor | instance
|
||||
88_k8s_ziti_controller | instance
|
||||
89_flask | instance, action
|
||||
90_postgres | instance, subresource, action
|
||||
91_redis | instance, action
|
||||
92_mongodb | instance, subresource
|
||||
93_rabbitmq | instance
|
||||
94_lucee | instance, action
|
||||
95_nodejs | instance, action
|
||||
96_pgadmin | instance, action
|
||||
97_nodered | instance
|
||||
98_http | instance, action
|
||||
99_gitea | instance
|
||||
```
|
||||
@@ -1,188 +0,0 @@
|
||||
# Universal Provider (ядро + генератор ресурсов) — подробный отчёт
|
||||
|
||||
Дата: 2026-01-31
|
||||
|
||||
## Цель
|
||||
Нужна архитектура «универсального провайдера», где:
|
||||
- есть **ядро** (общий клиент и универсальный флоу операций);
|
||||
- есть **папка ресурсов** (Go-файлы ресурсов);
|
||||
- есть **папка YAML-описаний сервисов** (чтобы DevOps мог добавить сервис одним файлом);
|
||||
- есть **генератор**, который:
|
||||
- читает YAML,
|
||||
- генерирует ресурсы,
|
||||
- генерирует реестр ресурсов,
|
||||
- дальше выполняется build (ядро + ресурсы из папки).
|
||||
|
||||
В итоге: если файла ресурса нет — в провайдере его не будет. Если YAML изменён — перегенерация.
|
||||
|
||||
---
|
||||
|
||||
## Итоговая архитектура (v2.0.0, «голое» ядро + ресурсы)
|
||||
Новая сборка размещена в:
|
||||
- [nubes_provider_gen](nubes_provider_gen)
|
||||
|
||||
Структура:
|
||||
- [nubes_provider_gen/internal/core](nubes_provider_gen/internal/core) — **ядро** (клиент, универсальный flow, запуск операций)
|
||||
- [nubes_provider_gen/internal/provider](nubes_provider_gen/internal/provider) — **провайдер** (schema + клиент)
|
||||
- [nubes_provider_gen/internal/resources_gen](nubes_provider_gen/internal/resources_gen) — **сгенерированные ресурсы** (Go)
|
||||
- [nubes_provider_gen/resources_yaml](nubes_provider_gen/resources_yaml) — **YAML-описания сервисов**
|
||||
- [nubes_provider_gen/tools/gen](nubes_provider_gen/tools/gen) — **генератор**
|
||||
- [nubes_provider_gen/test_persistent](nubes_provider_gen/test_persistent) — тестовые манифесты
|
||||
|
||||
### 1) Ядро (core)
|
||||
Файл: [nubes_provider_gen/internal/core/client.go](nubes_provider_gen/internal/core/client.go)
|
||||
|
||||
Содержит:
|
||||
- `UniversalClient` с HTTP-клиентом и API endpoint/token.
|
||||
- Универсальный flow **CreateGenericInstanceUniversalV6**:
|
||||
1. POST /instances
|
||||
2. POST /instanceOperations (create)
|
||||
3. GET /instanceOperations/{opUid}?fields=cfsParams
|
||||
4. POST /instanceOperationCfsParams (явные параметры)
|
||||
5. POST /instanceOperationCfsParams (дефолтные/пустые для пропущенных)
|
||||
6. GET /instanceOperations/{opUid}/validate-cfs
|
||||
7. POST /instanceOperations/{opUid}/run
|
||||
- Универсальные операции:
|
||||
- `RunInstanceOperationUniversal` (delete/suspend/resume и т.д.)
|
||||
- `RunInstanceOperationUniversalWithDefaults` (modify с автоподстановкой параметров)
|
||||
- Утилиты:
|
||||
- `FindInstanceByDisplayName`
|
||||
- `GetInstanceState`
|
||||
|
||||
**Почему нужно WithDefaults для modify**
|
||||
На modify требуются обязательные параметры (часто даже те, что не меняются). Без них backend падает.
|
||||
|
||||
### 2) Провайдер (provider)
|
||||
Файл: [nubes_provider_gen/internal/provider/provider.go](nubes_provider_gen/internal/provider/provider.go)
|
||||
|
||||
- Тип провайдера: `nubes`
|
||||
- Адрес: `terrareg.kube5s.ru/nubes/nubes`
|
||||
- Ресурсы приходят из **реестра**:
|
||||
- `resources_gen.AllResources()` — возвращает список функций создания ресурсов.
|
||||
|
||||
### 3) Генератор (tools/gen)
|
||||
Файл: [nubes_provider_gen/tools/gen/main.go](nubes_provider_gen/tools/gen/main.go)
|
||||
|
||||
Функции:
|
||||
- Читает все YAML-файлы в `resources_yaml/`
|
||||
- Генерирует ресурсный Go-файл в `internal/resources_gen/` для каждого YAML
|
||||
- Генерирует `registry.go` с `AllResources()`
|
||||
|
||||
То есть:
|
||||
- **Добавили YAML → сгенерировали Go → build**
|
||||
- Нет YAML → нет Go → нет ресурса
|
||||
|
||||
### 4) YAML-описания
|
||||
Файл: [nubes_provider_gen/resources_yaml/dummy.yaml](nubes_provider_gen/resources_yaml/dummy.yaml)
|
||||
|
||||
Минимальная схема (пример dummy):
|
||||
- `name`, `service_id`, `display_name_default`
|
||||
- `create.params` — ID параметров create
|
||||
- `modify.params` — ID параметров modify
|
||||
- `lifecycle` — defaults для `adopt_existing_on_create` и `suspend_on_destroy`
|
||||
|
||||
---
|
||||
|
||||
## Что было сделано (конкретные шаги)
|
||||
|
||||
### Шаг 1. «Голое» ядро + генератор
|
||||
Создан отдельный проект:
|
||||
- [nubes_provider_gen](nubes_provider_gen)
|
||||
Сделан core + provider + tools/gen + resources_yaml.
|
||||
|
||||
### Шаг 2. YAML для dummy
|
||||
Создан YAML dummy: [nubes_provider_gen/resources_yaml/dummy.yaml](nubes_provider_gen/resources_yaml/dummy.yaml)
|
||||
|
||||
### Шаг 3. Генерация ресурсов
|
||||
Команда:
|
||||
- `go run ./tools/gen`
|
||||
|
||||
Сгенерированы:
|
||||
- [nubes_provider_gen/internal/resources_gen/dummy_resource.go](nubes_provider_gen/internal/resources_gen/dummy_resource.go)
|
||||
- [nubes_provider_gen/internal/resources_gen/registry.go](nubes_provider_gen/internal/resources_gen/registry.go)
|
||||
|
||||
### Шаг 4. Build
|
||||
Команда:
|
||||
- `go build -o terraform-provider-nubes`
|
||||
|
||||
### Шаг 5. Тесты dummy
|
||||
Тестовый конфиг:
|
||||
- [nubes_provider_gen/test_persistent/main.tf](nubes_provider_gen/test_persistent/main.tf)
|
||||
- [nubes_provider_gen/test_persistent/dev_override.tfrc](nubes_provider_gen/test_persistent/dev_override.tfrc)
|
||||
|
||||
Проверены сценарии:
|
||||
1. `apply` (create/adopt) — OK
|
||||
2. `modify` (duration 750 → 820) — OK
|
||||
3. `destroy` (`suspend_on_destroy = true`) — OK
|
||||
4. `apply` после suspend (resume/adopt c явным `adopt_existing_on_create`) — OK
|
||||
5. удаление ресурса из манифеста + `apply` — OK
|
||||
6. возвращение ресурса в манифест + `apply` — OK
|
||||
|
||||
---
|
||||
|
||||
## Ошибки и как решались
|
||||
|
||||
### 1) `action modify not available`
|
||||
Причина: в Update использовался `id` из `Plan` (неизвестен).
|
||||
Решение: брать `id` из `State`.
|
||||
|
||||
### 2) `API error 400: Parameter specified does not belong to this operation`
|
||||
Причина: неверные ID параметров modify.
|
||||
Решение: для modify использовать `287/288` вместо `198/199`.
|
||||
|
||||
### 3) `API error 500: checkParam ... instanceOperationCfsParamUid` (повторно)
|
||||
Причина: операция modify требует подстановки всех параметров; без них backend падает.
|
||||
Решение:
|
||||
- Добавлен `RunInstanceOperationUniversalWithDefaults` — читает `cfsParams` и заполняет недостающие
|
||||
- Позже для dummy оказалось нужно отправлять **все** параметры (не только required)
|
||||
|
||||
### 4) `TLS handshake timeout`
|
||||
Причина: VPN/сеть.
|
||||
Решение: повторить команду.
|
||||
|
||||
### 5) `status 401`
|
||||
Причина: токен истёк/невалидный.
|
||||
Решение: заменить токен и сохранить по правилу `/home/naeel/terra/HH-MM-SS.token`.
|
||||
|
||||
### 6) Критерий завершения операции (важно)
|
||||
Любая операция (create/modify/delete/suspend/resume) считается **завершённой**, когда у неё заполнено **время окончания**.
|
||||
Это является признаком завершения **и успеха, и ошибки**. В UI и API ориентируемся на наличие `dtFinish`.
|
||||
|
||||
---
|
||||
|
||||
## Текущее состояние
|
||||
- Провайдер «голый» + ресурсы из YAML работает.
|
||||
- `dummy` полностью тестируется из YAML → Go → build.
|
||||
- В `resources_gen` присутствует 1 ресурс: `nubes_dummy`.
|
||||
|
||||
---
|
||||
|
||||
## План (следующий шаг)
|
||||
1) Расширить YAML-схему:
|
||||
- поддержку дополнительных параметров (например, failInProgress, whereFail и т.п.)
|
||||
- поддержку optional параметров с явными defaults
|
||||
2) Добавить генерацию документации из YAML
|
||||
3) Добавить проверку схемы YAML (валидация) перед генерацией
|
||||
4) Скрипт "build pipeline":
|
||||
- gen → registry → go build
|
||||
5) Опционально: тестовый фреймворк для «batch» тестов
|
||||
|
||||
---
|
||||
|
||||
## Важные файлы
|
||||
- Ядро: [nubes_provider_gen/internal/core/client.go](nubes_provider_gen/internal/core/client.go)
|
||||
- Генератор: [nubes_provider_gen/tools/gen/main.go](nubes_provider_gen/tools/gen/main.go)
|
||||
- Реестр: [nubes_provider_gen/internal/resources_gen/registry.go](nubes_provider_gen/internal/resources_gen/registry.go)
|
||||
- YAML dummy: [nubes_provider_gen/resources_yaml/dummy.yaml](nubes_provider_gen/resources_yaml/dummy.yaml)
|
||||
- Сгенерированный ресурс: [nubes_provider_gen/internal/resources_gen/dummy_resource.go](nubes_provider_gen/internal/resources_gen/dummy_resource.go)
|
||||
- Тесты: [nubes_provider_gen/test_persistent/main.tf](nubes_provider_gen/test_persistent/main.tf)
|
||||
|
||||
---
|
||||
|
||||
## MAYDO (отложено до запроса заказчика)
|
||||
- Добавить в YAML `outputs` (из `state/out`) и `state_params` (из `state/params`).
|
||||
- Добавить `param_meta`: `valueList`, `func`, `refSvcId`, `dataDescriptor`.
|
||||
- Добавить `secrets` (из `vault`) с пометкой чувствительных данных.
|
||||
- Добавить `operations` (man/описания, доступные операции) для генерации доков.
|
||||
- Добавить поддержку `subparams` (nested/list/json) и типизацию сложных структур.
|
||||
- Добавить обработку версий операций (если API это отдаёт).
|
||||
@@ -1,83 +0,0 @@
|
||||
# Prompt для GPT‑5.2‑Codex — анализ и задачи по `terra` провайдеру
|
||||
|
||||
Цель: дать модели полный контекст репозитория и набор задач для проектирования/реализации managed Terraform provider и ресурсов (особенно: S3 bucket, Edge / nsxt, Redis, DNS zone+record, Grafana tenant).
|
||||
|
||||
---
|
||||
|
||||
## 1) Короткая инструкция к модели
|
||||
You are GPT‑5.2‑Codex. Study the repository files listed below and perform the tasks described in the "Tasks" section. Answer in Russian. Do NOT modify any existing Go production code without an explicit operator approval (see IMMUTABILITY POLICY in REPO_CONTENTS.md). Do NOT output any secrets or tokens. Provide precise, actionable outputs only (JSON, Terraform snippets, git diffs, test files, PR description).
|
||||
|
||||
Constraints:
|
||||
- Do not commit secrets or tokens. Indicate placeholder values for secrets.
|
||||
- Respect repository rules: do not change function signatures or existing public APIs without confirmation.
|
||||
- Provide outputs as machine‑parsable artifacts where possible (JSON matrix, unified diffs, files to create).
|
||||
|
||||
---
|
||||
|
||||
## 2) Файлы для изучения (указать полные пути)
|
||||
- docs/ARCHITECTURE_NEW.md — архитектурный обзор
|
||||
- docs/README.md, docs/index.md — документация / навигация
|
||||
- docs/howitwasdone.md — история решений
|
||||
- docs/ai_universal_provider_gen.md — процесс генерации провайдера (YAML → Go)
|
||||
- universal_rebuild/tools/gen/main.go — генератор (основная логика)
|
||||
- universal_rebuild/internal/resources_gen/* — примеры сгенерированных ресурсов
|
||||
- docs/30_registry/resources/s3bucket.md — S3 bucket schema
|
||||
- docs/30_registry/resources/redis.md — Redis schema
|
||||
- docs/30_registry/resources/vc_nsxt.md — Edge (vc_nsxt) schema
|
||||
- docs/30_registry/resources/vcexternalip.md — внешний IPs и пример полей
|
||||
- docs/30_registry/resources/postgres.md — пример связанного ресурса (для референса)
|
||||
- docs/20_discovery/development-journey.md — как тестировались флоу
|
||||
- docs/70_api/* — API analysis and request/response examples
|
||||
- scripts/package_release.sh — release flow and artifact publishing
|
||||
- tools/upload_provider_s3.py — upload logic for release artifacts
|
||||
- tests/ (especially tests/modify_postgres, tests/lifecycle_scenario) — real terraform snippets and logs
|
||||
- REPO_CONTENTS.md — operational rules and immutability policy
|
||||
|
||||
---
|
||||
|
||||
## 3) Задачи (приоритетные — выполнить по очереди)
|
||||
1. Извлечь и вернуть JSON‑матрицу параметров для ресурсов:
|
||||
- S3 bucket (`nubes_s3bucket`) — поля create/outputs
|
||||
- Edge (`nubes_vc_nsxt`) — минимальный набор create/modify полей
|
||||
- Redis (`nubes_redis`) — минимальные параметры
|
||||
- DNS zone (`nubes_dnszone`) & DNS record (`nubes_dnsrecord`) — поля (если нет, предложи именования)
|
||||
- Grafana tenant (tenant / grafana_tenant) — поля
|
||||
Формат: { resource_name: {create: [{name,type,required,default,id}], outputs:[...] } }
|
||||
|
||||
2. На основе матрицы сгенерировать модуль Terraform (пример `modules/<resource>/main.tf`) с минимальными параметрами и README для каждого ресурса.
|
||||
|
||||
3. Сгенерировать unit/acceptance тесты (как минимум `terraform plan` checks) и CI шаги (`.github/workflows/validate.yml`) с проверками: `terraform fmt`, `terraform validate`, `terraform plan` (dry-run), go vet/format для Go кода.
|
||||
|
||||
4. Выдать unified diff / patch (git format‑patch или git diff) с предлагаемых изменений: новые файлы (modules + tests + ci), пример использования `TERRA_TEST` обновлённый.
|
||||
|
||||
5. Подготовить текст PR (title, description, список файлов, rationale, risks, rollback plan) и checklist для code reviewers.
|
||||
|
||||
6. Предложить список ручных smoke‑checks, которые оператор должен выполнить перед мёрджем (eg. run `terraform plan` with `TF_VAR_api_token`, check provider build process via `scripts/package_release.sh`).
|
||||
|
||||
---
|
||||
|
||||
## 4) Формат ответа (ожидается от модели)
|
||||
- 1) Краткое summary (3–5 предложений) — что сделано.
|
||||
- 2) JSON matrix (machine‑readable) с полями ресурсов.
|
||||
- 3) Сгенерированные Terraform файлы (в виде unified diff или содержимого файлов) и README примечания.
|
||||
- 4) Список тестов и CI workflow (YAML) — как patch.
|
||||
- 5) PR description (markdown) + checklist.
|
||||
- 6) В конце — список вопросов/неизвестностей, которые нужно уточнить у оператора.
|
||||
|
||||
---
|
||||
|
||||
## 5) Доп. инструкции по безопасности и процессу
|
||||
- Никогда не включай реальные токены в ответы.
|
||||
- Не выполнять реальные `apply` в production; предложи инструкции для тестовой среды (use `TERRA_TEST/terraform.tfvars` with placeholders).
|
||||
- Учитывай immutability rules: existing Go production code must not be modified without explicit permission. For generated code — create new files in `universal_rebuild/internal/resources_gen` or `universal_rebuild/tools/gen` output, do not alter core provider internals unless directed.
|
||||
|
||||
---
|
||||
|
||||
## 6) Пример стартового вопроса к модели
|
||||
"Проанализируй перечисленные файлы, верни JSON‑матрицу параметров для перечисленных ресурсов, затем сгенерируй модули Terraform с минимальными параметрами, подготовь тесты и CI workflow, и выведи единственный unified diff patch, который можно применить в ветке `feature/universal-provider-resources`.
|
||||
|
||||
Пожалуйста, начинай с матрицы параметров (JSON)."
|
||||
|
||||
---
|
||||
|
||||
Файл подготовил: GitHub Copilot (Raptor mini, но prompt для GPT‑5.2‑Codex).
|
||||
@@ -1,268 +0,0 @@
|
||||
<!-- ⛔⛔⛔ LEGACY: deck-api.ngcloud.ru ЗАКРЫВАЕТСЯ! Все примеры ниже — ИСТОРИЧЕСКИЕ. -->
|
||||
<!-- Актуальный API: https://lk-api-gateway.ngcloud.ru/api/v1/svc -->
|
||||
# How It Was Done — Developer Guide (закрытая страница)
|
||||
|
||||
**Filename & Versioning:** howitwasdone.md / 2026‑02‑04 / Draft v1
|
||||
|
||||
Этот документ — единый технический мануал. Он доступен только по прямой ссылке и не включён в публичную навигацию.
|
||||
|
||||
## Оглавление
|
||||
1. Архитектура и методы (CRUD)
|
||||
2. База знаний ошибок
|
||||
3. Глоссарий (со ссылками на места употребления)
|
||||
4. Билд и публикация провайдера и документации
|
||||
|
||||
---
|
||||
|
||||
## 1. Архитектура и методы (CRUD)
|
||||
|
||||
### 1.1 Архитектура универсального rebuild
|
||||
- Ядро: universal_rebuild/internal/core (универсальный клиент API и общий flow операций).
|
||||
- Провайдер: universal_rebuild/internal/provider (schema, конфигурация, подключение ресурсов).
|
||||
- YAML-спеки: universal_rebuild/resources_yaml (источник истины).
|
||||
- Генератор: universal_rebuild/tools/gen (генерация Go-ресурсов и registry).
|
||||
|
||||
Ключевое правило: новая логика — только новые функции/файлы. Существующий Go‑код не менять без согласования.
|
||||
|
||||
<a id="discovery"></a>
|
||||
### 1.2 Источник параметров (discovery)
|
||||
Параметры извлекаются через proxy endpoint API:
|
||||
- /index.cfm?endpoint=/services/{svcId}
|
||||
- /index.cfm?endpoint=/serviceOperation/{svcOperationId}
|
||||
- (опц.) /index.cfm?endpoint=/param-value-list/{svcOperationCfsParamId}
|
||||
|
||||
Схема: сервис → операции → CFS параметры → YAML → генерация Go.
|
||||
|
||||
<a id="create-universal"></a>
|
||||
### 1.3 Create (универсальный 7‑шаговый паттерн)
|
||||
1) POST /instances → instanceUid
|
||||
2) POST /instanceOperations (create) → instanceOperationUid
|
||||
3) GET /instanceOperations/{uid}?fields=cfsParams
|
||||
4) POST /instanceOperationCfsParams для каждого параметра
|
||||
5) GET /instanceOperations/{uid}/validate-cfs
|
||||
6) POST /instanceOperations/{uid}/run с payload {}
|
||||
7) Polling до завершения операции
|
||||
|
||||
Критично: параметры отправляются все, включая дефолты.
|
||||
|
||||
<a id="read-adopt"></a>
|
||||
### 1.4 Read / Adopt
|
||||
- При isDeleted или explainedStatus=deleted — state очищается.
|
||||
- При совпадении display_name и resume_if_exists=true — adopt/resume.
|
||||
- Предупреждения показываются только на create и не мешают managed ресурсам.
|
||||
|
||||
<a id="update-modify"></a>
|
||||
### 1.5 Update / Modify
|
||||
- IDs параметров на modify отличаются от create.
|
||||
- Нельзя хардкодить ID: нужно получать manifest операции и строить маппинг.
|
||||
- Если modify отсутствует в availableOperations — выдаётся ясная ошибка.
|
||||
|
||||
<a id="polling-rules"></a>
|
||||
### 1.6 Polling (железные правила)
|
||||
- Завершение операции определяется только по dtFinish.
|
||||
- После dtFinish результат определяется isSuccessful.
|
||||
- Для VM применяется двойной контроль: статус операции + статус инстанса (ERROR/STOPPED).
|
||||
|
||||
### 1.7 Нормализация типов
|
||||
- map/json → "{}"
|
||||
- list/array → "[]"
|
||||
- Пустые строки в JSON‑параметрах запрещены (валидаторы на plan).
|
||||
|
||||
<a id="soft-delete"></a>
|
||||
### 1.8 Soft Delete
|
||||
- Для тяжёлых ресурсов delete заменяется на suspend (карантин/retention).
|
||||
- Повторный apply при suspend может выполнять resume.
|
||||
|
||||
---
|
||||
|
||||
## 2. База знаний ошибок
|
||||
|
||||
### A. Аутентификация и токены
|
||||
|
||||
**Токены лежат в `secrets/{dev,test,prod}.token`. Срок действия — до декабря 2026.**
|
||||
Проверить дату JWT:
|
||||
```bash
|
||||
python3 -c "import json,base64; t=open('secrets/test.token').read().split('.'); d=json.loads(base64.urlsafe_b64decode(t[1]+'==')); from datetime import datetime,timezone; print(datetime.fromtimestamp(d['exp'],tz=timezone.utc))"
|
||||
```
|
||||
|
||||
**Симптом:** 403 Forbidden (НЕ 401!)
|
||||
**Причина (старый API index.cfm):** DDoS-Guard блокирует — нет Referer или нет User-Agent.
|
||||
**Решение для curl (старый API):**
|
||||
```bash
|
||||
curl -s --max-time 10 \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \
|
||||
-H "Referer: https://deck-test.ngcloud.ru/" \
|
||||
"https://deck-api-test.ngcloud.ru/api/v1/index.cfm?endpoint=/services/90"
|
||||
```
|
||||
Referer должен совпадать со стендом (test/dev/prod). Новый Gateway (lk-api-gateway) требует только Authorization.
|
||||
|
||||
**Симптом:** 401 Unauthorized / Authorization header expected
|
||||
**Причина:** токен истёк или не передан в окружение
|
||||
**Решение:** обновить access_token и сохранить в файле HH‑MM‑SS.token; убедиться, что Terraform читает токен
|
||||
|
||||
### B. Поллинг зависает
|
||||
**Симптом:** apply «висит», операция не завершается
|
||||
**Причина:** ожидание по статусу инстанса/операции без dtFinish
|
||||
**Решение:** критерий завершения — dtFinish; далее isSuccessful
|
||||
|
||||
### C. Modify: Invalid CFS parameter (400)
|
||||
**Причина:** отправлены create IDs для modify
|
||||
**Решение:** получать manifest операции и динамически маппить IDs
|
||||
|
||||
### D. Modify недоступен
|
||||
**Симптом:** action modify not available
|
||||
**Причина:** modify нет в availableOperations
|
||||
**Решение:** проверять доступные операции и выдавать явную ошибку
|
||||
|
||||
### E. Map/JSON/List: Invalid format
|
||||
**Симптом:** 400 Invalid format map/array/json
|
||||
**Причина:** пустые строки вместо {} или []
|
||||
**Решение:** нормализация по типу, trim, отправка {} или []
|
||||
|
||||
### F. VM: зависание на FW / 500 String[]→GUID
|
||||
**Симптом:** зависание на стадии FW, 500 checkParam
|
||||
**Причина:** пустые/невалидные JSON‑массивы, ошибки backend
|
||||
**Решение:** валидация JSON массивов, дефолт ["0.0.0.0/0"], фильтрация пустых значений; при повторении — эскалация на поддержку
|
||||
|
||||
### G. VM modify: checkParam instanceOperationCfsParamUid
|
||||
**Симптом:** 500 Invalid call of function checkParam
|
||||
**Причина:** дублирование параметров при POST/PUT
|
||||
**Решение:** гибридный PUT/POST по наличию instanceOperationCfsParamUid; проверить формат эндпойнта
|
||||
|
||||
### H. Postgres: split() on null
|
||||
**Симптом:** Cannot invoke method split() on null object
|
||||
**Причина:** передан UUID конкретного S3‑бакета
|
||||
**Решение:** использовать UUID сервиса S3 (svcId 12), а не бакета
|
||||
|
||||
### I. GPG/Registry
|
||||
**Симптом:** authentication signature from unknown issuer
|
||||
**Причина:** ключ подписи не совпадает с ключом в registry
|
||||
**Решение:** обновить signing_keys на сервере или пересобрать/подменить бинарник
|
||||
|
||||
**Симптом:** openpgp invalid data
|
||||
**Причина:** ASCII‑armor для .sig
|
||||
**Решение:** бинарная detached подпись без --armor
|
||||
|
||||
### J. Registry/S3
|
||||
**Симптом:** presigned URL не работает через Ingress
|
||||
**Причина:** Host заголовок ломает подпись
|
||||
**Решение:** proxy‑режим download на сервере registry
|
||||
|
||||
### K. Документация: 404
|
||||
**Причина:** неверный S3‑ключ (hostname в префиксе)
|
||||
**Решение:** фиксированный префикс docs/<ns>/<name>/<version>/
|
||||
|
||||
### L. Версии Terraform Plugin Framework
|
||||
**Симптом:** ошибки сборки при апгрейде
|
||||
**Причина:** несовместимость framework и plugin‑go
|
||||
**Решение:** использовать совместимые версии
|
||||
|
||||
### M. TLS handshake timeout
|
||||
**Причина:** сетевые условия/VPN
|
||||
**Решение:** повторить apply
|
||||
|
||||
---
|
||||
|
||||
## 3. Глоссарий (с ссылками на места употребления)
|
||||
|
||||
Каждый термин содержит ссылки на разделы этого же документа, где он используется.
|
||||
|
||||
- **instance** — экземпляр сервиса в Nubes Cloud.
|
||||
- Ссылки: [Create](#create-universal)
|
||||
|
||||
- **instanceOperation** — асинхронная операция над instance (create/modify/delete/suspend/resume).
|
||||
- Ссылки: [Create](#create-universal), [Polling](#polling-rules)
|
||||
|
||||
- **cfsParams** — список параметров операции, получаемый через manifest операции.
|
||||
- Ссылки: [Create](#create-universal), [Discovery](#discovery)
|
||||
|
||||
- **svcOperationCfsParamId** — ID параметра операции, различается для create и modify.
|
||||
- Ссылки: [Update / Modify](#update-modify)
|
||||
|
||||
- **dtFinish** — единственный надёжный индикатор завершения операции.
|
||||
- Ссылки: [Polling](#polling-rules)
|
||||
|
||||
- **isSuccessful** — результат операции после dtFinish.
|
||||
- Ссылки: [Polling](#polling-rules)
|
||||
|
||||
- **resourceRealm** — окружение/realm; иногда обязателен и задаётся пользователем.
|
||||
- Ссылки: [Read / Adopt](#read-adopt)
|
||||
|
||||
- **display_name** — человекочитаемое имя; используется для adopt/resume.
|
||||
- Ссылки: [Read / Adopt](#read-adopt)
|
||||
|
||||
- **resume_if_exists** — включение adopt/resume по display_name.
|
||||
- Ссылки: [Read / Adopt](#read-adopt)
|
||||
|
||||
- **delete_mode** — режим удаления (delete/suspend/state_only).
|
||||
- Ссылки: [Soft Delete](#soft-delete)
|
||||
|
||||
- **suspend** — мягкое удаление (карантин/retention).
|
||||
- Ссылки: [Soft Delete](#soft-delete)
|
||||
|
||||
- **validate-cfs** — проверка параметров операции до run.
|
||||
- Ссылки: [Create](#create-universal)
|
||||
|
||||
- **state.out** — выходные данные API; при отсутствии задаются null.
|
||||
- Ссылки: [Read / Adopt](#read-adopt)
|
||||
|
||||
- **computed** — вычисляемые поля, должны быть известны после apply.
|
||||
- Ссылки: [Read / Adopt](#read-adopt)
|
||||
|
||||
- **registry protocol** — API Terraform Registry (discovery + versions + download).
|
||||
- Ссылки: [Публикация](#publish-registry)
|
||||
|
||||
- **signing_keys** — публичные GPG ключи, выдаваемые registry.
|
||||
- Ссылки: [Публикация](#publish-registry)
|
||||
|
||||
- **NLI** — Natural Language Infrastructure (AI‑парсинг инструкций).
|
||||
- Ссылки: [AI‑интеграция](#ai-nli)
|
||||
|
||||
---
|
||||
|
||||
## 4. Билд и публикация провайдера и документации
|
||||
|
||||
### 4.1 Сборка провайдера (universal_rebuild)
|
||||
Общий цикл:
|
||||
1) Генерация YAML параметров сервисов.
|
||||
2) Генерация Go‑ресурсов.
|
||||
3) Сборка бинарника go build.
|
||||
|
||||
Ключевые каталоги:
|
||||
- universal_rebuild/resources_yaml
|
||||
- universal_rebuild/internal/resources_gen
|
||||
- universal_rebuild/tools/gen
|
||||
|
||||
<a id="publish-registry"></a>
|
||||
### 4.2 Публикация провайдера в Registry
|
||||
- Артефакты: zip, SHA256SUMS, SHA256SUMS.sig
|
||||
- Подпись: для .sig использовать бинарную detached подпись
|
||||
- Хранилище: S3 bucket terraform‑registry
|
||||
- Префикс — по правилам registry‑сервера
|
||||
|
||||
### 4.3 Документация (MkDocs)
|
||||
- Сборка: Docker образ squidfunk/mkdocs-material
|
||||
- Результат: директория site/
|
||||
- Публикация: scripts/publish-docs.sh
|
||||
- Путь: docs/<namespace>/<name>/<version>/
|
||||
|
||||
### 4.4 Важные нюансы
|
||||
- При смене домена обновлять registry и ключи подписи.
|
||||
- Presigned URL через Ingress может ломаться — использовать proxy mode.
|
||||
- Технические папки исключаются из публикации.
|
||||
|
||||
### 4.5 Рекомендованный порядок работ
|
||||
1) Discovery параметров сервиса
|
||||
2) Генерация YAML
|
||||
3) Генерация Go‑ресурсов
|
||||
4) Build
|
||||
5) Тесты create/modify/suspend/resume
|
||||
6) Документация и публикация
|
||||
|
||||
<a id="ai-nli"></a>
|
||||
### 4.6 AI‑интеграция (NLI)
|
||||
Кратко: реализована для ресурса Tubulus через ModifyPlan + askGemini, с защитой от двойного вызова AI.
|
||||
|
||||
### 4.7 Прямая ссылка
|
||||
https://registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> <!-- ⛔ LEGACY: registry.kube5s.ru <!-- ⛔ LEGACY: registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru --> ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->/docs/nubes/nubes/2.0.0/howitwasdone/
|
||||
@@ -1,53 +0,0 @@
|
||||
# Inverse-откат модификаторов: анализ ответа Опуса — 2026-09-23
|
||||
|
||||
Источник: prompt_for_opus_inverse_architecture.md → ответ Опуса (принят, анализ ниже).
|
||||
|
||||
## Принятые решения (по Опусу)
|
||||
|
||||
1. **Модель delete_params** → заменить плоский `{Code, Value}` на `{Code, Mode, Value?}`:
|
||||
- `Mode: static` — значение из `Value` (дефолт, обратная совместимость).
|
||||
- `Mode: zero_count` — обнулить integer-поля в элементах array-map-fixed, взяв live.
|
||||
2. **Баланс данные/логика**: форма преобразования выводится из `dataType`
|
||||
(boolean→"false", array-map-fixed→zero integer); сентинелы-значения — ТОЛЬКО данные в реестре.
|
||||
3. **Маркер поля**: явный `zero_fields:["count"]` (или флаг на sub_param), а НЕ «обнулить все integer»
|
||||
(риск: порт/приоритет/индекс в том же object).
|
||||
4. **Порядок destroy** — обратный порядок создания из `depends_on`.
|
||||
|
||||
## Мои замечания к ответу (что Опуc недоговорил)
|
||||
|
||||
- **A. Граф уже правильный.** Факт: `edge_net` (SNAT) зависит от `org_ips` (IP), `org_ips` — от `edge`.
|
||||
Обратный порядок destroy: `edge_net → org_ips → edge → vdc` уже корректен.
|
||||
Рекомендация Опуса «сделать ip_space зависимым от edge_net» — перепутана направлением; граф уже такой.
|
||||
- **B. Источник live для zero_count в Delete не указан.** `Delete` модификатора имеет только TF `state`,
|
||||
а live `vIPConfigure` надо читать через `GetInstanceStateParams` в рантайме Delete.
|
||||
- **C. Отличие sentinel от имени в valueList не разобрано** (valueList без разметки sentinel в данных API).
|
||||
- **D. Идемпотентность zero_count при повторном destroy не поднята** (count=0 → снова 0: no-op?).
|
||||
|
||||
## Открытые вопросы (второй раунд к Опусу) — ЗАКРЫТЫ
|
||||
|
||||
1. **Источник live для zero_count в Delete** → `GetInstanceStateParams` (не TF state). ✅
|
||||
2. **Sentinel в valueList** → явный `off_value:"no-needed"` в реестре (данные, не логика). ✅
|
||||
3. **Идемпотентность zero_count** → пропускать `run`, если live уже `count=0` (применимо и к static). ✅
|
||||
4. **count=0** → канонический inverse; полное удаление ipSpace = отдельный опциональный `Mode:remove` (не подменять zero_count). ✅
|
||||
|
||||
## ИТОГ — финальная модель inverse
|
||||
|
||||
`delete_params: []{ Code, Mode, Value?, zero_fields?, off_value? }`
|
||||
- `Mode: static` — обратное значение = `Value` (boolean→"false"; string→off_value).
|
||||
- `Mode: zero_count` — взять live array-map-fixed, обнулить поля из `zero_fields:["count"]`.
|
||||
- `Mode: remove` (опц., не для FullPipe) — полное удаление элемента.
|
||||
|
||||
Рантайм: Delete → `GetInstanceStateParams` → построить inverse → если live уже целевое → no-op (skip run) → иначе `modify`.
|
||||
|
||||
Реестр (yaml-generator main.go):
|
||||
- `vc_org.ip_space`: `inverse`, param `vIPConfigure` `Mode:zero_count, zero_fields:[count]`.
|
||||
- `vc_nsxt.network`: `inverse`, params `needEnableAVI`=static `"false"`, `ipSpaceName`=static off_value `"no-needed"`.
|
||||
|
||||
Порядок destroy (уже корректен в .tf): `edge_net → org_ips → edge → vdc`.
|
||||
|
||||
## Факты (не менять, проверено)
|
||||
|
||||
- `count=0` принимается API (minvalue:1 в схеме — не отвергает), идемпотентно (ORG_IP_MODIFIER_TEST_2026-09-22).
|
||||
- SNAT off = `ipSpaceName="no-needed"` (sentinel в valueList, HAR_SNAT_MODIFY_FINDINGS).
|
||||
- `needEnableAVI` boolean → inverse `"false"`.
|
||||
- vc_org.ip_space: `error` → нужен `inverse` (zero_count). vc_nsxt.network: inverse+static `needEnableAVI=false`, добавить `ipSpaceName="no-needed"`.
|
||||
@@ -1,264 +0,0 @@
|
||||
# Ответ Opus 4.8: код-ревью и оценка архитектуры (3 задачи roadmap)
|
||||
|
||||
Дата: 2026-09-22 | Вопрос: `docs/prompt_for_opus_review.md` (коммит `7eab45e`)
|
||||
|
||||
Ответ получен строго по контексту промпта (файлы не открывались).
|
||||
|
||||
---
|
||||
|
||||
Задание — код-ревью по контексту (файлы открывать запрещено). Отвечаю сжато по пунктам.
|
||||
|
||||
## 1. Покрывает ли модель 3 задачи
|
||||
|
||||
| Задача | Вердикт | Причина |
|
||||
|---|---|---|
|
||||
| vcOrg modify (динамич. IP) | 🟡 | Покрыта, если аллокация = modify-параметр (кол-во/список). Проблема — тип массива (array-map-fixed = JSON-строка) и семантика «растёт по мере необходимости» |
|
||||
| vcNsxt modify (SNAT + IP из пула vcOrg) | 🔴 | Текущая модель НЕ покрывает: refSvc ссылается на инстанс/uid, а нужен конкретный элемент списка IP. Нужна новая связка (computed-атрибут + ссылка) |
|
||||
| k8sShturval create | 🟢 | Стандартный CRUD-create, покрыт. Риски — только long-create и типы параметров |
|
||||
|
||||
## 2. Как моделировать пул IP vcOrg
|
||||
|
||||
- **(б) отдельный ресурс/подресурс на IP** — ✗ противоречит архитектуре: «один сервис = один ресурс», подресурсы/data source не генерируются. Потребует ломки генератора.
|
||||
- **(а) атрибут-массив на nubes_vc_org** — ✅ согласуется. Но НЕ через array-map-fixed (JSON-строка: нет семантического diff, ломается порядок, нельзя адресовать элемент).
|
||||
- **Рекомендация:** декларативное желаемое состояние — либо `int64 desired_count` (modify), либо реальный `ListNestedAttribute`; аллоцированные IP отдавать как **типизированный computed** (list объектов), а не плоскую JSON-мапу. Императивная семантика «добавь ещё один» несовместима с декларативным Terraform.
|
||||
|
||||
## 3. Как vcNsxt получает IP из vcOrg
|
||||
|
||||
| Вариант | Оценка |
|
||||
|---|---|
|
||||
| (а) refSvc-параметр | 🔴 не может указать на конкретный элемент списка, только на uid инстанса |
|
||||
| (б) computed-атрибут vcOrg + ссылка `nubes_vc_org.x.<attr>` | 🟢 лучший фит. IP в nsxt — обычная string, пользователь берёт конкретный элемент |
|
||||
| (в) data source | ✗ не генерируется |
|
||||
|
||||
**Рекомендация — (б).** Условие: computed-вывод vcOrg должен быть **индексируемым типизированным** (`allocated_ips[0]`), не спрятанным в JSON-строке `state_out_flat` — иначе граф зависимостей Terraform не построит ребро и придётся ручной `depends_on`.
|
||||
|
||||
## 4. Риски текущего кода для этих потоков
|
||||
|
||||
- 🔴 **modify с массивами:** array-map-fixed = JSON-строка → нет семантического diff, смена порядка API даёт ложные modify, «дорастить пул» = полная замена списка (опасно, возможен ре-аллок).
|
||||
- 🟡 **create-only guard:** если параметры SNAT-IP / аллокации помечены не как `is_modifiable` → ModifyPlan заблокирует задачи 1 и 2. Проверить флаги в YAML.
|
||||
- 🟡 **read-back:** если API возвращает IP в нестабильном порядке → вечный drift на vcOrg.
|
||||
- 🟡 **порядок плана:** зависимость nsxt→vcOrg держится, только если ссылаемый IP — first-class computed (unknown на plan). Внутри JSON-строки Terraform зависимость не отследит → apply-time ошибки.
|
||||
|
||||
## 5. k8sShturval create — что критично
|
||||
|
||||
- refSvc к vdc/org резолвится (display name ИЛИ uid).
|
||||
- 🔴 long-create: create-op должен **ждать готовности** (poll ready-state), а не только «принято»; следить за context timeout.
|
||||
- типы параметров: map-fixed (nested) vs array-map-fixed (JSON), корректность required/default.
|
||||
- read-back state_params после create для computed-полей.
|
||||
|
||||
## 6. Итог — рекомендации по приоритету
|
||||
|
||||
1. 🔴 Добавить в vcOrg **типизированный индексируемый computed** для аллоцированных IP (list объектов, не JSON-мапа) — разблокирует задачу 2 через ссылку.
|
||||
2. 🔴 Моделировать аллокацию как **декларативное желаемое состояние** (int64 count или ListNested) с реконсиляцией в modify; параметры пометить `is_modifiable`.
|
||||
3. 🟡 Заменить array-map-fixed (JSON-строка) на **ListNestedAttribute** — ради адресуемости элементов, семантического diff и стабильного порядка.
|
||||
4. 🟡 Гарантировать граф зависимостей: ссылаемый IP — отдельный computed-атрибут, не поле JSON, чтобы не требовать `depends_on`.
|
||||
5. 🟢 k8sShturval: добавить ожидание готовности (poll) в long-create и проверить резолв refSvc.
|
||||
|
||||
**Ключевой вывод:** задачи 1 и 3 укладываются в текущую модель с доработками генератора; **задача 2 требует новой абстракции** — типизированного computed-вывода на vcOrg (вариант 3б), т.к. refSvc принципиально не адресует элемент списка.
|
||||
|
||||
---
|
||||
|
||||
## Раунд 2 — уточняющие вопросы и пересмотр
|
||||
|
||||
После проверки кода выяснилось, что промпт раунда 1 описывал модель неполно. Opus получил уточнённый контекст и 7 доп. вопросов.
|
||||
|
||||
### Уточнение контекста (передано Opus)
|
||||
|
||||
- Генератор уже поддерживает 4 вида ресурсов: instance (`nubes_<svc>`), subresource, action (только redeploy), modifier (`nubes_<svc>_<modifier>`).
|
||||
- Modifier-ресурсы для задач 1 и 2 УЖЕ СГЕНЕРИРОВАНЫ:
|
||||
- `nubes_vc_org_ip_space` (vc_org, modify 207): `vc_org_id` (required), `v_ip_configure` (required, StringAttribute = JSON-строка). Computed-выводов НЕТ.
|
||||
- `nubes_vc_nsxt_network` (vc_nsxt, modify 111): `vc_nsxt_id` (required), `need_enable_avi` (bool), `virtual_services_count` (int64), `ip_space_name` (string optional), `qos_profile` (string), `routed_net_configuration` (required, JSON-строка). Computed-выводов НЕТ.
|
||||
- Маппинг: `map-fixed` → SingleNestedAttribute; `array-map-fixed` → StringAttribute (JSON-строка).
|
||||
- Завершение операции — поллинг до `dtFinish`. Delete modifier — no-op.
|
||||
- Связка NSXT↔Org идёт **по имени ipSpace**: `vc_org.modify` задаёт `vIPConfigure[].name`, `vc_nsxt.modify` принимает `ipSpaceName` = это имя. Конкретный IP нигде не адресуется.
|
||||
|
||||
### Ответ Opus (пересмотр)
|
||||
|
||||
## 1. Верен ли прошлый вердикт про «новую абстракцию»
|
||||
|
||||
🟢 **Прошлый вывод отменяется.** Он строился на неполной модели. modifier-ресурсы уже существуют → задача 2 реализуема сегодня двумя модификаторами (`nubes_vc_org_ip_space` → `nubes_vc_nsxt_network`), связанными по имени ipSpace. Никакой новой абстракции и ломки генератора не требуется.
|
||||
|
||||
## 2. Минимальная доработка генератора для ссылки вместо строки
|
||||
|
||||
Добавить **один computed-вывод** на `nubes_vc_org_ip_space` — эхо `v_ip_configure[].name` (имя ipSpace). Тогда:
|
||||
```
|
||||
ip_space_name = nubes_vc_org_ip_space.x.ip_space_name
|
||||
```
|
||||
Это даёт ребро графа + защиту от опечатки. Адресовать IP не нужно — связка по имени. Опционально сверх минимума: валидация, что имя существует в org (refSvc-подобная проверка `ip_space_name` по vIPConfigure целевого org).
|
||||
|
||||
## 3. SNAT: свободная строка или computed обязателен
|
||||
|
||||
- Функционально **свободная строка + `depends_on` работает** (связка по имени, IP не адресуется). 🟡 Риски: опечатка, нет автоматического ребра графа без ручного depends_on, нет drift-детекта.
|
||||
- **Экспортировать имя (string), НЕ список IP.** Список IP не нужен — привязка идёт по имени ipSpace, не по конкретному адресу.
|
||||
- Вердикт: не строго обязателен для функции, но настоятельно рекомендован для корректности графа. Минимум — computed `ip_space_name` (string).
|
||||
|
||||
## 4. v_ip_configure как JSON-строка — реально ли бьёт
|
||||
|
||||
🟢 **В этом сценарии практически безвредно.** Значение фактически write-only (computed read-back нет), потребителю нужно только имя, которое пользователь сам задал. Поэлементное сравнение/адресация не требуются → прошлый 🔴 понижается.
|
||||
|
||||
Остаточный 🟡: спурьёзный diff при переформатировании JSON пользователем; если count растёт со временем — семантика «полная замена списка» без гранулярного diff может ре-триггерить modify/ре-аллокацию. Но без read-back и при связке по имени это не блокер.
|
||||
|
||||
## 5. Delete modifier = no-op
|
||||
|
||||
🟡 **No-op допустим** для association/one-shot модификатора, если обратного payload нет — это известный паттерн.
|
||||
|
||||
**Реальные последствия destroy:** Terraform удалит запись из state, но бэкенд НЕ освободит IP и НЕ отключит SNAT → утечка ресурсов + расхождение state↔бэкенд.
|
||||
|
||||
- Если бэкенд поддерживает обратную операцию (де-аллокация / disable SNAT) — no-op это реальный пробел 🔴, надо подключить reverse-op.
|
||||
- НЕДОСТАТОЧНО ДАННЫХ: есть ли у vc_org/vc_nsxt обратный payload. Если его действительно нет — no-op единственный вариант, но обязательно задокументировать поведение destroy.
|
||||
|
||||
## 6. Нужен ли ещё «int64 desired_count на инстансе»
|
||||
|
||||
🟢 **Нет, прошлый п.2 отменяется.** modifier — правильная единица для чередующейся зависимости (org аллоцирует → nsxt потребляет → org доаллоцирует). Счётчик на инстанс-ресурсе связал бы жизненный цикл пула с жизненным циклом инстанса и сломал бы взаимное чередование org↔nsxt. **Отдельный modifier строго лучше.**
|
||||
|
||||
## 7. k8sShturval create — что остаётся критичным
|
||||
|
||||
Помимо резолва refSvc (display name ИЛИ uid) и корректности required/default в map-fixed/array-map-fixed:
|
||||
- 🟡 **read-back computed после create** (kubeconfig/vault-выводы) — что реально возвращается в state.
|
||||
- 🟡 **обработка failure-состояния в поллинге**: dtFinish должен различать «готово успешно» и «завершилось с ошибкой», иначе провал маскируется под успех. НЕДОСТАТОЧНО ДАННЫХ по логике ошибки в poll-цикле.
|
||||
- 🟢 timeout-бюджет long-create vs интервал поллинга.
|
||||
|
||||
---
|
||||
|
||||
**Итог пересмотра:** обе задачи 1 и 2 **укладываются в существующий modifier-паттерн без новых абстракций**. Единственная стоящая доработка — добавить computed-экспорт `ip_space_name` на org-модификатор (п.2/3), чтобы заменить свободную строку ссылкой и построить граф зависимостей. Проблема JSON-строки в этом сценарии не критична (п.4). Главный остаточный риск — no-op delete и утечка IP/SNAT при destroy (п.5), требует проверки наличия обратного payload.
|
||||
|
||||
---
|
||||
|
||||
## Раунд 3 — дрейф сгенерированного кода и защитный механизм
|
||||
|
||||
Отдельная тема (не roadmap): расхождение `generated/{stand}/go` ↔ `provider/internal/resources_gen`, как предотвратить и сделать видимым.
|
||||
|
||||
### Контекст, переданный Opus
|
||||
|
||||
Go-провайдер Terraform. Ресурсы генерируются из YAML-спеков генератором.
|
||||
Цепочка: YAML → generated/{stand}/go/*.go → (копирование) → provider/internal/resources_gen/*.go → сборка бинаря.
|
||||
|
||||
Факты:
|
||||
- generated/ и provider/internal/resources_gen/ внесены в .gitignore → дрейф между ними НЕ виден в git status/diff.
|
||||
- Реальный дрейф уже случился: копия в provider/ устарела (сгенерирована до двух последних фиксов генератора).
|
||||
- Есть два скрипта сборки: (а) правильный — копирует свежий generated во временный каталог provider и собирает оттуда; (б) рисковый — собирает напрямую из provider/ (берёт потенциально устаревшую локальную копию).
|
||||
|
||||
Вопросы: 1) коммитить автоген или .gitignore+проверка; 2) два пути сборки — корень ли; 3) что предотвращает дрейф, а не детектит; 4) убрать вторую копию и генерировать при сборке; 5) как гарантированно «видно сразу при изменении кода».
|
||||
|
||||
### Ответ Opus
|
||||
|
||||
## 1. Коммитить сгенерированный код или .gitignore + проверка дрейфа
|
||||
|
||||
🟢 **Коммитить сгенерированный код** (для соло/малой команды это надёжнее). Обоснование:
|
||||
- Расхождение всплывает в `git diff`/PR-ревью — там, куда уже смотришь, а не в отдельной проверке, которую надо не забыть запустить.
|
||||
- CI-гейт `regenerate && git diff --exit-code` превращает дрейф в жёсткую ошибку сборки.
|
||||
- Минус — шум в диффах от больших автогенов, но это приемлемая цена за видимость.
|
||||
|
||||
.gitignore + внешняя проверка дрейфа 🟡 — работает, только если проверку невозможно обойти; сейчас её обходят вторым скриптом. Скрытый код = скрытый дрейф (уже случилось).
|
||||
|
||||
## 2. Два пути сборки — корень проблемы?
|
||||
|
||||
🔴 **Да, это корень.** Проблема не в дрейфе как таковом, а в том, что **существует путь, собирающий из потенциально устаревшей копии**. Пока рисковый скрипт (б) доступен, дрейф рано или поздно попадёт в бинарь.
|
||||
|
||||
**Оставить ровно один канонический путь.** Скрипт (б) удалить или сделать так, чтобы он физически не мог собрать из несвежей копии (см. п.3–4).
|
||||
|
||||
## 3. Что ПРЕДОТВРАЩАЕТ дрейф, а не только детектит
|
||||
|
||||
| Механизм | Роль |
|
||||
|---|---|
|
||||
| Жёсткий fail на старте сборки | 🟡 **детект** — не даёт собрать плохой бинарь, но дрейф уже есть |
|
||||
| Авто-синхронизация/генерация перед сборкой | 🟢 **предотвращение** — копия физически не может быть устаревшей |
|
||||
| git pre-commit hook | 🟡 **детект** на этапе коммита |
|
||||
|
||||
**Предотвращает только генерация/синк как обязательная часть сборки** (копия всегда свежая). Fail и hook — детекторы. Рекомендуемая комбинация: **генерация-при-сборке (предотвращение) + CI `git diff --exit-code` после регенерации (страховка от обхода)**.
|
||||
|
||||
## 4. Более сильный паттерн: убрать вторую копию, генерировать при сборке
|
||||
|
||||
🟢 **Да, это сильнейший вариант.** Нет постоянной копии → нечему рассинхронизироваться. Единственный источник истины = YAML + генератор (`go generate` / шаг Makefile перед `go build`).
|
||||
|
||||
Минусы:
|
||||
- 🟡 Сборка зависит от наличия и детерминированности генератора (версию генератора надо пинить).
|
||||
- 🟡 Медленнее сборка.
|
||||
- 🔴 Провайдер перестаёт быть чистым Go-модулем: нельзя `go get`/собрать без тулчейна генератора; воспроизводимость завязана на версию генератора.
|
||||
- Сложнее просто «посмотреть» сгенерированный код без сборки.
|
||||
- НЕДОСТАТОЧНО ДАННЫХ: детерминирован ли генератор (стабильный порядок полей/файлов) — без этого diff-гейт будет шуметь.
|
||||
|
||||
**Компромисс:** генерировать при сборке И коммитить результат (генерация переписывает закоммиченную копию, CI ловит расхождение) — совмещает «невозможно устареть» с «видно в диффе».
|
||||
|
||||
## 5. «Видно сразу при изменении кода, не закопано в доках»
|
||||
|
||||
Гарантированно это даёт **только закоммиченный сгенерированный код**: расхождение появляется в `git diff` того же PR, где меняется генератор/YAML. Усилить:
|
||||
- **pre-commit hook** `regenerate` → расхождение всплывает до коммита;
|
||||
- **CI-гейт** `regenerate && git diff --exit-code` → нельзя смёржить с дрейфом.
|
||||
|
||||
Вариант «генерировать при сборке без копии» даёт *другую* гарантию — «устареть невозможно», но там **нет диффа для обзора**. Если требование именно «видно в изменениях кода» — нужен закоммиченный автоген.
|
||||
|
||||
---
|
||||
|
||||
**Итог — рекомендация по приоритету:**
|
||||
1. 🔴 Убрать рисковый путь сборки (б); оставить один канонический.
|
||||
2. 🟢 Сделать генерацию/синк обязательным шагом канонической сборки (предотвращение).
|
||||
3. 🟢 Закоммитить сгенерированный код + CI-гейт `regenerate && git diff --exit-code` (видимость + защита от обхода).
|
||||
4. 🟡 Запинить версию генератора и обеспечить детерминированный вывод, иначе гейт будет ложно срабатывать.
|
||||
|
||||
---
|
||||
|
||||
## Раунд 4 — универсальность и реестр исключений
|
||||
|
||||
Тема: принцип «универсально по умолчанию, исключения — только если API не ложится», и как кодировать исключения, чтобы они были однозначно понимаемы при каждой генерации.
|
||||
|
||||
### Контекст, переданный Opus (кратко)
|
||||
|
||||
Пайплайн: YAML-спека сервиса (из API) → универсальный генератор → Go-ресурсы → сборка. Код/скрипты/логика генерации — одни для всех стендов и сервисов; различается только YAML. Механизм modifier-ресурсов уже универсален (дженерик читает `kind: modifier` + имя). Не универсально сейчас: хардкод в yaml-generator (`svc.ID == 19 → ip_space`, `svc.ID == 22 → network`) и в docs-generator (`ServiceID == 90`, спец-вывод postgres). Отдельно был инцидент дрейфа git-ignored сгенерированного кода (фон).
|
||||
|
||||
Выработанный принцип (просили оценить): «Универсально по умолчанию. Исключение — только если API реально не ложится. Каждое исключение — одна явная таблица/структура, которую КОД ЧИТАЕТ, с полями ЧТО/ЧТО ДЕЛАЕТ/ПОЧЕМУ/ГДЕ. Канонический список — в ARCHITECTURE-доке, код ссылается. Изменение = изменение таблицы → видно в диффе.»
|
||||
|
||||
### Ответ Opus
|
||||
|
||||
## 1. Оценка принципа
|
||||
|
||||
🟢 **Принцип корректен долгосрочно** — «convention over configuration + escape hatch».
|
||||
|
||||
Подводные камни:
|
||||
- 🟡 «API не ложится» субъективно → нужен объективный тест-триггер, иначе exception creep.
|
||||
- 🔴 Исключения не возвращаются в ядро: когда паттерн повторился 2–3 раза, нужен ритуал «промоушена» в ядро.
|
||||
- 🟡 Обратный перекос: обобщать реально одноразовый случай — раздувает ядро.
|
||||
|
||||
## 2. Как кодировать исключения
|
||||
|
||||
| Вариант | Видимость в diff | Нельзя «проспать» | Поддержка | Рассинхрон с YAML |
|
||||
|---|---|---|---|---|
|
||||
| (а) именованная таблица в коде, код её читает | 🟢 | 🟢 (если итерирует и падает на неучтённом) | 🟡 нужна пересборка | 🟢 низкий |
|
||||
| (б) отдельный yaml-конфиг | 🟢 | 🟡 легко забыть подключить | 🟢 без пересборки | 🟡 средний |
|
||||
| (в) аннотации в YAML-спеке | 🔴 | 🔴 | 🔴 | 🔴 фатально: YAML регенерится → аннотации затираются |
|
||||
|
||||
**(в) отклонить.** **Рекомендация: (а)** — именованная структура, которую код итерирует и ассертит.
|
||||
|
||||
## 3. Граница «логика» vs «данные»
|
||||
|
||||
- В ядре — механизм/алгоритм (как модификатор генерится, маппинг схемы). Никогда не per-service.
|
||||
- В реестре — чистые данные («сервис X → имя модификатора Y», «сервис 90 → набор полей Z»).
|
||||
|
||||
Признаки: 1) `if id == N`, меняющий поток исполнения → извлечь данные; 2) убрать пункт → меняются только значения, не поведение → данные; 3) copy-paste кода → механизм (обобщать), разные строки таблицы → данные.
|
||||
|
||||
## 4. Паттерн «override registry» в кодогенераторах
|
||||
|
||||
- tfplugingen-openapi: `generator_config.yml` отдельно от спеки + IR (`terraform-plugin-codegen-spec`).
|
||||
- OpenAPI Generator: vendor extensions `x-*` + template-оверрайды + config-json.
|
||||
- protoc-плагины: custom options (напр. `google.api.http`).
|
||||
|
||||
Что перенять: 1) отдельный версионируемый конфиг оверрайдов; 2) IR-слой; 3) fail на неучтённом; 4) стабильное символьное имя, не сырой ID.
|
||||
|
||||
## 5. Не противоречит ли реестр «YAML — единственный источник»
|
||||
|
||||
Не противоречит — при разделении двух доменов истины:
|
||||
- **API-YAML** = истина про «что есть сервис» (машинно-владеемый, регенерится).
|
||||
- **Реестр оверрайдов** = истина про «наши провайдер-специфичные решения» (человеко-владеемый).
|
||||
|
||||
Теневой источник — только если ОДИН факт лежит в обоих. 🔴 Нельзя аннотировать API-YAML. Конвейер: `API-YAML + overrides → merged IR → codegen`.
|
||||
|
||||
## 6. Риски «таблица + раздел в ARCHITECTURE.md»
|
||||
|
||||
- 🔴 ARCHITECTURE.md дрейфует от таблицы → возврат к `if id==19`, если кто-то добавит ветку в обход.
|
||||
- 🔴 Числовые ID (19/22/90) непрозрачны.
|
||||
|
||||
Как закрыть: 1) 🔴 единая точка маршрутизации + CI-lint/grep-гейт против `svc.ID ==` вне реестра; 2) раздел ARCHITECTURE генерировать ИЗ реестра (golden-test); 3) 4 поля — поля структуры, а не комментарии; 4) стабильные символьные ключи; 5) fail-fast: генератор падает, если спец-обработка без записи в реестре.
|
||||
|
||||
---
|
||||
|
||||
**Итог:** сильнейшая реализация — отдельный человеко-владеемый override-реестр (данные, не логика), стабильные ключи, IR-слой применения, CI-гейт против хардкодов вне реестра, раздел ARCHITECTURE генерируется из реестра. Это устраняет и `if id==N`, и дрейф доки.
|
||||
@@ -1,44 +0,0 @@
|
||||
Ты — технический редактор документации Terraform-провайдера Nubes Cloud.
|
||||
|
||||
Ниже тебе даны Markdown-файлы документации ОДНОГО сервиса.
|
||||
Твоя задача — ТОЛЬКО улучшить читаемость. НЕ МЕНЯЙ техническое содержание.
|
||||
|
||||
⛔ ЗАПРЕТЫ:
|
||||
1. НЕ удаляй/добавляй параметры, типы, default, ID, имена ресурсов
|
||||
2. HTML-таблицы: меняй ТОЛЬКО текст внутри <td>...</td>
|
||||
3. НЕ трогай HCL-блоки (```hcl ... ```)
|
||||
4. НЕ трогай navigation-строки [Manual](...)
|
||||
5. НЕ меняй заголовки (#, ##, ###)
|
||||
6. НЕ меняй имена nubes_*
|
||||
|
||||
✅ ДЕЛАТЬ:
|
||||
1. MAN-секцию: HTML → читаемый Markdown (сохранить ВЕСЬ текст)
|
||||
2. Описания: связные предложения на русском
|
||||
3. Единообразие отступов и форматирования
|
||||
|
||||
📤 ОТВЕТ: СТРОГО JSON {"имя_файла.md": "полный текст", ...}
|
||||
|
||||
📥 КАК ГОТОВИТЬ ВХОДНЫЕ ДАННЫЕ:
|
||||
Взять файлы из директории generated/test/docs/ для сервиса postgres:
|
||||
- postgres.md
|
||||
- postgres_example.md
|
||||
- postgres_params_create.md
|
||||
- postgres_params_modify.md
|
||||
- postgres_outputs.md
|
||||
- postgres_ops.md
|
||||
- postgres_params.md
|
||||
- postgres_backup.md
|
||||
- postgres_backup_example.md
|
||||
- postgres_database.md
|
||||
- postgres_database_example.md
|
||||
- postgres_user.md
|
||||
- postgres_user_example.md
|
||||
|
||||
Скопировать содержимое каждого файла в секцию ниже.
|
||||
Заменить {service} на имя сервиса (например postgres).
|
||||
|
||||
--- {service}.md ---
|
||||
{вставить содержимое}
|
||||
--- {service}_params_create.md ---
|
||||
{вставить содержимое}
|
||||
... (повторить для всех файлов)
|
||||
@@ -1,130 +0,0 @@
|
||||
# ТЗ для DeepSeek Flash: убрать create-time проверку существования из `ModifyPlan`
|
||||
|
||||
Дата: 2026-09-21 | Статус: не сделано | Версия провайдера на момент бага: 2.0.6
|
||||
|
||||
## Цель
|
||||
|
||||
Починить `terraform destroy` (и любую `tainted`-замену), который падает с
|
||||
`РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)`.
|
||||
|
||||
## Контекст
|
||||
|
||||
- Репозиторий: `/home/naeel/TF/tf_provider`
|
||||
- Провайдер: `terraform-provider-nubes`, Go, `terraform-plugin-framework v1.8.0`
|
||||
- Ресурсы генерируются шаблоном, **НЕ правятся руками**
|
||||
- Модуль провайдера живёт в `provider/` (не в корне репозитория)
|
||||
|
||||
## Симптом
|
||||
|
||||
```
|
||||
$ terraform destroy
|
||||
nubes_vc_vdc.vdc: Refreshing state... [id=db2cefc3-...]
|
||||
nubes_vc_nsxt.edge: Refreshing state... [id=8AAEC14D-...]
|
||||
│ Error: РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)
|
||||
│ with nubes_vc_nsxt.edge,
|
||||
│ on edge.tf line 1, in resource "nubes_vc_nsxt" "edge":
|
||||
```
|
||||
|
||||
То же самое при обычном `terraform plan`.
|
||||
|
||||
## Причина (подтверждена фактами)
|
||||
|
||||
1. `nubes_vc_nsxt.edge` в state помечен **`tainted`** (следствие прошлой неудачной
|
||||
apply с `vdc_group_uid`: `Provider returned invalid result object after apply`).
|
||||
Проверка: `terraform.tfstate` → `instances[].status == "tainted"`.
|
||||
2. Tainted-ресурс Terraform обязан **заменить** (destroy + create). Это видно в плане:
|
||||
|
||||
```
|
||||
# nubes_vc_nsxt.edge is tainted, so must be replaced
|
||||
-/+ resource "nubes_vc_nsxt" "edge" {
|
||||
```
|
||||
|
||||
3. `terraform destroy` сначала выполняет **внутренний обычный plan**
|
||||
(`Context.destroyPlan: calling Context.plan` — видно в `TF_LOG=TRACE`), и уже
|
||||
на этом шаге планируется замена edge.
|
||||
4. Create-узел замены вызывает `ModifyPlan` с **prior state = null**, поэтому guard
|
||||
`if state != nil && !state.ID.IsNull() && ...` пропускается, и доходит до
|
||||
create-time проверки существования.
|
||||
5. Проверка находит **живой** инстанс в облаке (старый edge ещё не удалён — удаление
|
||||
идёт на apply) → `РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)` → plan падает →
|
||||
`destroy` не начинается.
|
||||
|
||||
Ключевое: на уровне `ModifyPlan` **невозможно** отличить «создание нового ресурса»
|
||||
от «create-узла замены» — у обоих prior state = null. Поэтому проверки существования
|
||||
в `ModifyPlan` быть не должно в принципе.
|
||||
|
||||
Доказательство, что вызов идёт из `ModifyPlan`: `/tmp/nubes_find_debug.log` содержит
|
||||
`[FIND-DEBUG] PlanExistingResourceDiagnostics entered: serviceId=22 name="fullpipe-edge"`.
|
||||
Эту строку пишет только Plan-функция; `Create...` в этот лог не пишет.
|
||||
|
||||
## Правка
|
||||
|
||||
**Один файл:** `TOOLS/resource-generator/internal/templates/instance.go`, шаблон метода `ModifyPlan`.
|
||||
|
||||
Удалить целиком блок от строки
|
||||
|
||||
```go
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
до строки
|
||||
|
||||
```go
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, {{.ServiceID}}, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs, {{.SupportsSuspendDestroy}})...)
|
||||
```
|
||||
|
||||
включительно. Это весь хвост `ModifyPlan` после блока «Missing required attribute»:
|
||||
`adoptExistingOnCreate`, resolve refSvc, `params`, `desiredDomain`, `domainServiceIDs`
|
||||
и сам вызов диагностики.
|
||||
|
||||
### Что НЕ трогать
|
||||
|
||||
- destroy-guard `if req.Plan.Raw.IsNull() { return }` — **оставить**;
|
||||
- блок create-only проверок (по `state.ID`) — **оставить**;
|
||||
- блок «Missing required attribute» — **оставить**;
|
||||
- `Create` — там вызов `CreateExistingResourceDiagnosticsWithDomainAndServices`
|
||||
**остаётся**: проверка выполняется на apply, уже после удаления старого инстанса.
|
||||
|
||||
### Побочный эффект (принять как норму)
|
||||
|
||||
Из plan пропадают проверки ref-параметров / domain / существования по имени. Это
|
||||
штатное поведение Terraform: на apply `Create` резолвит refSvc (с ошибкой) и делает
|
||||
проверку существования.
|
||||
|
||||
## Проверка
|
||||
|
||||
```bash
|
||||
cd /home/naeel/TF/tf_provider && ./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev
|
||||
```
|
||||
|
||||
```bash
|
||||
TMP=$(mktemp -d) && cp -R provider "$TMP/provider" && find "$TMP/provider/internal/resources_gen" -maxdepth 1 -type f -name '*.go' -delete && cp generated/dev/go/*.go "$TMP/provider/internal/resources_gen/" && (cd "$TMP/provider" && go build ./...) && echo BUILD_OK && rm -rf "$TMP"
|
||||
```
|
||||
|
||||
Затем проверить сгенерированный код:
|
||||
|
||||
- `generated/dev/go/22_vc_nsxt_resource.go`: в `ModifyPlan` вызова
|
||||
`PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices` больше нет;
|
||||
- в `Create` вызов `CreateExistingResourceDiagnosticsWithDomainAndServices` остался.
|
||||
|
||||
Если после удаления какой-то импорт стал неиспользуемым (`fmt`, `resources_core`) —
|
||||
проверить сборкой. Обычно `Create` сохраняет те же импорты, отдельная правка флага
|
||||
`NeedsFmtImport` не требуется.
|
||||
|
||||
## Что НЕ делать
|
||||
|
||||
- **НЕ собирать и НЕ заливать** провайдер — только правка шаблона + генерация + сборка-проверка.
|
||||
- Не править сгенерированный код руками.
|
||||
- Не трогать `provider/internal/resources_core/resource_diagnostics_required.go`.
|
||||
|
||||
## Критерий готовности
|
||||
|
||||
`BUILD_OK` и в сгенерированном edge `ModifyPlan` нет create-time проверки.
|
||||
|
||||
## Обходной путь без правок (если надо убить стенд прямо сейчас)
|
||||
|
||||
```bash
|
||||
terraform untaint nubes_vc_nsxt.edge && terraform destroy
|
||||
```
|
||||
@@ -1,41 +0,0 @@
|
||||
# Промпт для Opus 4.8: код-ревью и оценка архитектуры (3 задачи roadmap)
|
||||
|
||||
Дата: 2026-09-22 | Статус: для отправки
|
||||
|
||||
```text
|
||||
Роль: ревьюер архитектуры Go-провайдера Terraform.
|
||||
|
||||
ПРАВИЛА:
|
||||
- Файлы НЕ открывай, в репозиторий не лезь — отвечай только по контексту ниже.
|
||||
- Анализируй ТОЛЬКО 3 указанные задачи, не весь проект.
|
||||
- Ответ максимально сжатый: только выводы/риски/рекомендации. Без вводных, без «рассмотрим», без повторов. Списки или таблица. Риск помечай 🔴/🟡/🟢.
|
||||
- Если для ответа не хватает факта — пиши «НЕДОСТАТОЧНО ДАННЫХ: …», не выдумывай.
|
||||
|
||||
КОНТЕКСТ (достаточен, файлы не нужны):
|
||||
- terraform-provider-nubes, Go, terraform-plugin-framework v1.8.0. Ресурсы ГЕНЕРИРУЮТСЯ из YAML-спек сервисов (не рукописные).
|
||||
- Один сервис → один ресурс инстанса nubes_<service> (CRUD). В YAML: operations (create/modify/suspend/…), params с type (bool/int64/string/map-fixed/array-map-fixed), required, default, is_modifiable, ref_svc_id.
|
||||
- Schema: param → Required (если required, без default, не refSvc); Optional; Computed+Default (если default); Optional+Computed (если параметр читается обратно из state_params инстанса, или это JSON).
|
||||
- Create: резолвит refSvc-параметры (принимают display name ИЛИ UUID → uid в API), вызывает create-op, затем читает state обратно.
|
||||
- Update: если изменились modify-параметры → вызывает modify-op с ними. ModifyPlan запрещает менять create-only атрибуты (ошибка).
|
||||
- Read: читает state_params инстанса обратно в input-поля (drift), и выставляет computed-мапы: state_params, state_out, state_params_flat, state_out_flat + vault_*.
|
||||
- Delete: suspend / delete / state_only — в зависимости от наличия suspend-op у сервиса.
|
||||
- Межресурсные зависимости: пользователь в .tf ссылается на атрибуты других ресурсов (напр. nubes_vc_vdc.vdc.id). ref_svc_id валидирует значение по инстансам целевого сервиса и резолвит в uid.
|
||||
- Data sources НЕ генерируются (только ресурсы).
|
||||
- map-fixed → SingleNestedAttribute (HCL: `x = { … }`); array-map-fixed → StringAttribute (JSON-строка).
|
||||
- Сервисы: vc_org (19), vc_vdc (21), vc_nsxt (22). Спека k8s_shturval уже есть.
|
||||
|
||||
УЖЕ СДЕЛАНО: vcVdc create, vcNsxt create — работают (v2.0.8).
|
||||
|
||||
АНАЛИЗИРОВАТЬ (только это):
|
||||
1. vcOrg modify — аллокация внешних IP в организацию ПО МЕРЕ НЕОБХОДИМОСТИ (динамически, число заранее не фиксировано).
|
||||
2. vcNsxt modify — включить SNAT и указать внешний IP, взятый из уже аллоцированного пула vcOrg.
|
||||
3. k8sShturval create.
|
||||
|
||||
ВОПРОСЫ (ответь по пунктам, кратко):
|
||||
1. Покрывает ли текущая модель эти 3 задачи, или для какой-то нужна новая абстракция (data source / action / subresource)? По каждой задаче — вердикт.
|
||||
2. vcOrg IP-аллокация: как моделировать пул внешних IP, растущий по мере необходимости — (а) атрибут-массив на nubes_vc_org, (б) отдельный ресурс/подресурс на каждый IP? Что лучше согласуется с текущей архитектурой и почему.
|
||||
3. vcNsxt: как передать «внешний IP из vcOrg»? Сравни: (а) refSvc-параметр, (б) computed-атрибут vcOrg + ссылка nubes_vc_org.<name>.<attr>, (в) data source. Учти ограничение: refSvc ссылается на инстанс/uid, но не на конкретный элемент списка.
|
||||
4. Риски текущего кода именно для этих потоков: update/modify с массивными параметрами; create-only guard; read-back; порядок плана между зависимыми ресурсами.
|
||||
5. k8sShturval create: что критично проверить (refSvc к vdc/org, долгий create, типы параметров)?
|
||||
6. Итог: 3–5 конкретных рекомендаций по приоритету — что добавить/изменить в генераторе или ресурсах.
|
||||
```
|
||||
@@ -1,28 +0,0 @@
|
||||
# Задача: 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-тест запустить.
|
||||
@@ -1,46 +0,0 @@
|
||||
# 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`?
|
||||
|
||||
## Формат ответа
|
||||
На каждый вопрос: ДА/НЕТ + код (файл:строка) + конкретное исправление.
|
||||
Не читай другие файлы.
|
||||
@@ -1,51 +0,0 @@
|
||||
# Промпт для Opus: IaC-развёртывание Штурвала, проблема `modify` и скрытых зависимостей
|
||||
|
||||
## Правила ответа (жёстко)
|
||||
|
||||
1. НЕ лезь в файлы/репозиторий/сеть. Отвечай ТОЛЬКО по материалу ниже.
|
||||
2. Отвечай КРАТКО, тезисами, по номерам вопросов. Без простыней.
|
||||
3. Токены/секреты/креды НЕ нужны — если захочешь, не упоминай и не проси.
|
||||
4. Если для ответа не хватает данных — прямо пиши «неизвестно», не выдумывай.
|
||||
5. Не предлагай «ручной ЛК / скрипт / пресеты дефолтного окружения» как решение IaC — это уже отклонено (клиенту нужен полноценный IaC).
|
||||
|
||||
## Контекст
|
||||
|
||||
Terraform-провайдер для Nubes Cloud. Клиенту нужен IaC: один конфиг + `terraform apply` = вся инфраструктура. Цепочка Штурвала:
|
||||
|
||||
```
|
||||
vcOrg -> create
|
||||
vcVdc -> create
|
||||
vcNsxt -> create
|
||||
vcOrg -> modify (аллокация внешних IP)
|
||||
vcNsxt -> modify (включить SNAT, указать внешний IP из vcOrg)
|
||||
k8sShturval -> create
|
||||
```
|
||||
|
||||
Операции строго последовательны.
|
||||
|
||||
Факты (подтверждены):
|
||||
- Провайдер генерируется из YAML-спеков. Схема tf-ресурса строится ТОЛЬКО из операции `create`.
|
||||
- `vIPConfigure` (array-map-fixed, sub: name/count) есть только в `modify` vc_org (id 207); в `create` (136) его нет.
|
||||
- `ipSpaceName` (string) есть только в `modify` vc_nsxt (id 111); в `create` (10) его нет.
|
||||
- `vIPConfigure` — **не накопительный, а replace-семантика** (подтверждено `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md`): повторный `modify` с тем же `count` не аккумулирует IP (1→1), работает в обе стороны (вверх/вниз/до 0), `count=0` принимается. Значение задаётся целиком, читается из `state.params`.
|
||||
- `ipSpaceName` выводится из цепочки `providerVdc -> providerGateway -> ipSpace`, которую пользователь не знает. Сейчас платформа «подкладывает» недостающие параметры при создании пустой орги.
|
||||
- Допущение платформы: в организации один T0/провайдер-шлюз. Рост числа T0 отложен.
|
||||
- Платформа в движении: форма ресурсов зависит от новых спеков (ждут, придут сначала в sandbox).
|
||||
|
||||
Прецедент (VCD): та же цепочка делается отдельными ресурсами с `depends_on` — `vcd_nsxt_alb_settings` (count + is_active), `vcd_nsxt_alb_edgegateway_service_engine_group` (reserved_virtual_services), `vcd_network_routed_v2`, `vcd_ip_space_custom_quota` (на оргу). Включение/выключение = `count`, inverse = удаление ресурса.
|
||||
|
||||
Разница с каноном: у нас нет отдельного API-объекта под модификацию — только операция `modify` над родителем (Read = чтение родителя, Delete = обратный modify, нужна идемпотентность).
|
||||
|
||||
## Вопросы
|
||||
|
||||
1. `vIPConfigure` уже ведёт себя как replace-состояние (идемпотентно, обе стороны, `count=0` читается из `state.params`). Как это оформить в tf-ресурсе, чтобы Read брал `state.params`, а Delete (inverse) выставлял `count=0` — если отдельного API-объекта нет?
|
||||
|
||||
2. Как провайдер должен получать выводимое значение `ipSpaceName` (цепочка providerVdc → providerGateway → ipSpace): data-source, вычисляемое из state родителя, или иное? Где граница «данные vs логика», что хранить в реестре, что выводить из типа/state?
|
||||
|
||||
3. Как спроектировать форму ресурсов, чтобы не завязываться на допущение «в организации один T0», и что сломается/что менять, если T0 станет больше одного?
|
||||
|
||||
4. Стоит ли ждать новых спеков платформы перед проектированием ресурсов, или форму ресурсов можно зафиксировать уже сейчас так, чтобы она пережила изменение спеков? Что в спеках — блокер, что — нет?
|
||||
|
||||
5. Минимально-инвазивный порядок внедрения: что должно прийти от платформы (spec/API) до того, как мы начинаем кодить, а что можем сделать на стороне провайдера уже сейчас?
|
||||
|
||||
Отвечай по номерам, кратко.
|
||||
@@ -1,61 +0,0 @@
|
||||
# Задача: спроектировать ПРОСТУЮ логику «изменяемости» параметров (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-наслоений, набор правил.
|
||||
|
||||
Ответ — кратко, с конкретной архитектурой и точками правки (файл + функция).
|
||||
@@ -1,73 +0,0 @@
|
||||
# Спроектировать С НУЛЯ архитектуру/логику «ресурсов-модификаторов» (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.
|
||||
|
||||
Ответ — архитектурный документ (краткий, структурированный), с конкретными файлами
|
||||
и функциями. НЕ код-ревью, а ПРОЕКТ.
|
||||
@@ -1,64 +0,0 @@
|
||||
# Уточнения к архитектуре модификаторов — расхождения с фактическим кодом
|
||||
|
||||
Не принимаю предыдущие ответы за истину. Сверка с реальным кодом выявила расхождения.
|
||||
Прошу пересмотреть/уточнить.
|
||||
|
||||
## Факт №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, пока не разберутся)? Что каноничнее?
|
||||
|
||||
Ответ — кратко, по пунктам.
|
||||
@@ -1,41 +0,0 @@
|
||||
# Баг: 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)?
|
||||
|
||||
Ответ кратко, тезисно, с указанием конкретной строки/места фикса.
|
||||
@@ -1,39 +0,0 @@
|
||||
# Ревью плана реализации: редизайн модификаторов
|
||||
|
||||
Прошу отревьюить план `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?
|
||||
|
||||
Ответ — тезисно, с указанием конкретного шага и что в нём поправить.
|
||||
@@ -1,29 +0,0 @@
|
||||
# Код-ревью: модификаторы (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 самых критичных замечания именно по модификаторам.
|
||||
|
||||
Ответ — кратко, тезисно, без кода-простыней.
|
||||
@@ -1,45 +0,0 @@
|
||||
# Проверка решения бага 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, рефакторинг или дополнительные исследования.
|
||||
@@ -1,359 +0,0 @@
|
||||
# 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