docs: план рефакторинга resource-generator (размоноличивание + SubParams)

This commit is contained in:
“Naeel”
2026-07-16 14:30:13 +04:00
parent af65106a05
commit c197fb8b0c
@@ -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)