From e65bc45f5a66dbdd9e82f96aadc094d8a18c41a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sun, 5 Jul 2026 08:51:45 +0400 Subject: [PATCH] v5.0.59: redeploy as git_revision, no separate action resources, gen_v2 follows ARCHITECTURE.md --- TEST_STAND/PG/main.tf | 2 +- TEST_STAND/POSTGRES/main.tf | 2 +- TEST_STAND/buck0/main.tf | 2 +- devops/profiles/test/profile.env | 6 +- universal_rebuild/internal/core/client.go | 51 ++++++++ .../tools/gen_v2/generate_resources_v2.go | 113 +++++++++++++----- 6 files changed, 142 insertions(+), 34 deletions(-) diff --git a/TEST_STAND/PG/main.tf b/TEST_STAND/PG/main.tf index 4c77ef2..8445565 100644 --- a/TEST_STAND/PG/main.tf +++ b/TEST_STAND/PG/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { nubes = { source = "terra.k8c.ru/nubes-test/nubes" - version = "5.0.58" + version = "5.0.59" } } } diff --git a/TEST_STAND/POSTGRES/main.tf b/TEST_STAND/POSTGRES/main.tf index ec1027f..6681e64 100644 --- a/TEST_STAND/POSTGRES/main.tf +++ b/TEST_STAND/POSTGRES/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { nubes = { source = "terra.k8c.ru/nubes-test/nubes" - version = "5.0.58" + version = "5.0.59" } } } diff --git a/TEST_STAND/buck0/main.tf b/TEST_STAND/buck0/main.tf index deb7672..ab50a34 100644 --- a/TEST_STAND/buck0/main.tf +++ b/TEST_STAND/buck0/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { nubes = { source = "terra.k8c.ru/nubes-test/nubes" - version = "5.0.58" + version = "5.0.59" } } } diff --git a/devops/profiles/test/profile.env b/devops/profiles/test/profile.env index 478e4ef..832653e 100644 --- a/devops/profiles/test/profile.env +++ b/devops/profiles/test/profile.env @@ -3,9 +3,9 @@ NUBES_API_ENDPOINT="https://lk-api-gateway-test.ngcloud.ru/api/v1/svc" TOKEN_FILE="secrets/test.token" # Release versions -RELEASE_VERSION="5.0.58" -PROVIDER_VERSION="5.0.58" -DOCS_VERSION="5.0.58" +RELEASE_VERSION="5.0.59" +PROVIDER_VERSION="5.0.59" +DOCS_VERSION="5.0.59" # Docs generation — ONLY from docs_gen// (never from docs/) DOCS_GEN_DIR="universal_rebuild/docs_gen/test" diff --git a/universal_rebuild/internal/core/client.go b/universal_rebuild/internal/core/client.go index 006a9a7..0175751 100644 --- a/universal_rebuild/internal/core/client.go +++ b/universal_rebuild/internal/core/client.go @@ -476,6 +476,57 @@ func (c *UniversalClient) RunInstanceOperationUniversalWithDefaults(ctx context. return c.waitForOperationFinish(ctx, opUid, c.operationTimeoutForContext(ctx, state.ServiceId, action)) } +// RunRedeployOperation запускает redeploy для сервисов, поддерживающих пересборку из git. +// В отличие от modify, redeploy не требует CFS-параметров — операция запускается без них. +// Используется генератором (gen_v2) согласно ARCHITECTURE.md. +func (c *UniversalClient) RunRedeployOperation(ctx context.Context, instanceUid string, timeoutOverride string) error { + state, err := c.GetInstanceState(ctx, instanceUid) + if err != nil { + return err + } + if state.OperationIsPending || state.OperationIsInProgress { + if err := c.waitForInstanceIdle(ctx, instanceUid, c.idleTimeoutFor(state.ServiceId)); err != nil { + return err + } + } + + opId := 0 + for _, op := range state.AvailableOperations { + if strings.EqualFold(op.Operation, "redeploy") { + opId = op.SvcOperationId + break + } + } + if opId == 0 { + return fmt.Errorf("операция redeploy недоступна для экземпляра %s", instanceUid) + } + + payload := map[string]interface{}{ + "instanceUid": instanceUid, + "svcOperationId": opId, + "operation": "redeploy", + } + opUid, err := c.postIgnoreResponse(ctx, "/instanceOperations", payload, true) + if err != nil { + return fmt.Errorf("не удалось создать операцию redeploy: %w", err) + } + if opUid == "" { + return fmt.Errorf("не удалось получить UID операции redeploy") + } + + if _, _, err := c.doRequest(ctx, "POST", fmt.Sprintf("/instanceOperations/%s/run", opUid), map[string]interface{}{}); err != nil { + return err + } + + timeout := c.operationTimeoutForContext(ctx, state.ServiceId, "redeploy") + if timeoutOverride != "" { + if d, parseErr := time.ParseDuration(timeoutOverride); parseErr == nil { + timeout = d + } + } + return c.waitForOperationFinish(ctx, opUid, timeout) +} + // Instance state structures type ApiOperation struct { diff --git a/universal_rebuild/tools/gen_v2/generate_resources_v2.go b/universal_rebuild/tools/gen_v2/generate_resources_v2.go index ff3710f..f461415 100644 --- a/universal_rebuild/tools/gen_v2/generate_resources_v2.go +++ b/universal_rebuild/tools/gen_v2/generate_resources_v2.go @@ -96,6 +96,12 @@ type GenResource struct { NeedsStringDefault bool HasDomainParam bool DomainServiceIDs []int + // HasRedeploy: сервис поддерживает redeploy (пересборка из git). + // Если true — в основной ресурс добавляется поле git_revision. + // При изменении git_revision вызывается redeploy вместо modify. + // Правило из ARCHITECTURE.md: только redeploy включается как inline action. + // restart, recovery, reconcile — исключены (ручные операции, только UI). + HasRedeploy bool // NeedsJsonPlanMod: хотя бы один атрибут имеет data_type: json. // Управляет добавлением импорта planmodifier в сгенерированный файл. NeedsJsonPlanMod bool @@ -277,6 +283,37 @@ func loadSpecs(dir string) ([]GenResource, []GenSubresource, []GenAction, error) // 3) есть строковые CreateOnlyParams — шаблон генерирует EqualFold в create-only validation (ModifyPlan) // RefSvcId=12 (S3 Object Storage) НЕ генерирует restore-блок — для S3 UUID обязателен точный формат gr.NeedsStringsImport = hasRestoreCasingParams(gr.SchemaParams) || analyzeNeedsStrings(gr.ModifyParams) || analyzeNeedsStrings(gr.CreateOnlyRequiredParams) || analyzeNeedsStrings(gr.CreateOnlyParams) + + // --- Action processing (ДО append, чтобы HasRedeploy попал в слайс) --- + // Правила из ARCHITECTURE.md: + // - redeploy → встроить как git_revision в основной ресурс + // - restart, recovery, reconcile → исключить (ручные операции, только UI) + // - всё остальное → стандартный action-ресурс (на будущее, если появятся новые) + for _, op := range spec.Operations { + if op.Kind != "action" { + continue + } + switch op.Action { + case "redeploy": + gr.HasRedeploy = true + case "restart", "recovery", "reconcile": + // Исключены + default: + act := GenAction{ + ServiceName: spec.Name, + ServiceID: spec.ServiceID, + ActionName: op.Action, + OperationName: op.Name, + Params: convertParams(op.Params), + } + act.SchemaParams = act.Params + analyzeParams(&act.UsesBool, &act.UsesInt64, &act.UsesString, &act.HasDefaults, &act.NeedsBoolDefault, &act.NeedsInt64Default, &act.NeedsStringDefault, act.SchemaParams) + act.NeedsJsonPlanMod = analyzeJsonPlanMod(act.SchemaParams) + act.NeedsStringsImport = analyzeNeedsStrings(act.SchemaParams) + actions = append(actions, act) + } + } + services = append(services, gr) srByName := map[string]*GenSubresource{} @@ -326,24 +363,6 @@ func loadSpecs(dir string) ([]GenResource, []GenSubresource, []GenAction, error) subs = append(subs, *sr) } - for _, op := range spec.Operations { - if op.Kind != "action" { - continue - } - act := GenAction{ - ServiceName: spec.Name, - ServiceID: spec.ServiceID, - ActionName: op.Action, - OperationName: op.Name, - Params: convertParams(op.Params), - } - act.SchemaParams = act.Params - analyzeParams(&act.UsesBool, &act.UsesInt64, &act.UsesString, &act.HasDefaults, &act.NeedsBoolDefault, &act.NeedsInt64Default, &act.NeedsStringDefault, act.SchemaParams) - act.NeedsJsonPlanMod = analyzeJsonPlanMod(act.SchemaParams) - act.NeedsStringsImport = analyzeNeedsStrings(act.SchemaParams) - actions = append(actions, act) - } - return nil }) @@ -887,6 +906,12 @@ type {{ToCamel .Name}}Model struct { ResourceName types.String ` + "`" + `tfsdk:"resource_name"` + "`" + ` OperationTimeout types.String ` + "`" + `tfsdk:"operation_timeout"` + "`" + ` LogLevel types.String ` + "`" + `tfsdk:"log_level"` + "`" + ` + {{- if .HasRedeploy }} + // --- Redeploy support (ARCHITECTURE.md) --- + // git_revision: при изменении вызывает redeploy вместо modify. + // Опциональное поле — если не задано, modify работает как обычно. + GitRevision types.String ` + "`" + `tfsdk:"git_revision"` + "`" + ` + {{- end }} {{- range .SchemaParams }} {{ToCamel .Code}} {{ParamType .}} ` + "`" + `tfsdk:"{{ToSnake .Code}}"` + "`" + ` {{- end }} @@ -920,6 +945,12 @@ func (r *{{ToCamel .Name}}Resource) Schema(ctx context.Context, req resource.Sch {{- if .IsJson }}PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()},{{- end }} }, {{- end }} + {{- if .HasRedeploy }} + // --- Redeploy support (ARCHITECTURE.md) --- + // При изменении git_revision вызывается redeploy вместо modify. + // Если не задано — modify работает без изменений. + "git_revision": schema.StringAttribute{Optional: true, MarkdownDescription: "Git revision (commit hash/tag). Changing this triggers redeploy instead of modify."}, + {{- end }} "suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool({{.SuspendOnDestroy}})}, "adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool({{.AdoptExistingOnCreate}})}, {{- range .OutputParams }} @@ -1291,7 +1322,20 @@ func (r *{{ToCamel .Name}}Resource) Update(ctx context.Context, req resource.Upd } {{- end }} - if !hasServiceParamChanges { + {{- if .HasRedeploy }} + // --- Redeploy support (ARCHITECTURE.md) --- + // Проверяем изменился ли git_revision. + // Если да — вызываем redeploy вместо modify. + // git_revision = "" (не задано) → обычный modify. + redeployRequested := false + if !plan.GitRevision.IsNull() && !plan.GitRevision.IsUnknown() { + if state.GitRevision.IsNull() || state.GitRevision.IsUnknown() || plan.GitRevision.ValueString() != state.GitRevision.ValueString() { + redeployRequested = true + } + } + {{- end }} + + if !hasServiceParamChanges{{if .HasRedeploy}} && !redeployRequested{{end}} { plan.ID = instanceID {{- range .OutputParams }} plan.{{ToCamel .Code}} = state.{{ToCamel .Code}} @@ -1300,12 +1344,6 @@ func (r *{{ToCamel .Name}}Resource) Update(ctx context.Context, req resource.Upd return } - params := map[int]string{ - {{- range .ModifyParams }} - {{.ID}}: {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}}, - {{- end }} - } - operationTimeout := "" if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() { operationTimeout = plan.OperationTimeout.ValueString() @@ -1313,10 +1351,29 @@ func (r *{{ToCamel .Name}}Resource) Update(ctx context.Context, req resource.Upd if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() { ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString()) } - if err := resources_core.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil { - resp.Diagnostics.AddError("Ошибка клиента", err.Error()) - return + + {{- if .HasRedeploy }} + // --- Redeploy support (ARCHITECTURE.md) --- + if redeployRequested { + // Вызываем redeploy — пустой params (операция не требует CFS-параметров) + if err := r.client.RunRedeployOperation(ctx, instanceID.ValueString(), operationTimeout); err != nil { + resp.Diagnostics.AddError("Ошибка клиента", err.Error()) + return + } + } else { + {{- end }} + params := map[int]string{ + {{- range .ModifyParams }} + {{.ID}}: {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}}, + {{- end }} + } + if err := resources_core.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil { + resp.Diagnostics.AddError("Ошибка клиента", err.Error()) + return + } + {{- if .HasRedeploy }} } + {{- end }} plan.ID = instanceID state, diags := resources_core.RefreshResourceState(ctx, r.client, instanceID.ValueString(), {{.ServiceID}}, plan, []resources_core.StateField{