Files
tf_provider/docs/FORENSIC_ANALYSIS_REPORT_2026-09-23.md
T
2026-09-23 19:23:31 +03:00

235 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` придётся получать не из дефолтного эндпоинта, а из контекста инстанса. Это вопрос рантайма провайдера, а не генератора.
### Итоговая оценка
Ошибок, которые ломали бы выводы отчёта, не выявлено. Отчёт можно использовать как подтверждённую основу для дальнейшего проектирования.