From 97d5ca818e5cc609264771a59a65506ba63afbc1 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Thu, 24 Sep 2026 09:32:55 +0300 Subject: [PATCH] =?UTF-8?q?docs(plans):=20=D0=BF=D0=BB=D0=B0=D0=BD=20?= =?UTF-8?q?=D0=B4=D0=B2=D1=83=D1=85=20=D1=80=D0=B5=D1=81=D1=83=D1=80=D1=81?= =?UTF-8?q?=D0=BE=D0=B2-=D0=BC=D0=BE=D0=B4=D0=B8=D1=84=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BE=D0=B2=20(nubes=5Fvc=5Forg=5Fip=5Falloc?= =?UTF-8?q?ation,=20nubes=5Fvc=5Fnsxt=5Fsnat)=20+=20=D0=B2=D0=BE=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D1=8B=20=D0=BD=D0=B0=20=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PLAN_IAC_MODIFY_RESOURCES_2026-09-24.md | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 NOTES/10_plans/PLAN_IAC_MODIFY_RESOURCES_2026-09-24.md diff --git a/NOTES/10_plans/PLAN_IAC_MODIFY_RESOURCES_2026-09-24.md b/NOTES/10_plans/PLAN_IAC_MODIFY_RESOURCES_2026-09-24.md new file mode 100644 index 0000000..49c9a56 --- /dev/null +++ b/NOTES/10_plans/PLAN_IAC_MODIFY_RESOURCES_2026-09-24.md @@ -0,0 +1,94 @@ +# ПЛАН: два ресурса-модификатора для цепочки Штурвала (2026-09-24) + +> Статус: **план, не реализовано**. Отправляется на ревью Opus. +> Решения приняты пользователем: 2 ресурса сейчас, универсальность потом; орга — не наша (адресация по uid); +> «один ресурс = весь массив `vIPConfigure`»; тип атрибута — String+JSON; apply — только пользователь. + +## 1. Цель + +Дать клиенту возможность собрать цепочку **одним `apply`**: + +``` +nubes_vc_org (вне state, адресация по uid) +nubes_vc_vdc → nubes_vc_nsxt +nubes_vc_org_ip_allocation (modify 662, vIPConfigure) ← новый ресурс +nubes_vc_nsxt_snat (modify 372, ipSpaceName) ← новый ресурс +nubes_k8s_shturval_cluster +``` + +Сейчас это невозможно: `Create` не отправляет modify-only параметры, а `Update` — второй прогон. + +## 2. Вне scope + +- Универсальный механизм (реестр модификаторов, генераторные метки) — потом. +- `vcExternalIp` — не разбирали. +- Правка генератора по modify-only (см. §7) — отдельный этап, требует решения. + +## 3. Ресурс 1 — `nubes_vc_org_ip_allocation` + +| | | +|---|---| +| Файл | `provider/internal/resources_core/org_ip_allocation_resource.go` (новый, hand-written) | +| Регистрация | `provider/internal/provider/provider.go`, `Resources()` (рядом с `NewServiceOperationResource`) | +| Атрибуты | `org_uid` — String, Required; `vip_configure` — String (JSON `[{"name":..,"count":..}]`), Required, нормализация JSON как в `resources_core/json_planmodifier.go`; `keep_on_destroy` — Bool, Optional, default `false` | +| ID | `org_uid` (один ресурс на оргу; массив целиком) | +| Create/Update | `modify` на инстансе орги: `vIPConfigure` = JSON-массив целиком (replace-семантика). Путь: `core.RunInstanceOperationUniversalByCode` (или обёртка `resources_core`), под `LockInstance(org_uid)` | +| Read | `core.GetInstanceStateParams(org_uid)` → ключ `vIPConfigure`; пустое/`[{}]`/`count=0` → нормализовать; родитель 404/deleted → `RemoveResource` (`resources_core.ShouldRemoveFromState`) | +| Delete | `keep_on_destroy=true` → no-op + Warning. Иначе: родитель жив → modify с `count=0` по каждому элементу (форма **проверена** тестом 09-22) + Warning; родитель мёртв → no-op + Warning | +| Import | passthrough по `org_uid` | + +## 4. Ресурс 2 — `nubes_vc_nsxt_snat` + +| | | +|---|---| +| Файл | `provider/internal/resources_core/nsxt_snat_resource.go` (новый) | +| Атрибуты | `nsxt_uid` — String, Required; `ip_space_name` — String, Required (`no-needed` = SNAT выключен, канон из HAR); `keep_on_destroy` — Bool, Optional, default `false` | +| ID | `nsxt_uid` | +| Create/Update | `modify` 372 = `ip_space_name`. Отправляется **только** 372 (остальные досыпаются из live — проверить, см. §8 вопрос 1) | +| Read | live `ipSpaceName` из `state.params`; отсутствует или `no-needed` → null; родитель мёртв → `RemoveResource` | +| Delete | inverse: `modify` с `ipSpaceName = "no-needed"` (канон, подтверждён HAR) | +| Import | passthrough по `nsxt_uid` | + +## 5. Зависимости и порядок + +``` +nubes_vc_nsxt → nubes_vc_org_ip_allocation → nubes_vc_nsxt_snat → k8s cluster +``` + +- SNAT обязан зависеть от org-IP: имя ipSpace берётся из аллокации (ребра графа TF не видит — связь по имени). +- Destroy пойдёт обратно: cluster → SNAT (`no-needed`) → org-IP (`count=0`) → nsxt → vdc. +- Инвариант: destroy модификаторов **не трогает** саму оргу. + +## 6. Этапы работ (последовательность) + +1. **Проверка по коду** (чтение): приоритет live→paramValue→default при дозаполнении параметров; `instance.go:478` (что именно эмитит Update). +2. `nubes_vc_org_ip_allocation` + регистрация + unit-тесты (нормализация JSON, чтение `[{}]`, Delete-ветки). +3. `nubes_vc_nsxt_snat` + регистрация + unit-тесты. +4. Общие хелперы в `resources_core` (если дублируются). +5. Живой прогон на dev (**apply — пользователь**): `FullPipe`, орга **saas** (`organization_type = "saas"`, иначе коллизия имени `WZ03709-iaas`). +6. Проверки после прогона: `plan` чистый (нет дрейфа), SNAT включён в одном apply, `destroy` не падает. +7. Документация: `HOW_TO/`/`docs/`, `VERSIONS.md`, коммиты по смыслу. + +## 7. Отдельный этап (требует решения): генератор + +Причина — инцидент: создание `nubes_vc_org` с `v_ip_configure` даёт `inconsistent result after apply` +(платформа после create отдаёт `vIPConfigure: [{}]`, read-back перекрывает план). + +Минимальные правки генератора (по Opus): +- **а)** modify-only параметр → **Optional+Computed** + `Deprecated` + не отправлять в `Update` (переход без breaking; удаление атрибута — только в следующем major); +- **б)** исключить modify-only поля из **create-read-back** (`InputField`). + +**Не реализуем в этом этапе** — ждём решения пользователя (правка генератора задевает все сервисы). + +## 8. Вопросы для ревью Opus + +1. Верно ли, что `RunInstanceOperationUniversalByCode` дозаполняет незаданные параметры из **live `state.params`** + (а не из дефолтов формы)? Если да — SNAT-ресурс может шлать только 372. Если нет — нужен явный pre-read+merge. +2. Delete для «весь массив»: слать `[{name, count:"0"}]` (проверено тестом) или `[]` (не проверено)? Что безопаснее + и не оставит ли `[]` элемент в state платформы? +3. Read-нормализация: считать ли `count="0"` и `[{}]` одним состоянием «пусто»? Не даст ли это ложный дрейф + при плановом уменьшении 3 → 0? +4. Переходный вариант (Deprecated + Optional+Computed, instance больше не шлёт параметр): не появится ли дрейф, + когда значение выставил модификатор, а instance-ресурс его только читает? +5. Достаточно ли `depends_on` (SNAT → org-IP) для корректного destroy, если org-IP-модификатор должен + уничтожиться **до** эджа? Нужны ли дополнительные рёбра?