584 lines
24 KiB
Go
584 lines
24 KiB
Go
package templates
|
||
|
||
const Instance = `package resources_gen
|
||
|
||
import (
|
||
"context"
|
||
{{- if .NeedsStringsImport }}
|
||
"strings"
|
||
{{- end }}
|
||
{{- if .NeedsFmtImport }}
|
||
"fmt"
|
||
{{- end }}
|
||
|
||
"terraform-provider-nubes/internal/core"
|
||
"terraform-provider-nubes/internal/resources_core"
|
||
|
||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||
{{- if .NeedsInt64Default }}
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||
{{- end }}
|
||
{{- if .NeedsStringDefault }}
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||
{{- end }}
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||
{{- if .NeedsBoolUseStateForUnknown }}
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
|
||
{{- end }}
|
||
{{- if .NeedsInt64UseStateForUnknown }}
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
||
{{- end }}
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||
)
|
||
|
||
// Code generated by TOOLS/resource-generator. DO NOT EDIT.
|
||
// Service: {{.Name}}
|
||
// Service ID: {{.ServiceID}}
|
||
|
||
var _ resource.Resource = &{{ToCamel .Name}}Resource{}
|
||
var _ resource.ResourceWithModifyPlan = &{{ToCamel .Name}}Resource{}
|
||
var _ resource.ResourceWithImportState = &{{ToCamel .Name}}Resource{}
|
||
|
||
type {{ToCamel .Name}}Resource struct {
|
||
client *core.UniversalClient
|
||
}
|
||
|
||
{{- range .SchemaParams }}
|
||
{{- if (IsNested .) }}
|
||
// {{NestedModelName $.Name .Code}} — вложенная модель для map-fixed параметра {{.Code}}.
|
||
type {{NestedModelName $.Name .Code}} struct {
|
||
{{- range .SubParams }}
|
||
{{ToCamel .Code}} {{ParamType .}} ` + "`" + `tfsdk:"{{ToSnake .Code}}" json:"{{.Code}}"` + "`" + `
|
||
{{- end }}
|
||
}
|
||
{{- end }}
|
||
{{- end }}
|
||
|
||
type {{ToCamel .Name}}Model struct {
|
||
ID types.String ` + "`" + `tfsdk:"id"` + "`" + `
|
||
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 }}
|
||
{{- if (IsNested .) }}
|
||
{{ToCamel .Code}} {{NestedTfType . $.Name}} ` + "`" + `tfsdk:"{{ToSnake .Code}}"` + "`" + `
|
||
{{- else }}
|
||
{{ToCamel .Code}} {{ParamType .}} ` + "`" + `tfsdk:"{{ToSnake .Code}}"` + "`" + `
|
||
{{- end }}
|
||
{{- end }}
|
||
{{- if .SupportsSuspendDestroy }}
|
||
SuspendOnDestroy types.Bool ` + "`" + `tfsdk:"suspend_on_destroy"` + "`" + `
|
||
{{- end }}
|
||
AdoptExistingOnCreate types.Bool ` + "`" + `tfsdk:"adopt_existing_on_create"` + "`" + `
|
||
{{- range .OutputParams }}
|
||
{{ToCamel .Code}} {{OutputType .}} ` + "`" + `tfsdk:"{{ToSnake .Code}}"` + "`" + `
|
||
{{- end }}
|
||
}
|
||
|
||
func New{{ToCamel .Name}}Resource() resource.Resource {
|
||
return &{{ToCamel .Name}}Resource{}
|
||
}
|
||
|
||
func (r *{{ToCamel .Name}}Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||
resp.TypeName = req.ProviderTypeName + "_{{.Name}}"
|
||
}
|
||
|
||
func (r *{{ToCamel .Name}}Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||
attrs := map[string]schema.Attribute{
|
||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||
"resource_name": schema.StringAttribute{Required: true},
|
||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||
{{- range .SchemaParams }}
|
||
{{- if (IsNested .) }}
|
||
"{{ToSnake .Code}}": {{NestedSchemaBlock .}}
|
||
{{- range .SubParams }}
|
||
"{{ToSnake .Code}}": schema.{{SubSchemaType .}}Attribute{
|
||
{{- if .Default }}Optional: true, Computed: true, Default: {{SubDefaultExpr .}},{{else if .Required}}Required: true,{{else}}Optional: true,{{end}}
|
||
{{- if ne (ParamDescription .) "" }}MarkdownDescription: {{ParamDescription .}},{{end}}
|
||
},
|
||
{{- end }}
|
||
{{NestedSchemaEnd .}}
|
||
{{- else }}
|
||
"{{ToSnake .Code}}": schema.{{if eq (ParamType .) "types.Bool"}}Bool{{else if eq (ParamType .) "types.Int64"}}Int64{{else}}String{{end}}Attribute{
|
||
{{- if and .Required (eq (ParamDefaultExpr .) "") (eq .RefSvcId 0) }}Required: true,{{else}}Optional: true,{{end}}
|
||
{{- if ne (ParamDefaultExpr .) "" }}Computed: true, Default: {{ParamDefaultExpr .}},{{- else if or .IsJson (ShouldBeOptionalComputed .) }}Computed: true,{{- end }}
|
||
{{- if ne (ParamDescription .) "" }}MarkdownDescription: {{ParamDescription .}},{{end}}
|
||
{{- if .Sensitive }}Sensitive: true,{{end}}
|
||
{{- if .IsJson }}PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()},{{- else if ShouldUseStateForUnknown . }}PlanModifiers: []planmodifier.{{if eq (ParamType .) "types.Bool"}}Bool{boolplanmodifier.UseStateForUnknown()}{{else if eq (ParamType .) "types.Int64"}}Int64{int64planmodifier.UseStateForUnknown()}{{else}}String{stringplanmodifier.UseStateForUnknown()}{{end}},{{- end }}
|
||
},
|
||
{{- 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 }}
|
||
{{- if .SupportsSuspendDestroy }}
|
||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool({{.SuspendOnDestroy}})},
|
||
{{- end }}
|
||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool({{.AdoptExistingOnCreate}})},
|
||
{{- range .OutputParams }}
|
||
{{- if or (OutputIsMap .) (OutputIsList .) }}
|
||
"{{ToSnake .Code}}": schema.{{if OutputIsMap .}}Map{{else}}List{{end}}Attribute{Computed: true, ElementType: types.StringType{{if OutputSensitive .}}, Sensitive: true{{end}}},
|
||
{{- else }}
|
||
"{{ToSnake .Code}}": schema.StringAttribute{Computed: true{{if OutputSensitive .}}, Sensitive: true{{end}}},
|
||
{{- end }}
|
||
{{- end }}
|
||
}
|
||
|
||
resp.Schema = schema.Schema{Attributes: attrs}
|
||
}
|
||
|
||
func (r *{{ToCamel .Name}}Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
|
||
if r.client == nil {
|
||
return
|
||
}
|
||
|
||
var config *{{ToCamel .Name}}Model
|
||
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
if config == nil {
|
||
return
|
||
}
|
||
|
||
var state *{{ToCamel .Name}}Model
|
||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
// Destroy-план (plan == null): create-time проверку «уже существует / adopt»
|
||
// запускать нельзя — удаление не валидируется через существование инстанса.
|
||
if req.Plan.Raw.IsNull() {
|
||
return
|
||
}
|
||
|
||
if state != nil && !state.ID.IsNull() && !state.ID.IsUnknown() {
|
||
var plan {{ToCamel .Name}}Model
|
||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
{{- if .HasRefSvcParams }}
|
||
// FIX(uuid-case): resolve ref_svc params НЕ делаем в ModifyPlan.
|
||
// Terraform правило: plan ОБЯЗАН равняться config для user-provided атрибутов.
|
||
// Resolve (displayName → UUID или uppercase → lowercase) нужен только для
|
||
// API-вызова в Create/Update. Делать его здесь = менять plan = ошибка
|
||
// "Provider produced invalid plan: planned value does not match config value".
|
||
{{- end }}
|
||
if !plan.ResourceName.IsNull() && !plan.ResourceName.IsUnknown() && !state.ResourceName.IsNull() && !state.ResourceName.IsUnknown() {
|
||
if plan.ResourceName.ValueString() != state.ResourceName.ValueString() {
|
||
resp.Diagnostics.AddError("Нельзя изменить resource_name", "Параметр resource_name задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||
return
|
||
}
|
||
}
|
||
{{- range .CreateOnlyParams }}
|
||
{{- if not (IsNested .) }}
|
||
if !plan.{{ToCamel .Code}}.IsNull() && !plan.{{ToCamel .Code}}.IsUnknown() && !state.{{ToCamel .Code}}.IsNull() && !state.{{ToCamel .Code}}.IsUnknown() {
|
||
{{- if eq (ParamType .) "types.Bool" }}
|
||
if plan.{{ToCamel .Code}}.ValueBool() != state.{{ToCamel .Code}}.ValueBool() {
|
||
resp.Diagnostics.AddError("Нельзя изменить {{ToSnake .Code}}", "Параметр задается при создании и не может быть изменен.")
|
||
return
|
||
}
|
||
{{- else if eq (ParamType .) "types.Int64" }}
|
||
if plan.{{ToCamel .Code}}.ValueInt64() != state.{{ToCamel .Code}}.ValueInt64() {
|
||
resp.Diagnostics.AddError("Нельзя изменить {{ToSnake .Code}}", "Параметр задается при создании и не может быть изменен.")
|
||
return
|
||
}
|
||
{{- else }}
|
||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||
// значение, менять его нельзя только если оно реально другое.
|
||
if !strings.EqualFold(plan.{{ToCamel .Code}}.ValueString(), state.{{ToCamel .Code}}.ValueString()) {
|
||
resp.Diagnostics.AddError("Нельзя изменить {{ToSnake .Code}}", "Параметр задается при создании и не может быть изменен.")
|
||
return
|
||
}
|
||
{{- end }}
|
||
}
|
||
{{- end }}
|
||
{{- end }}
|
||
return
|
||
}
|
||
{{- range .SchemaParams }}
|
||
{{- if and .Required (eq (ParamDefaultExpr .) "") (not (IsNested .)) }}
|
||
if config.{{ToCamel .Code}}.IsNull() {
|
||
resp.Diagnostics.AddError("Missing required attribute", "{{ToSnake .Code}} is required.")
|
||
return
|
||
}
|
||
if config.{{ToCamel .Code}}.IsUnknown() {
|
||
return
|
||
}
|
||
{{- end }}
|
||
{{- end }}
|
||
|
||
// ⛔ Create-time проверка существования/усыновления здесь СОЗНАТЕЛЬНО НЕ вызывается.
|
||
//
|
||
// Причина: при tainted-ресурсе Terraform планирует ЗАМЕНУ (destroy+create), и
|
||
// create-узел замены приходит в ModifyPlan с prior state = null — ровно как у
|
||
// нового ресурса. Отличить «замену» от «создания» на этом уровне невозможно,
|
||
// поэтому проверка «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ» ложно срабатывала на
|
||
// ещё не удалённый инстанс и блокировала plan/destroy.
|
||
//
|
||
// Проверка осталась в Create (CreateExistingResourceDiagnosticsWithDomainAndServices):
|
||
// на apply она выполняется ПОСЛЕ удаления старого инстанса, поэтому конфликта уже нет.
|
||
}
|
||
|
||
func (r *{{ToCamel .Name}}Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||
var data {{ToCamel .Name}}Model
|
||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
{{- range .SchemaParams }}
|
||
{{- if and .Required (eq (ParamDefaultExpr .) "") (not (IsNested .)) }}
|
||
if data.{{ToCamel .Code}}.IsNull() || data.{{ToCamel .Code}}.IsUnknown() {
|
||
resp.Diagnostics.AddError("Missing required attribute", "{{ToSnake .Code}} is required.")
|
||
return
|
||
}
|
||
{{- end }}
|
||
{{- end }}
|
||
{{- if .HasRefSvcParams }}
|
||
{{- range .CreateParams }}
|
||
{{- if and (gt .RefSvcId 0) (eq (ParamType .) "types.String") }}
|
||
// refSvc-поле резолвим только для API-запроса.
|
||
// config не перезаписываем: пользовательский display name или UUID должен
|
||
// пройти в state ровно в том виде, в котором его передал Terraform.
|
||
resolved{{ToCamel .Code}} := data.{{ToCamel .Code}}.ValueString()
|
||
if !data.{{ToCamel .Code}}.IsNull() && !data.{{ToCamel .Code}}.IsUnknown() {
|
||
var err error
|
||
resolved{{ToCamel .Code}}, err = r.client.ResolveRefSvcParamValue(ctx, {{.RefSvcId}}, data.{{ToCamel .Code}}.ValueString())
|
||
if err != nil {
|
||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||
return
|
||
}
|
||
}
|
||
{{- end }}
|
||
{{- end }}
|
||
{{- end }}
|
||
resourceName := data.ResourceName.ValueString()
|
||
desiredDomain := ""
|
||
{{- if .HasDomainParam }}
|
||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||
desiredDomain = data.Domain.ValueString()
|
||
}
|
||
{{- end }}
|
||
domainServiceIDs := []int{ {{- range .DomainServiceIDs }}{{.}}, {{- end }} }
|
||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, {{.ServiceID}}, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs, {{.SupportsSuspendDestroy}})...)
|
||
// ⛔ Проверяем HasError ДО create — при hard-error (running без adopt, suspend без adopt,
|
||
// not created, конфликт) сайд-эффект create не должен выполняться.
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
|
||
params := map[int]string{
|
||
{{- range .CreateParams }}
|
||
{{- if not (IsNested .) }}
|
||
{{- if and (gt .RefSvcId 0) (eq (ParamType .) "types.String") }}
|
||
{{.ID}}: resolved{{ToCamel .Code}},
|
||
{{- else }}
|
||
{{.ID}}: {{ParamFormat . (printf "data.%s" (ToCamel .Code))}},
|
||
{{- end }}
|
||
{{- 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() {
|
||
operationTimeout = data.OperationTimeout.ValueString()
|
||
}
|
||
if !data.LogLevel.IsNull() && !data.LogLevel.IsUnknown() {
|
||
ctx = core.CtxWithLogLevel(ctx, data.LogLevel.ValueString())
|
||
}
|
||
id, err := resources_core.CreateResourceWithTimeout(ctx, r.client, {{.ServiceID}}, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||
if err != nil {
|
||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||
return
|
||
}
|
||
data.ID = types.StringValue(id)
|
||
|
||
state, diags := resources_core.RefreshResourceState(ctx, r.client, id, {{.ServiceID}}, data, []resources_core.StateField{
|
||
{{- range .OutputParams }}
|
||
{Code: "{{.Code}}"},
|
||
{{- end }}
|
||
}, []resources_core.InputField{
|
||
{{- range .SchemaParams }}
|
||
{{- if eq .RefSvcId 0 }}
|
||
{Code: "{{.Code}}", Field: "{{ToCamel .Code}}", Type: "{{.Type}}"},
|
||
{{- end }}
|
||
{{- end }}
|
||
})
|
||
resp.Diagnostics.Append(diags...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||
}
|
||
|
||
func (r *{{ToCamel .Name}}Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||
var state {{ToCamel .Name}}Model
|
||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||
return
|
||
}
|
||
if r.client != nil {
|
||
remove, err := resources_core.ShouldRemoveFromState(ctx, r.client, state.ID.ValueString())
|
||
if err != nil {
|
||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||
return
|
||
}
|
||
if remove {
|
||
resp.State.RemoveResource(ctx)
|
||
return
|
||
}
|
||
}
|
||
|
||
newState, diags := resources_core.RefreshResourceState(ctx, r.client, state.ID.ValueString(), {{.ServiceID}}, state, []resources_core.StateField{
|
||
{{- range .OutputParams }}
|
||
{Code: "{{.Code}}"},
|
||
{{- end }}
|
||
}, []resources_core.InputField{
|
||
{{- range .SchemaParams }}
|
||
{{- if eq .RefSvcId 0 }}
|
||
{Code: "{{.Code}}", Field: "{{ToCamel .Code}}", Type: "{{.Type}}"},
|
||
{{- end }}
|
||
{{- end }}
|
||
})
|
||
resp.Diagnostics.Append(diags...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||
}
|
||
|
||
func (r *{{ToCamel .Name}}Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||
var plan {{ToCamel .Name}}Model
|
||
var state {{ToCamel .Name}}Model
|
||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
|
||
instanceID := state.ID
|
||
if instanceID.IsNull() || instanceID.IsUnknown() {
|
||
instanceID = plan.ID
|
||
}
|
||
if instanceID.IsNull() || instanceID.IsUnknown() {
|
||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра для modify")
|
||
return
|
||
}
|
||
|
||
hasServiceParamChanges := false
|
||
{{- range .ModifyParams }}
|
||
{{- if not (IsNested .) }}
|
||
if !hasServiceParamChanges {
|
||
if plan.{{ToCamel .Code}}.IsNull() != state.{{ToCamel .Code}}.IsNull() || plan.{{ToCamel .Code}}.IsUnknown() != state.{{ToCamel .Code}}.IsUnknown() {
|
||
hasServiceParamChanges = true
|
||
} else if !plan.{{ToCamel .Code}}.IsNull() && !plan.{{ToCamel .Code}}.IsUnknown() && !state.{{ToCamel .Code}}.IsNull() && !state.{{ToCamel .Code}}.IsUnknown() {
|
||
{{- if eq (ParamType .) "types.Bool" }}
|
||
if plan.{{ToCamel .Code}}.ValueBool() != state.{{ToCamel .Code}}.ValueBool() {
|
||
hasServiceParamChanges = true
|
||
}
|
||
{{- else if eq (ParamType .) "types.Int64" }}
|
||
if plan.{{ToCamel .Code}}.ValueInt64() != state.{{ToCamel .Code}}.ValueInt64() {
|
||
hasServiceParamChanges = true
|
||
}
|
||
{{- else }}
|
||
if !strings.EqualFold(plan.{{ToCamel .Code}}.ValueString(), state.{{ToCamel .Code}}.ValueString()) {
|
||
hasServiceParamChanges = true
|
||
}
|
||
{{- end }}
|
||
}
|
||
}
|
||
{{- end }}
|
||
{{- end }}
|
||
|
||
{{- 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}} {
|
||
// Сервисные параметры не изменились → modify НЕ вызываем.
|
||
//
|
||
// НО read-back поля (state_params*, state_out*, vault_*) обязаны быть
|
||
// перечитаны из инстанса. Раньше здесь слепо копировались значения из
|
||
// старого state, из-за чего tfstate хранил устаревшее значение:
|
||
// пример — state_params["needEnableAVI"]="false", когда на платформе уже
|
||
// true (модификатор включил ALB). Это давало ложный дрейф
|
||
// «Objects have changed outside of Terraform» на каждом plan.
|
||
plan.ID = instanceID
|
||
refreshed, refreshDiags := resources_core.RefreshResourceState(ctx, r.client, instanceID.ValueString(), {{.ServiceID}}, plan, []resources_core.StateField{
|
||
{{- range .OutputParams }}
|
||
{Code: "{{.Code}}"},
|
||
{{- end }}
|
||
}, []resources_core.InputField{
|
||
{{- range .SchemaParams }}
|
||
{{- if eq .RefSvcId 0 }}
|
||
{Code: "{{.Code}}", Field: "{{ToCamel .Code}}", Type: "{{.Type}}"},
|
||
{{- end }}
|
||
{{- end }}
|
||
})
|
||
resp.Diagnostics.Append(refreshDiags...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, &refreshed)...)
|
||
return
|
||
}
|
||
|
||
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 .HasRedeploy }}
|
||
// --- Redeploy support (ARCHITECTURE.md) ---
|
||
// 1. Modify — только если изменились параметры сервиса
|
||
if hasServiceParamChanges {
|
||
{{- end }}
|
||
params := map[int]string{
|
||
{{- range .ModifyParams }}
|
||
{{- 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
|
||
}
|
||
{{- if .HasRedeploy }}
|
||
}
|
||
|
||
// 2. Redeploy — только если изменился git_revision
|
||
if redeployRequested {
|
||
redeployParams := map[int]string{}
|
||
{{- range .RedeployParams }}
|
||
{{- if (IsNested .) }}
|
||
if plan.{{ToCamel .Code}} != nil {
|
||
redeployParams[{{.ID}}] = {{NestedJSONExpr . "plan"}}
|
||
}
|
||
{{- else }}
|
||
redeployParams[{{.ID}}] = {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}}
|
||
{{- end }}
|
||
{{- end }}
|
||
if err := r.client.RunRedeployOperation(ctx, instanceID.ValueString(), operationTimeout, redeployParams); err != nil {
|
||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||
return
|
||
}
|
||
}
|
||
{{- end }}
|
||
|
||
plan.ID = instanceID
|
||
state, diags := resources_core.RefreshResourceState(ctx, r.client, instanceID.ValueString(), {{.ServiceID}}, plan, []resources_core.StateField{
|
||
{{- range .OutputParams }}
|
||
{Code: "{{.Code}}"},
|
||
{{- end }}
|
||
}, []resources_core.InputField{
|
||
{{- range .SchemaParams }}
|
||
{{- if eq .RefSvcId 0 }}
|
||
{Code: "{{.Code}}", Field: "{{ToCamel .Code}}", Type: "{{.Type}}"},
|
||
{{- end }}
|
||
{{- end }}
|
||
})
|
||
resp.Diagnostics.Append(diags...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||
}
|
||
|
||
func (r *{{ToCamel .Name}}Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||
var state {{ToCamel .Name}}Model
|
||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||
return
|
||
}
|
||
|
||
{{- if .SupportsSuspendDestroy }}
|
||
deleteMode := "state_only"
|
||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||
deleteMode = "suspend"
|
||
}
|
||
{{- else }}
|
||
deleteMode := "delete"
|
||
{{- end }}
|
||
|
||
operationTimeout := ""
|
||
if !state.OperationTimeout.IsNull() && !state.OperationTimeout.IsUnknown() {
|
||
operationTimeout = state.OperationTimeout.ValueString()
|
||
}
|
||
if !state.LogLevel.IsNull() && !state.LogLevel.IsUnknown() {
|
||
ctx = core.CtxWithLogLevel(ctx, state.LogLevel.ValueString())
|
||
}
|
||
if err := resources_core.DeleteResourceWithTimeout(ctx, r.client, state.ID.ValueString(), deleteMode, operationTimeout); err != nil {
|
||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||
return
|
||
}
|
||
}
|
||
|
||
func (r *{{ToCamel .Name}}Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||
}
|
||
|
||
func (r *{{ToCamel .Name}}Resource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||
if req.ProviderData == nil {
|
||
return
|
||
}
|
||
client, ok := req.ProviderData.(*core.UniversalClient)
|
||
if !ok {
|
||
resp.Diagnostics.AddError("Error", "Invalid client type")
|
||
return
|
||
}
|
||
r.client = client
|
||
}
|
||
`
|