diff --git a/TOOLS/resource-generator/internal/templates/modifier.go b/TOOLS/resource-generator/internal/templates/modifier.go index dd0898e..3fb8fce 100644 --- a/TOOLS/resource-generator/internal/templates/modifier.go +++ b/TOOLS/resource-generator/internal/templates/modifier.go @@ -4,6 +4,7 @@ const Modifier = `package resources_gen import ( "context" + "fmt" "strings" "terraform-provider-nubes/internal/core" @@ -36,12 +37,12 @@ type {{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Resource struct { } type {{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Model struct { - ID types.String ` + "`" + `tfsdk:"id"` + "`" + ` - {{ToCamel .ServiceName}}ID types.String ` + "`" + `tfsdk:"{{ToSnake .ServiceName}}_id"` + "`" + ` - OperationTimeout types.String ` + "`" + `tfsdk:"operation_timeout"` + "`" + ` - LogLevel types.String ` + "`" + `tfsdk:"log_level"` + "`" + ` + ID types.String {{.bt}}tfsdk:"id"{{.bt}} + {{ToCamel .ServiceName}}ID types.String {{.bt}}tfsdk:"{{ToSnake .ServiceName}}_id"{{.bt}} + OperationTimeout types.String {{.bt}}tfsdk:"operation_timeout"{{.bt}} + LogLevel types.String {{.bt}}tfsdk:"log_level"{{.bt}} {{- range .SchemaParams }} - {{ToCamel .Code}} {{ParamType .}} ` + "`" + `tfsdk:"{{ToSnake .Code}}"` + "`" + ` + {{ToCamel .Code}} {{ParamType .}} {{.bt}}tfsdk:"{{ToSnake .Code}}"{{.bt}} {{- end }} } @@ -76,28 +77,9 @@ func (r *{{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Resource) Create var plan {{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Model resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } - {{- range .SchemaParams }} - {{- if and .Required (eq (ParamDefaultExpr .) "") }} - if plan.{{ToCamel .Code}}.IsNull() || plan.{{ToCamel .Code}}.IsUnknown() { - resp.Diagnostics.AddError("Missing required attribute", "{{ToSnake .Code}} is required.") - return - } - {{- end }} - {{- end }} - instanceUID := strings.TrimSpace(plan.{{ToCamel .ServiceName}}ID.ValueString()) - if instanceUID == "" { resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра"); return } - params := resources_core.CompactParams(map[string]string{ - {{- range .SchemaParams }} - "{{.Code}}": {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}}, - {{- end }} - }) - operationTimeout := "" - if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() { operationTimeout = plan.OperationTimeout.ValueString() } - if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() { ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString()) } - if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "{{.OperationName}}", params, operationTimeout); err != nil { + if err := r.reconcile(ctx, &plan, nil); err != nil { resp.Diagnostics.AddError("Ошибка клиента", err.Error()); return } - plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "{{.OperationName}}", "{{.ModifierName}}")) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -125,32 +107,68 @@ func (r *{{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Resource) Update var plan {{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Model resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } - {{- range .SchemaParams }} - {{- if and .Required (eq (ParamDefaultExpr .) "") }} - if plan.{{ToCamel .Code}}.IsNull() || plan.{{ToCamel .Code}}.IsUnknown() { - resp.Diagnostics.AddError("Missing required attribute", "{{ToSnake .Code}} is required.") - return - } - {{- end }} - {{- end }} - instanceUID := strings.TrimSpace(plan.{{ToCamel .ServiceName}}ID.ValueString()) - params := resources_core.CompactParams(map[string]string{ - {{- range .SchemaParams }} - "{{.Code}}": {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}}, - {{- end }} - }) - operationTimeout := "" - if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() { operationTimeout = plan.OperationTimeout.ValueString() } - if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() { ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString()) } - if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "{{.OperationName}}", params, operationTimeout); err != nil { + if err := r.reconcile(ctx, &plan, nil); err != nil { resp.Diagnostics.AddError("Ошибка клиента", err.Error()); return } - plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "{{.OperationName}}", "{{.ModifierName}}")) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } +// reconcile — единый путь apply для модификатора (полный payload, досылка в core). +// override — обратные значения (wire-строки), используются только Delete=inverse. +func (r *{{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Resource) reconcile(ctx context.Context, model *{{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Model, override map[string]string) error { + instanceUID := strings.TrimSpace(model.{{ToCamel .ServiceName}}ID.ValueString()) + if instanceUID == "" { return fmt.Errorf("отсутствует идентификатор экземпляра") } + params := map[string]string{} + {{- range .SchemaParams }} + {{- if .Required }} + if model.{{ToCamel .Code}}.IsNull() || model.{{ToCamel .Code}}.IsUnknown() { + return fmt.Errorf("{{ToSnake .Code}} is required") + } + params["{{.Code}}"] = {{ParamFormat . (printf "model.%s" (ToCamel .Code))}} + {{- else }} + if !model.{{ToCamel .Code}}.IsNull() && !model.{{ToCamel .Code}}.IsUnknown() { + params["{{.Code}}"] = {{ParamFormat . (printf "model.%s" (ToCamel .Code))}} + } + {{- end }} + {{- end }} + for k, v := range override { + params[k] = v + } + operationTimeout := "" + if !model.OperationTimeout.IsNull() && !model.OperationTimeout.IsUnknown() { operationTimeout = model.OperationTimeout.ValueString() } + if !model.LogLevel.IsNull() && !model.LogLevel.IsUnknown() { ctx = core.CtxWithLogLevel(ctx, model.LogLevel.ValueString()) } + {{- if eq .Idempotency "check_before_run" }} + if err := resources_core.RunOperationByCodeIdempotent(ctx, r.client, instanceUID, "{{.OperationName}}", params, operationTimeout); err != nil { + return err + } + {{- else }} + if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "{{.OperationName}}", params, operationTimeout); err != nil { + return err + } + {{- end }} + model.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "{{.OperationName}}", "{{.ModifierName}}")) + return nil +} + func (r *{{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - // No-op: no confirmed inverse payload exists for this modifier. + var state {{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { return } + {{- if eq .DeleteStrategy "error" }} + resp.Diagnostics.AddError("Запрещено удалять", "Модификатор {{.ServiceName}}.{{.ModifierName}} нельзя удалять: эффект необратим. Обратитесь в техническую поддержку.") + return + {{- else if eq .DeleteStrategy "inverse" }} + override := map[string]string{ + {{- range .DeleteParams }} + "{{.Code}}": {{printf "%q" .Value}}, + {{- end }} + } + if err := r.reconcile(ctx, &state, override); err != nil { + resp.Diagnostics.AddError("Ошибка клиента", err.Error()); return + } + {{- else }} + resp.Diagnostics.AddWarning("Эффект остаётся на платформе", "Удаление модификатора {{.ServiceName}}.{{.ModifierName}} снимает его из state, но его эффект на платформе остаётся. Откатите вручную при необходимости.") + {{- end }} } func (r *{{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Resource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { @@ -159,4 +177,4 @@ func (r *{{ToCamel (printf "%s_%s" .ServiceName .ModifierName)}}Resource) Config if !ok { resp.Diagnostics.AddError("Error", "Invalid client type"); return } r.client = client } -` \ No newline at end of file +`