chore: save current changes
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
# 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.
|
||||
|
||||
На текущем этапе никаких решений по этим компонентам принимать нельзя.
|
||||
@@ -0,0 +1,234 @@
|
||||
# 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` придётся получать не из дефолтного эндпоинта, а из контекста инстанса. Это вопрос рантайма провайдера, а не генератора.
|
||||
|
||||
### Итоговая оценка
|
||||
|
||||
Ошибок, которые ломали бы выводы отчёта, не выявлено. Отчёт можно использовать как подтверждённую основу для дальнейшего проектирования.
|
||||
@@ -0,0 +1,115 @@
|
||||
# Ответ Opus: анализ решения IaC-развёртывания Штурвала (модификаторы + скрытые зависимости)
|
||||
|
||||
**Дата:** 2026-09-23
|
||||
**Связанный промпт:** `docs/prompts/prompt_for_opus_iac_shturval_modify.md`
|
||||
**Связанный анализ:** `docs/SHTURVAL_IAC_MODIFY_ANALYSIS_2026-09-23.md`
|
||||
**Статус:** документирование ответа. Конкретный план НЕ составляется.
|
||||
|
||||
---
|
||||
|
||||
## 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 без «прочитать текущее» и без «освободить конкретное» несовместим с декларативной моделью. Чинится на стороне платформы, не провайдера.
|
||||
|
||||
### Пункт 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.
|
||||
- **Блокер (нужно от платформы):**
|
||||
- a) чтение текущего числа vIP по `name` (иначе нет Read/идемпотентности);
|
||||
- b) адресное освобождение IP (иначе нет корректного Delete);
|
||||
- c) API чтения цепочки providerVdc → providerGateway → ipSpace (иначе data-source невозможен).
|
||||
- **Вывод:** проектируем форму сейчас, помечаем a/b/c как зависимости от платформы. Новые спеки повлияют на **резолвинг значений**, не на форму ресурсов — если форма построена на «целевое состояние + селектор + data-source».
|
||||
|
||||
### Пункт 5 — минимально-инвазивный порядок внедрения
|
||||
|
||||
**От платформы (до кодинга ресурсов) — обязательно:**
|
||||
- Read-эндпоинт: текущее количество vIP по `name` (для идемпотентности/Read).
|
||||
- Освобождение конкретных vIP (для Delete/inverse).
|
||||
- Read цепочки → `ipSpaceName` (для data-source).
|
||||
- Подтверждение, что генератор умеет строить схему из объединения `create`+`modify` полей (иначе `vIPConfigure`/`ipSpaceName` вообще не попадут в схему).
|
||||
|
||||
**На стороне провайдера — можно сейчас, не дожидаясь:**
|
||||
- Раздельные ресурсы vip-allocation / nsxt-snat с `depends_on`.
|
||||
- Логика «target − current = дельта» (заглушка current, пока нет Read).
|
||||
- Data-source-скелет для `ipSpaceName` (с TODO на реальный обход цепочки).
|
||||
- Optional+computed селектор шлюза.
|
||||
- Inverse-контракт (Delete = обратный modify до нуля).
|
||||
|
||||
**Главный неустранимый на нашей стороне блокер:** накопительный API без чтения текущего состояния и без адресного освобождения. Без этого идемпотентность и Delete принципиально недостижимы. Правится в спеках/API платформы, а не в провайдере.
|
||||
|
||||
---
|
||||
|
||||
## 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. **Три конкретных блокера от платформы** как API-требования (Read счётчика, адресное освобождение, Read цепочки) — раньше это было «unknown, проверить», теперь жёсткий список.
|
||||
|
||||
### Главное отличие одной фразой
|
||||
|
||||
Старые планы отвечали на «**как сделать модификатор в tf**». Опус отвечает на «**как сделать его идемпотентным и IaC-честным при накопительном API и скрытых зависимостях**» — и выявил, что ядро проблемы не в провайдере, а в платформе (Read счётчика + адресное освобождение + Read цепочки).
|
||||
|
||||
---
|
||||
|
||||
## 4. Спорный/непроверенный момент (требует живой проверки)
|
||||
|
||||
Опус утверждает: накопительный `vIPConfigure` без Read-счётчика и без адресного освобождения «несовместим с декларативной моделью».
|
||||
|
||||
Проверено частично: из HAR (`org2.har`, `org_enough_.har`) видно, что live `vIPConfigure` с `count` читается (это уже потенциально «Read текущего числа»). Значит блокер (a) — чтение счётчика — возможно, уже закрыт.
|
||||
|
||||
НЕ проверено: умеет ли API **адресно освободить** конкретный `name`/`count` (откат), или освобождение тоже накопительное. Это блокер (b), и его надо проверить живым API, прежде чем принимать вывод Опуса за окончательный.
|
||||
|
||||
---
|
||||
|
||||
## 5. Резюме
|
||||
|
||||
- Форма ресурсов — проектируем сейчас, она не зависит от спеков.
|
||||
- Три блокера платформы: Read счётчика vIP, адресное освобождение, Read цепочки ipSpace.
|
||||
- Из трёх блокеров (a) частично подтверждён HAR-дампами; (b) и (c) — непроверены.
|
||||
- Конкретный план внедрения пока НЕ составляется (по решению пользователя).
|
||||
|
||||
**Следующий возможный шаг (только по запросу):** проверить живым API блокеры (b) адресное освобождение и (c) чтение цепочки, после чего фиксировать, что реально блокирует, а что уже закрыто.
|
||||
@@ -0,0 +1,169 @@
|
||||
# Штурвал через 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}]` — транзакция «выделить N внешних IP» на ресурсной платформе. Повторный вызов **накапливает** (аккумулирует квоту), а не задаёт состояние. Это не декларативное значение.
|
||||
- `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; дёргать `modify` через скрипт на каждый apply без live-сверки (накопит `vIPConfigure`); ждать, что «прописал поле в tf → apply» заработает без правки провайдера.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Промпт для 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` — накопительный: повторный `modify` выделяет IP заново, а не задаёт состояние.
|
||||
- `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` идемпотентным tf-ресурсом (чтобы повторный apply не выделял IP заново)? Как устроить Read и Delete (inverse: обнулить count), если отдельного API-объекта нет?
|
||||
|
||||
2. Как провайдер должен получать выводимое значение `ipSpaceName` (цепочка providerVdc → providerGateway → ipSpace): data-source, вычисляемое из state родителя, или иное? Где граница «данные vs логика», что хранить в реестре, что выводить из типа/state?
|
||||
|
||||
3. Как спроектировать форму ресурсов, чтобы не завязываться на допущение «в организации один T0», и что сломается/что менять, если T0 станет больше одного?
|
||||
|
||||
4. Стоит ли ждать новых спеков платформы перед проектированием ресурсов, или форму ресурсов можно зафиксировать уже сейчас так, чтобы она пережила изменение спеков? Что в спеках — блокер, что — нет?
|
||||
|
||||
5. Минимально-инвазивный порядок внедрения: что должно прийти от платформы (spec/API) до того, как мы начинаем кодить, а что можем сделать на стороне провайдера уже сейчас?
|
||||
|
||||
Отвечай по номерам, кратко.
|
||||
Reference in New Issue
Block a user