chore: save current changes

This commit is contained in:
Repinoid
2026-09-24 07:25:27 +03:00
parent a96e38fb1e
commit d93ff66482
7 changed files with 243 additions and 22 deletions
+5
View File
@@ -9,3 +9,8 @@
НИКОГДА НЕ ПРЕВЫШАЙ СВОИ ПОЛНОМОЧИЯ !!! НИКОГДА НЕ ПРЕВЫШАЙ СВОИ ПОЛНОМОЧИЯ !!!
ВСЕГДА СОБЛЮДАЙ БЕЗОПАСНОСТЬ И КОНФИДЕНЦИАЛЬНОСТЬ !!! ВСЕГДА СОБЛЮДАЙ БЕЗОПАСНОСТЬ И КОНФИДЕНЦИАЛЬНОСТЬ !!!
НИКОГДА НЕ ПЕРЕДАВАЙ СЕКРЕТЫ ИНТЕРНЕТУ БЕЗ РАЗРЕШЕНИЯ !!! НИКОГДА НЕ ПЕРЕДАВАЙ СЕКРЕТЫ ИНТЕРНЕТУ БЕЗ РАЗРЕШЕНИЯ !!!
НЕ ВЫЗЫВАТЬ ДРУГИЕ АГЕНТЫ БЕЗ РАЗРЕШЕНИЯ !!!
коммитить после каждой правки, чтобы зафиксировать текущее состояние и избежать потери изменений. Использовать осмысленные сообщения коммитов, отражающие суть изменений.
ВСЕГДА СОХРАНЯТЬ РЕЗЕРВНЫЕ КОПИИ ВАЖНЫХ ФАЙЛОВ ПЕРЕД ВНЕСЕНИЕМ ИЗМЕНЕНИЙ.
НИКОГДА НЕ ПОЛАГАЙСЯ НА ПАМЯТЬ — ВСЕГДА ПРОВЕРЯЙ АКТУАЛЬНОСТЬ ИНСТРУКЦИЙ.
ВСЕГДА СОБЛЮДАЙ ИНСТРУКЦИИ, ДАВАЙТЕ ПОДТВЕРЖДЕНИЯ И НЕ ДЕЛАЙТЕ САМОСТОЯТЕЛЬНЫХ ИЗМЕНЕНИЙ.
+5
View File
@@ -1,3 +1,8 @@
> ⛔ **LEGACY / НЕ ИСТОЧНИК ИСТИНЫ (помечено 2026-09-24).**
> Этот план относится к отменённому заходу (метки `kind: modifier` в YAML + реестр в `yaml-generator`).
> Актуальное состояние и выводы: `docs/CHAT_RESUME_IAC_2026-09-24.md`,
> `docs/SHTURVAL_IAC_MODIFY_ANALYSIS_2026-09-23.md`, `docs/OPUS_ANSWER_IAC_SHTURVAL_MODIFY_2026-09-23.md`.
# ПЛАН реализации: редизайн ресурсов-модификаторов (kind: modifier) # ПЛАН реализации: редизайн ресурсов-модификаторов (kind: modifier)
Основа: `HISTORY/OPUS/2026-09-22_modifier_architecture_project.md`. Основа: `HISTORY/OPUS/2026-09-22_modifier_architecture_project.md`.
@@ -1,3 +1,8 @@
> ⛔ **LEGACY / НЕ ИСТОЧНИК ИСТИНЫ (помечено 2026-09-24).**
> Идеология отменённого захода (метки `kind: modifier` в YAML, реестр модификаторов в `yaml-generator`,
> `delete_strategy`/`inverse` как механизм генератора). Сохранена как история.
> Актуальное: `docs/CHAT_RESUME_IAC_2026-09-24.md`.
# Архитектурная концепция: Modifier-ресурсы (Идеология, правила и интеграция в Terraform Provider) # Архитектурная концепция: Modifier-ресурсы (Идеология, правила и интеграция в Terraform Provider)
## 1. Введение и архитектурный контекст ## 1. Введение и архитектурный контекст
+199
View File
@@ -0,0 +1,199 @@
# 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).
@@ -5,6 +5,8 @@
**Связанный анализ:** `docs/SHTURVAL_IAC_MODIFY_ANALYSIS_2026-09-23.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. Суть ответа (главный вывод) ## 1. Суть ответа (главный вывод)
@@ -26,7 +28,7 @@
- **Update:** та же дельта-логика (target изменился → доначислить/освободить). - **Update:** та же дельта-логика (target изменился → доначислить/освободить).
- **Delete (inverse):** `modify` с обратным знаком до `count=0` по этому `name`. Требует адресного освобождения конкретных IP. Если освобождение — тоже накопительный modify без адресации, inverse корректно сделать нельзя. - **Delete (inverse):** `modify` с обратным знаком до `count=0` по этому `name`. Требует адресного освобождения конкретных IP. Если освобождение — тоже накопительный modify без адресации, inverse корректно сделать нельзя.
**Риск:** накопительный API без «прочитать текущее» и без «освободить конкретное» несовместим с декларативной моделью. Чинится на стороне платформы, не провайдера. **Риск (ОПРОВЕРГНУТ позже):** это утверждение строилось на ложной посылке «накопительный API». Факт: `vIPConfigure` — replace-состояние, дельта `target − current` по факту не нужна — достаточно слать целевой `count`, платформа сама выставляет его (идемпотентно). См. `ORG_IP_MODIFIER_TEST_2026-09-22.md`.
### Пункт 2 — `ipSpaceName` (цепочка providerVdc → providerGateway → ipSpace) ### Пункт 2 — `ipSpaceName` (цепочка providerVdc → providerGateway → ipSpace)
@@ -47,20 +49,21 @@
- **Форму ресурсов можно и нужно фиксировать сейчас** — она диктуется моделью Terraform, а не спеками. - **Форму ресурсов можно и нужно фиксировать сейчас** — она диктуется моделью Terraform, а не спеками.
- **Не блокер (делаем сейчас):** раздельные ресурсы + `depends_on`; целевое состояние вместо дельты; селектор шлюза; data-source для `ipSpaceName`; inverse через обратный modify. - **Не блокер (делаем сейчас):** раздельные ресурсы + `depends_on`; целевое состояние вместо дельты; селектор шлюза; data-source для `ipSpaceName`; inverse через обратный modify.
- **Блокер (нужно от платформы):** - **Блокер (нужно от платформы) — только один подтверждённый:**
- a) чтение текущего числа vIP по `name` (иначе нет Read/идемпотентности);
- b) адресное освобождение IP (иначе нет корректного Delete);
- c) API чтения цепочки providerVdc → providerGateway → ipSpace (иначе data-source невозможен). - c) API чтения цепочки providerVdc → providerGateway → ipSpace (иначе data-source невозможен).
- **Вывод:** проектируем форму сейчас, помечаем a/b/c как зависимости от платформы. Новые спеки повлияют на **резолвинг значений**, не на форму ресурсов — если форма построена на «целевое состояние + селектор + data-source». - **Сняты как ложные (опровергнуты тестом 2026-09-22):**
- a) чтение текущего числа vIP — УЖЕ работает через `state.params.vIPConfigure`;
- b) адресное освобождение IP — УЖЕ работает: `count` меньше/`0` задаётся тем же `modify`, в обе стороны.
- **Вывод:** проектируем форму сейчас, блокер только (c). Новые спеки повлияют на **резолвинг значений**, не на форму ресурсов — если форма построена на «целевое состояние + селектор + data-source».
### Пункт 5 — минимально-инвазивный порядок внедрения ### Пункт 5 — минимально-инвазивный порядок внедрения
**От платформы (до кодинга ресурсов) — обязательно:** **От платформы (до кодинга ресурсов) — обязательно:**
- Read-эндпоинт: текущее количество vIP по `name` (для идемпотентности/Read).
- Освобождение конкретных vIP (для Delete/inverse).
- Read цепочки → `ipSpaceName` (для data-source). - Read цепочки → `ipSpaceName` (для data-source).
- Подтверждение, что генератор умеет строить схему из объединения `create`+`modify` полей (иначе `vIPConfigure`/`ipSpaceName` вообще не попадут в схему). - Подтверждение, что генератор умеет строить схему из объединения `create`+`modify` полей (иначе `vIPConfigure`/`ipSpaceName` вообще не попадут в схему).
> ⚠️ Read счётчика vIP и адресное освобождение — НЕ блокеры (уже подтверждено тестом). Исключены.
**На стороне провайдера — можно сейчас, не дожидаясь:** **На стороне провайдера — можно сейчас, не дожидаясь:**
- Раздельные ресурсы vip-allocation / nsxt-snat с `depends_on`. - Раздельные ресурсы vip-allocation / nsxt-snat с `depends_on`.
- Логика «target − current = дельта» (заглушка current, пока нет Read). - Логика «target − current = дельта» (заглушка current, пока нет Read).
@@ -68,7 +71,7 @@
- Optional+computed селектор шлюза. - Optional+computed селектор шлюза.
- Inverse-контракт (Delete = обратный modify до нуля). - Inverse-контракт (Delete = обратный modify до нуля).
**Главный неустранимый на нашей стороне блокер:** накопительный API без чтения текущего состояния и без адресного освобождения. Без этого идемпотентность и Delete принципиально недостижимы. Правится в спеках/API платформы, а не в провайдере. **Главный неустранимый на нашей стороне блокер:** ❌ СНЯТ — строился на ложной посылке «накопительный API». Реальный остаточный блокер — только (c) чтение цепочки providerVdc → providerGateway → ipSpace (для data-source `ipSpaceName`).
--- ---
@@ -87,29 +90,33 @@
2. **`ipSpaceName` — data-source, не аргумент ресурса** — сдвиг от «юзер вписывает значение» к «computed-from-parent». Раньше виделось как ввод. 2. **`ipSpaceName` — data-source, не аргумент ресурса** — сдвиг от «юзер вписывает значение» к «computed-from-parent». Раньше виделось как ввод.
3. **Селектор шлюза (`t0_id`/`provider_gateway`) как optional+computed сейчас** — в старых планах про «один T0» вообще не было (пришло только из реплики Виталия). 3. **Селектор шлюза (`t0_id`/`provider_gateway`) как optional+computed сейчас** — в старых планах про «один T0» вообще не было (пришло только из реплики Виталия).
4. **Чёткая граница «данные vs логика»** — в реестре только «тип поля + что computed-from-parent», цепочка обхода — логика data-source. 4. **Чёткая граница «данные vs логика»** — в реестре только «тип поля + что computed-from-parent», цепочка обхода — логика data-source.
5. **Три конкретных блокера от платформы** как API-требования (Read счётчика, адресное освобождение, Read цепочки) — раньше это было «unknown, проверить», теперь жёсткий список. 5. **Блокеры от платформы** — из трёх заявленных Опуса два (Read счётчика, адресное освобождение) **ложны** (опровергнуты тестом), остаётся один реальный: Read цепочки providerVdc→providerGateway→ipSpace.
### Главное отличие одной фразой ### Главное отличие одной фразой
Старые планы отвечали на «**как сделать модификатор в tf**». Опус отвечает на «**как сделать его идемпотентным и IaC-честным при накопительном API и скрытых зависимостях**» — и выявил, что ядро проблемы не в провайдере, а в платформе (Read счётчика + адресное освобождение + Read цепочки). Старые планы отвечали на «**как сделать модификатор в tf**». Опус отвечает на «**как сделать его идемпотентным и IaC-честным**» — но его центральный вывод «ядро проблемы в платформе (накопительный API)» **оказался ошибочным**, т.к. исходная посылка «накопительный» неверна (см. поправку в шапке). Реальный остаток — только `ipSpaceName` (цепочка providerVdc→providerGateway→ipSpace) и селектор шлюза.
--- ---
## 4. Спорный/непроверенный момент (требует живой проверки) ## 4. Спорный/непроверенный момент — РАЗРЕШЁН
Опус утверждает: накопительный `vIPConfigure` без Read-счётчика и без адресного освобождения «несовместим с декларативной моделью». Посылка Опуса «накопительный `vIPConfigure` без Read-счётчика и адресного освобождения несовместим с декларативной моделью» **опровергнута** тестом `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md`:
Проверено частично: из HAR (`org2.har`, `org_enough_.har`) видно, что live `vIPConfigure` с `count` читается (это уже потенциально «Read текущего числа»). Значит блокер (a) — чтение счётчика — возможно, уже закрыт. - повторный `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}]`.
НЕ проверено: умеет ли API **адресно освободить** конкретный `name`/`count` (откат), или освобождение тоже накопительное. Это блокер (b), и его надо проверить живым API, прежде чем принимать вывод Опуса за окончательный. Единственный реально непроверенный момент: полное удаление ipSpace (`[]` / отсутствие элемента) — тест этого не покрывал. Для IaC-задачи «обнулить» достаточно, полное удаление — опционально.
--- ---
## 5. Резюме ## 5. Резюме (с поправкой)
- Форма ресурсов — проектируем сейчас, она не зависит от спеков. - Форма ресурсов — проектируем сейчас, она не зависит от спеков.
- Три блокера платформы: Read счётчика vIP, адресное освобождение, Read цепочки ipSpace. - `vIPConfigure` — **НЕ блокер**: идемпотентно, обе стороны, `count` читается/задаётся из `state.params` (тест 2026-09-22).
- Из трёх блокеров (a) частично подтверждён HAR-дампами; (b) и (c) — непроверены. - Единственный подтверждённый блокер: **Read цепочки `providerVdc → providerGateway → ipSpace`** для data-source `ipSpaceName` (c).
- Непроверено: полное удаление ipSpace (`[]`), но для задачи достаточно `count=0`.
- Конкретный план внедрения пока НЕ составляется (по решению пользователя). - Конкретный план внедрения пока НЕ составляется (по решению пользователя).
**Следующий возможный шаг (только по запросу):** проверить живым API блокеры (b) адресное освобождение и (c) чтение цепочки, после чего фиксировать, что реально блокирует, а что уже закрыто. **Следующий возможный шаг (только по запросу):** проверить (c) чтение цепочки ipSpace живым API.
@@ -45,7 +45,7 @@ k8sShturval -> create
### 2.2. Эти параметры — не «настройки», а отложенные действия ### 2.2. Эти параметры — не «настройки», а отложенные действия
- `vIPConfigure=[{name,count}]` — транзакция «выделить N внешних IP» на ресурсной платформе. Повторный вызов **накапливает** (аккумулирует квоту), а не задаёт состояние. Это не декларативное значение. - `vIPConfigure=[{name,count}]` — задаёт желаемое число внешних IP целиком. **Не накопительная** (повторный вызов с тем же `count` не аккумулирует, подтверждено `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md`), работает в обе стороны (вверх/вниз/до `count=0`). Это **декларативное значение** в смысле «желаемое количество IP по данному ipSpace».
- `ipSpaceName` — включение SNAT на конкретный ipSpace, который возникает **только после** того, как на орге выделены IP. - `ipSpaceName` — включение SNAT на конкретный ipSpace, который возникает **только после** того, как на орге выделены IP.
- Эти операции требуют порядка (org.modify → затем nsxt.modify) и зависят от живого состояния инстанса, а не от дефолтов формы. - Эти операции требуют порядка (org.modify → затем nsxt.modify) и зависят от живого состояния инстанса, а не от дефолтов формы.
@@ -166,4 +166,4 @@ providerVdc → никто не знает изначально
**Рекомендация:** не городить модификатор сейчас по устаревшим спекам. Дождаться изменений спеков (сначала sandbox), параллельно — обсчитать два blockers: (1) как провайдер будет резолвить `providerVdc → providerGateway → ipSpace` без ручного ввода; (2) допущение «один T0». После этого проектировать форму ресурсов. **Рекомендация:** не городить модификатор сейчас по устаревшим спекам. Дождаться изменений спеков (сначала sandbox), параллельно — обсчитать два blockers: (1) как провайдер будет резолвить `providerVdc → providerGateway → ipSpace` без ручного ввода; (2) допущение «один T0». После этого проектировать форму ресурсов.
**Что точно не делать:** вписывать параметры «насильно» в tfstate; дёргать `modify` через скрипт на каждый apply без live-сверки (накопит `vIPConfigure`); ждать, что «прописал поле в tf → apply» заработает без правки провайдера. **Что точно не делать:** вписывать параметры «насильно» в tfstate; ждать, что «прописал поле в tf → apply» заработает без правки провайдера. (`vIPConfigure` при этом НЕ накопительный — см. §2.2, тест 2026-09-22.)
@@ -27,7 +27,7 @@ k8sShturval -> create
- Провайдер генерируется из YAML-спеков. Схема tf-ресурса строится ТОЛЬКО из операции `create`. - Провайдер генерируется из YAML-спеков. Схема tf-ресурса строится ТОЛЬКО из операции `create`.
- `vIPConfigure` (array-map-fixed, sub: name/count) есть только в `modify` vc_org (id 207); в `create` (136) его нет. - `vIPConfigure` (array-map-fixed, sub: name/count) есть только в `modify` vc_org (id 207); в `create` (136) его нет.
- `ipSpaceName` (string) есть только в `modify` vc_nsxt (id 111); в `create` (10) его нет. - `ipSpaceName` (string) есть только в `modify` vc_nsxt (id 111); в `create` (10) его нет.
- `vIPConfigure` — накопительный: повторный `modify` выделяет IP заново, а не задаёт состояние. - `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`, которую пользователь не знает. Сейчас платформа «подкладывает» недостающие параметры при создании пустой орги. - `ipSpaceName` выводится из цепочки `providerVdc -> providerGateway -> ipSpace`, которую пользователь не знает. Сейчас платформа «подкладывает» недостающие параметры при создании пустой орги.
- Допущение платформы: в организации один T0/провайдер-шлюз. Рост числа T0 отложен. - Допущение платформы: в организации один T0/провайдер-шлюз. Рост числа T0 отложен.
- Платформа в движении: форма ресурсов зависит от новых спеков (ждут, придут сначала в sandbox). - Платформа в движении: форма ресурсов зависит от новых спеков (ждут, придут сначала в sandbox).
@@ -38,7 +38,7 @@ k8sShturval -> create
## Вопросы ## Вопросы
1. Как корректно выразить накопительный `vIPConfigure` идемпотентным tf-ресурсом (чтобы повторный apply не выделял IP заново)? Как устроить Read и Delete (inverse: обнулить count), если отдельного API-объекта нет? 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? 2. Как провайдер должен получать выводимое значение `ipSpaceName` (цепочка providerVdc → providerGateway → ipSpace): data-source, вычисляемое из state родителя, или иное? Где граница «данные vs логика», что хранить в реестре, что выводить из типа/state?