docs: план рефакторинга resource-generator (размоноличивание + SubParams)
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user