From 92cb40e476cce4350698875778906e2925c7a6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 18 Jul 2026 22:51:43 +0400 Subject: [PATCH] fix: nil-guard for nested map-fixed params in generated code --- TEST_STAND/POSTGRES/main.tf | 2 +- TOOLS/ARCHITECTURE.md | 18 ++++++++++ .../internal/templates/instance.go | 33 ++++++++++++++----- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/TEST_STAND/POSTGRES/main.tf b/TEST_STAND/POSTGRES/main.tf index e72bf42..58573f1 100644 --- a/TEST_STAND/POSTGRES/main.tf +++ b/TEST_STAND/POSTGRES/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { nubes = { source = "registry.kube5s.ru/nubes-test/nubes" - version = "5.1.3" + version = "5.1.4" } } } diff --git a/TOOLS/ARCHITECTURE.md b/TOOLS/ARCHITECTURE.md index 33b8964..3622484 100644 --- a/TOOLS/ARCHITECTURE.md +++ b/TOOLS/ARCHITECTURE.md @@ -116,6 +116,24 @@ Each operation has a kind: - GET operations (GetInstanceState, GetInstanceStateRaw) retry 401 with 2s/4s/8s backoff. - `doRequest` treats 401 as retryable for GET requests (alongside 429, 502, 503, 504). +### Generated Code Resilience + +- **Zero-value fallback** (`normalizeUniversalValueV6`): если параметр отсутствует + в пользовательском `.tf`, подставлять zero-value по `dataType`: + - `integer` → `"0"`, `boolean` → `"false"`, `map-fixed` → `"{}"`, `array` → `"[]"`, `string` → `""` + - Это предотвращает NullPointerException на стороне API при добавлении новых полей. +- **modify всегда через WithDefaults** (`RunInstanceOperationUniversalWithDefaults`): + modify-операции запрашивают `cfsParams` у API и отправляют все параметры, + включая новые, с дефолтами из API. +- **Nil-guard для nested-параметров** (шаблон `instance.go`): `map-fixed`-параметры + (указатели на вложенные структуры) проверяются на nil перед доступом к sub-полям. + Если состояние создано до добавления нового `map-fixed`-параметра — он будет nil, + и код не должен падать с nil pointer dereference. Вместо этого параметр пропускается, + и zero-value fallback подставит `{}`. +- **Merge SubParams union** (`params.go`): при совпадении `Code` параметра в разных + операциях (create/modify) с разными наборами sub-params, `Merge()` объединяет их + union'ом, а не отбрасывает второй. Это гарантирует что структура содержит все поля. + ### Subresource Resources Subresource operations are exposed as standard resources. diff --git a/TOOLS/resource-generator/internal/templates/instance.go b/TOOLS/resource-generator/internal/templates/instance.go index d66d30a..0ff26e1 100644 --- a/TOOLS/resource-generator/internal/templates/instance.go +++ b/TOOLS/resource-generator/internal/templates/instance.go @@ -235,13 +235,18 @@ func (r *{{ToCamel .Name}}Resource) ModifyPlan(ctx context.Context, req resource {{- end }} params := map[int]string{ {{- range .CreateParams }} - {{- if (IsNested .) }} - {{.ID}}: {{NestedJSONExpr . "config"}}, - {{- else }} + {{- if not (IsNested .) }} {{.ID}}: {{ParamFormat . (printf "config.%s" (ToCamel .Code))}}, {{- end }} {{- end }} } + {{- range .CreateParams }} + {{- if (IsNested .) }} + if config.{{ToCamel .Code}} != nil { + params[{{.ID}}] = {{NestedJSONExpr . "config"}} + } + {{- end }} + {{- end }} desiredDomain := "" {{- if .HasDomainParam }} if !config.Domain.IsNull() && !config.Domain.IsUnknown() { @@ -325,13 +330,18 @@ func (r *{{ToCamel .Name}}Resource) Create(ctx context.Context, req resource.Cre params := map[int]string{ {{- range .CreateParams }} - {{- if (IsNested .) }} - {{.ID}}: {{NestedJSONExpr . "data"}}, - {{- else }} + {{- if not (IsNested .) }} {{.ID}}: {{ParamFormat . (printf "data.%s" (ToCamel .Code))}}, {{- end }} {{- end }} } + {{- range .CreateParams }} + {{- if (IsNested .) }} + if data.{{ToCamel .Code}} != nil { + params[{{.ID}}] = {{NestedJSONExpr . "data"}} + } + {{- end }} + {{- end }} operationTimeout := "" if !data.OperationTimeout.IsNull() && !data.OperationTimeout.IsUnknown() { @@ -547,13 +557,18 @@ func (r *{{ToCamel .Name}}Resource) Update(ctx context.Context, req resource.Upd {{- end }} params := map[int]string{ {{- range .ModifyParams }} - {{- if (IsNested .) }} - {{.ID}}: {{NestedJSONExpr . "plan"}}, - {{- else }} + {{- if not (IsNested .) }} {{.ID}}: {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}}, {{- end }} {{- end }} } + {{- range .ModifyParams }} + {{- if (IsNested .) }} + if plan.{{ToCamel .Code}} != nil { + params[{{.ID}}] = {{NestedJSONExpr . "plan"}} + } + {{- end }} + {{- end }} if err := resources_core.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil { resp.Diagnostics.AddError("Ошибка клиента", err.Error()) return