From c197fb8b0cc668719ac4e93f51af6efb20e28ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 16 Jul 2026 14:30:13 +0400 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=BF=D0=BB=D0=B0=D0=BD=20=D1=80=D0=B5?= =?UTF-8?q?=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=D0=B0=20?= =?UTF-8?q?resource-generator=20(=D1=80=D0=B0=D0=B7=D0=BC=D0=BE=D0=BD?= =?UTF-8?q?=D0=BE=D0=BB=D0=B8=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?+=20SubParams)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/TODO/resource_generator_refactor_plan.md | 225 ++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 docs/TODO/resource_generator_refactor_plan.md diff --git a/docs/TODO/resource_generator_refactor_plan.md b/docs/TODO/resource_generator_refactor_plan.md new file mode 100644 index 0000000..d81ec0a --- /dev/null +++ b/docs/TODO/resource_generator_refactor_plan.md @@ -0,0 +1,225 @@ +# План рефакторинга resource-generator + +Дата: 2026-07-16 | Ветка: svc-api | Версия: 5.0.70 + +## Контекст + +`TOOLS/resource-generator` генерирует Go-код Terraform-ресурсов из YAML-спеков. +Две задачи: +1. Размонолитить `templates/templates.go` (1224 строки) +2. Внедрить поддержку `SubParams` → вложенные Terraform-блоки для `map-fixed` + +--- + +## Блок 1. Размоноличивание templates.go + +### Вопрос 1.1 — Как разбивать? + +**Ответ: Вариант A** — physical split без изменения логики. + +Файл содержит 3 крупные строковые константы: +- `Instance` (~730 строк) — основной CRUD +- `Subresource` (~330 строк) — подресурсы +- `Action` (~160 строк) — действия + +Разбиваем на 3 файла в том же пакете `templates`: +- `instance.go` — константа `Instance` +- `subresource.go` — константа `Subresource` +- `action.go` — константа `Action` + +Вариант B (embed `.tmpl`) — плюс в читаемости, но ломает обратную совместимость +с writers.go который ссылается на `templates.Instance` как на Go-переменную. +Неоправданный риск на этом этапе. + +Вариант C (sub-templates) — избыточен, шаблоны и так линейные. + +### Вопрос 1.2 — Куда класть .tmpl при embed? + +Не применяется (выбран вариант A). + +### Вопрос 1.3 — Можно ли менять публичные имена? + +**Не менять.** `templates.Instance`, `templates.Subresource`, `templates.Action` +используются в `writers/writers.go` как `template.Must(template.New(...).Funcs(...).Parse(templates.Instance))`. +Имена констант сохраняем, разносим только файлы. + +### Вопрос 1.4 — Требуется ли byte-identical вывод? + +**Не требуется.** Сгенерированный код меняется между запусками (разные сервисы, +разные параметры). Главное — семантическая корректность и компилируемость. + +--- + +## Блок 2. SubParams → вложенные Terraform-блоки + +### Вопрос 2.1 — Добавить SubParams в types.Param? + +**Да.** Нужно: + +1. В `types.Param` добавить поле `SubParams []Param` +2. В `ConvertParams` (loader.go:223) рекурсивно конвертировать `p.SubParams`: + ```go + subParams := make([]types.Param, 0, len(p.SubParams)) + for _, sp := range p.SubParams { + subParams = append(subParams, ConvertParams([]types.ParamSpec{sp})[0]) + } + out = append(out, types.Param{..., SubParams: subParams}) + ``` +3. В шаблонах `Instance` — для каждого Param с непустым SubParams генерировать `SingleNestedAttribute`/`ListNestedAttribute` + +### Вопрос 2.2 — Как определять map-fixed? + +`DataType` в YAML = `"map-fixed"` или `"array-map-fixed"`. +`NormalizeParamType` сейчас возвращает `"string"` для них (нет match). +Нужно добавить: +```go +if v == "map-fixed" || v == "array-map-fixed" { + return "map-fixed" // новый тип, не "string" +} +``` +Определение nested — по `len(p.SubParams) > 0`. + +### Вопрос 2.3 — Какой Terraform-конструкт? + +**map-fixed** (один объект) → `schema.SingleNestedAttribute`: +```go +"clusterConfiguration": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "cpu": schema.Int64Attribute{Required: true, ...}, + "memory": schema.Int64Attribute{Required: true, Default: int64default.StaticInt64(512)}, + "replicas": schema.Int64Attribute{Required: true, Default: int64default.StaticInt64(1)}, + "disk": schema.Int64Attribute{Required: true, Default: int64default.StaticInt64(10)}, + }, +}, +``` + +**array-map-fixed** (список объектов) → `schema.ListNestedAttribute`: +```go +"postgresConf": schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "paramName": schema.StringAttribute{Required: true}, + "paramValue": schema.StringAttribute{Required: true}, + }, + }, +}, +``` + +### Вопрос 2.4 — Go-тип для Model? + +**Вариант B** — генерировать именованный struct. + +Для `clusterConfiguration` → `PostgresClusterConfigurationModel`: +```go +type PostgresClusterConfigurationModel struct { + Cpu types.Int64 `tfsdk:"cpu"` + Memory types.Int64 `tfsdk:"memory"` + Replicas types.Int64 `tfsdk:"replicas"` + Disk types.Int64 `tfsdk:"disk"` +} +``` + +В основной Model: +```go +ClusterConfiguration *PostgresClusterConfigurationModel `tfsdk:"cluster_configuration"` +``` + +Причина: типобезопасность, проще в Create/Read/Update (прямой доступ к полям +вместо возни с `types.Object` и `AttributeTypes`). + +Для списков (`array-map-fixed`): +```go +PostgresConf []PostgresPostgresConfModel `tfsdk:"postgres_conf"` +``` + +Имя вложенного типа: `{ResourceCamel}{ParamCamel}Model` — например +`PostgresClusterConfigurationModel`, `PostgresBackupConfigurationModel`. + +### Вопрос 2.5 — Как передавать map-fixed в API? + +**Вариант А** — сериализовать в JSON-строку и класть по ID родительского параметра. + +API Nubes принимает параметры как плоский `map[int]string`: +- `clusterConfiguration` (id=788) → JSON `{"cpu":1000,"memory":2048,"replicas":2,"disk":20}` +- `postgresConfiguration` (id=791) → JSON `{"version":"16","sslRequired":true,...}` + +В `resources_core` уже есть `FormatString` для JSON-параметров. Для map-fixed +нужен новый хелпер `FormatNested`: +```go +func FormatNested(v interface{}) (string, error) { + b, err := json.Marshal(v) + return string(b), err +} +``` + +Или переиспользовать существующий `FormatString` если Model реализует +`json.Marshaler` или передаётся как `types.Object`. + +**Важно**: каждый ID подпараметра (`svcOperationCfsSubparamId`) в API не используется +отдельно — они нужны только внутри dataDescriptor. API принимает родительский +`svcOperationCfsParamId` с JSON-строкой. + +### Вопрос 2.6 — Есть ли у SubParams свои ID? + +**Да.** Каждый sub-param имеет: +- `id` — свой `svcOperationCfsSubparamId` (используется внутри dataDescriptor, + но НЕ в API-вызовах как отдельный параметр) +- `code` — имя подполя +- `data_type` — `string`, `integer > 0`, `boolean`, `json`, `uuid` +- `required` — обязательность +- `default` — значение по умолчанию +- `value_list` — допустимые значения (для enum-полей) +- НЕТ `ref_svc_id` (ссылки на другие сервисы — только на верхнем уровне) + +### Вопрос 2.7 — Для каких видов ресурсов SubParams? + +На этом этапе — **только Instance** (основной CRUD). + +Subresource и Action используют плоские параметры (create_user: username+role, +create_database: dbName+dbOwner). map-fixed у них не встречается. + +### Вопрос 2.8 — Готовый YAML с sub_params? + +**Да.** `generated/dev/resources_yaml/90_postgres.yaml`: +- `clusterConfiguration` (map-fixed, 4 sub_params) +- `startupConfiguration` (map-fixed, 1 sub_param) +- `accessConfiguration` (map-fixed, 4 sub_params) +- `postgresConfiguration` (map-fixed, 4 sub_params) +- `postgresConf` (array-map-fixed, 2 sub_params) +- `backupConfiguration` (map-fixed, 3 sub_params) +- `autoscaleConfiguration` (map-fixed, 4 sub_params) +- `mtlsConfiguration` (map-fixed, 5 sub_params) — только dev + +### Вопрос 2.9 — Output для вложенных атрибутов? + +**Write-only.** State-поля `state_params`/`state_out` возвращаются API как плоский +map — обратная конвертация из JSON в nested struct не требуется. +Вложенные атрибуты участвуют только в Create/Modify (запись), не в Read (чтение). + +--- + +## Блок 3. Порядок работ + +### Вопрос 3.1 — Размоноличивание и SubParams — вместе или отдельно? + +**Отдельно.** Два независимых этапа: + +**Этап 1: Размоноличивание** (чисто механическое, ~10 минут) +1. Создать `templates/instance.go` — вырезать константу `Instance` +2. Создать `templates/subresource.go` — вырезать константу `Subresource` +3. Создать `templates/action.go` — вырезать константу `Action` +4. `templates/templates.go` — оставить только package + imports (если общие) +5. Проверить `go build` + сгенерировать тестовый ресурс — вывод должен быть идентичен + +**Этап 2: SubParams** (основная работа, ~2-4 часа) +1. `types.Param` — добавить `SubParams []Param` +2. `loader.ConvertParams` — рекурсивная конвертация +3. `loader.NormalizeParamType` — добавить `"map-fixed"`, `"array-map-fixed"` +4. `helpers.ParamType` — возвращать специальный тип для nested +5. Шаблон `Instance` — для каждого Param с SubParams: + - В Model: генерировать вложенный struct + поле указатель/слайс + - В Schema: `SingleNestedAttribute` / `ListNestedAttribute` + - В Create/Modify: сериализация в JSON по родительскому ID +6. Протестировать на postgres (самый сложный — 8 map-fixed + 1 array-map-fixed)