Files
tf_provider/PLAN_modifier_redesign.md
T

203 lines
9.0 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.
# ПЛАН реализации: редизайн ресурсов-модификаторов (kind: modifier)
Основа: `HISTORY/OPUS/2026-09-22_modifier_architecture_project.md`.
Цель — закрыть все классы багов A–E, без костылей, по согласованной архитектуре.
Порядок шагов строгий: контракт → загрузка → шаблон → core → yaml → пересборка.
---
## Шаг 1. Контракт YAML в `TOOLS/lib/types.go`
Файл: `TOOLS/lib/types.go`, `OperationSpec`.
Добавить поля (тег yaml, omitempty):
```go
DeleteStrategy string `yaml:"delete_strategy,omitempty"` // "" → noop_warn
Idempotency string `yaml:"idempotency,omitempty"` // "" → none
DeleteParams []ParamSpec `yaml:"delete_params,omitempty"`
```
Enum `delete_strategy`: `noop_warn` | `inverse` | `error`.
Enum `idempotency`: `none` | `check_before_run`.
Проверка: `TOOLS/resource-generator` получает поля через алиас `OperationSpec = lib.OperationSpec` — отдельной правки не нужно, но `go build ./...` в lib и в resource-generator.
---
## Шаг 2. GenModifier — производные поля
Файл: `TOOLS/resource-generator/internal/types/types.go`, `GenModifier`.
Добавить:
```go
DeleteStrategy string // нормализованный enum (noop_warn|inverse|error)
Idempotency string // none|check_before_run
DeleteParams []Param // из spec.DeleteParams (ConvertParams), только при inverse
```
---
## Шаг 3. LoadSpecs — заполнение modifier + валидация
Файл: `TOOLS/resource-generator/internal/loader/loader.go`, ветка `op.Kind == "modifier"`.
До `continue`:
- `modifier.DeleteStrategy = normalizeDeleteStrategy(op.DeleteStrategy)` (пусто → `noop_warn`);
- `modifier.Idempotency = normalizeIdempotency(op.Idempotency)` (пусто → `none`);
- `modifier.DeleteParams = ConvertParams(op.DeleteParams)` (при inverse).
Файл: `loader.go`, `ValidateSpec` — расширить fail-fast для modifier:
- `delete_strategy` вне enum → ошибка;
- `delete_strategy == "inverse"` и пуст `delete_params` → ошибка;
- каждый `delete_params.code` обязан существовать в `op.Params` (сравнение по lower-code) → иначе ошибка;
- `idempotency` вне enum → ошибка.
---
## Шаг 4. Шаблон `modifier.go` — редизайн
Файл: `TOOLS/resource-generator/internal/templates/modifier.go`.
4.1. **Убрать `CompactParams`** — в Create/Update передавать map напрямую
(все заданные поля; решение о досылке — в core).
4.2. **Единый `reconcile()`** — вынести общее тело Create/Update в приватный метод
`reconcile(ctx, plan *Model)`, вызываемый из Create и Update. Устраняет дубль веток.
4.3. **ID = identity** — `plan.ID = BuildActionID(instanceUID, modifierName)`
(убрать operation из ID). Реализовать через существующий `BuildActionID(instanceUID, "", modifierName)`
или новый helper `BuildModifierID(instanceUID, modifierName)`.
4.4. **Delete по стратегии**:
```
{{- if eq .DeleteStrategy "error" }}
Delete → AddError (запрет destroy)
{{- else if eq .DeleteStrategy "inverse" }}
Delete → modify с DeleteParams + досылка live остальных (reconcile-вариант)
{{- else }}
Delete → RemoveResource + AddWarning («эффект остаётся на платформе»)
{{- end }}
```
4.5. **Pre-check idempotency** — в reconcile при `eq .Idempotency "check_before_run"`:
передавать флаг в вызов операции (см. шаг 6).
---
## Шаг 5. JSON-эквивалентность в нейтральный пакет (снять цикл импорта)
Проблема: `JSONStringsEquivalent` в `resources_core`, а comparison нужен в `core`.
- создать `provider/internal/core/jsonutil/jsonutil.go`:
перенести `JSONStringsEquivalent` + `normalizeJSONIfPossible` + `encodeCanonicalJSON` +
`writeCanonicalJSON` + `normalizeJSONScalarsToStrings` из `resources_core/json_normalize.go`;
- `resources_core/json_normalize.go` оставить как обёртку (реэкспорт `jsonutil.JSONStringsEquivalent`)
или заменить вызовы на `jsonutil.JSONStringsEquivalent`.
Проверка: `go build ./...`, нет цикла импорта.
---
## Шаг 6. core — pre-check `modifierDesiredEqualsCurrent`
Файл: `provider/internal/core/operation_run_bycode.go` (или новый `modifier_compare.go`).
Добавить экспортированный:
```go
func (c *UniversalClient) modifierDesiredEqualsCurrent(
desired map[string]string, cfsParams []universalCfsParam) bool
```
Логика:
- для каждого desired-кода → найти `universalCfsParam` → live `ParamValue`;
- нормализовать обе стороны `normalizeUniversalValueV6`;
- map-fixed/array-map-fixed → `jsonutil.JSONStringsEquivalent`;
- все поля совпали → true.
Опционально: добавить в `RunInstanceOperationUniversalByCode` параметр `idempotent bool`
(или новый метод-обёртка). В `operation_run_bycode.go` после `fetchOperationCfsParams`:
```
if idempotent && c.modifierDesiredEqualsCurrent(paramsByID, cfsParams) {
return nil // skip run
}
```
idle-гейт (`waitForInstanceIdle`) уже стоит выше — не трогать.
---
## Шаг 7. Передача флага `idempotent` вплоть до client
Цепочка: шаблон → `resources_core.RunOperationByCodeWithTimeout` → `core.RunInstanceOperationUniversalByCode`.
- добавить вариант `RunOperationByCodeIdempotent(...)` в `resources_core/crud.go`
(или расширить сигнатуру существующей, не ломая другие вызовы);
- пробросить флаг в `RunInstanceOperationUniversalByCode`.
---
## Шаг 8. YAML-разметка в `generated/dev/resources_yaml/`
`19_vc_org.yaml` (modify 207, modifier ip_space):
```yaml
- name: modify
id: 207
kind: modifier
modifier: ip_space
delete_strategy: error
idempotency: check_before_run
```
`22_vc_nsxt.yaml` (modify 111, modifier network):
```yaml
- name: modify
id: 111
kind: modifier
modifier: network
delete_strategy: inverse
delete_params:
- code: needEnableAVI
value: "false"
```
ВАЖНО: `generated/dev` — gitignored/генерируемый. Канон YAML — в yaml-generator
(источник из API), поэтому разметку `delete_strategy`/`idempotency` надо вносить в
**источник** (механизм исключений yaml-generator, а не в generated), иначе перегенерация
её сотрёт. Уточнить: где живёт источник правил модификаторов (registry исключений).
---
## Шаг 9. Тесты
`provider/internal/core/` — unit:
- `modifierDesiredEqualsCurrent`: совпадение/расхождение по bool/int/map-fixed/array-map-fixed;
- досылка: live→default→skip (integer>0 без дефолта не шлёт `"0"`).
`TOOLS/resource-generator` — unit:
- `ValidateSpec`: неизвестный enum, inverse без delete_params, delete_params.code вне params.
---
## Шаг 10. Сборка и заливка
- `go build ./...` + `go vet ./...` по всем 5 модулям;
- подать сборку `TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev`
(bump версии отдельным коммитом).
---
## Порядок коммитов (по смыслу)
1. `feat(lib): delete_strategy/idempotency/delete_params в OperationSpec`
2. `feat(gen): GenModifier расширение + LoadSpecs + ValidateSpec`
3. `refactor(gen): шаблон modifier — reconcile, без CompactParams, ID identity, Delete стратегия`
4. `refactor(core): вынести JSON-эквивалентность в jsonutil`
5. `feat(core): modifierDesiredEqualsCurrent + флаг idempotent`
6. `feat(yaml): разметка ip_space/network`
7. `test(core,gen): unit-кейсы`
8. `chore(dev): bump версии`
---
## Открытый вопрос (блокирует шаг 8)
**Где живёт источник-канон YAML модификаторов** (не `generated/dev`)? Разметку
`delete_strategy`/`idempotency` надо вносить в источник, а не в сгенерированный файл,
иначе перегенерация (`01_generate_yamls.sh`) сотрёт. Нужно найти механизм
(yaml-generator exceptions / registry), где держать эти флаги.