Files
tf_provider/TOOLS/resource-generator/internal/templates/subresource.go
T
Repinoid 93784e44a7 fix(subresource): диагностика чтения пароля + одна точка заполнения в Create
По итогам код-ревью (коммит 64328ab):
- ResolveUserPasswordFromVault теперь возвращает и текст предупреждения: если пароль
  прочитать не удалось (недоступен API/Vault, пустое имя, нет записи), в выводе apply
  появляется Warning. Раньше поле молча становилось null и причина была невидима.
  Отсутствие секретов у родителя ошибкой не считается (для части сервисов это норма).
- В шаблоне подресурса заполнение вынесено в одно замыкание applyPassword(),
  вызываемое перед каждым resp.State.Set в Create (4 сохранения — 4 вызова).
  Убирает четыре одинаковые строки и снижает риск забыть новую ветку выхода.
- Тест проверяет: замыкание есть, предупреждение есть, и вызовов applyPassword()
  не меньше, чем сохранений состояния в Create.

Проверено: generated/test/go/90_postgres_user_resource.go — 4 State.Set, 4 вызова
(плюс одно упоминание в комментарии); go test ./... ok; go build ./internal/... и
полная сборка провайдера во временной копии с новым generated — чисто.
2026-10-01 19:31:35 +03:00

518 lines
23 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package templates
const Subresource = `package resources_gen
import (
"context"
"strings"
"terraform-provider-nubes/internal/core"
"terraform-provider-nubes/internal/resources_core"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
{{- if .NeedsInt64Default }}
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
{{- end }}
{{- if .NeedsStringDefault }}
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
{{- end }}
{{- if .NeedsBoolPlanMod }}
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
{{- end }}
{{- if .NeedsInt64PlanMod }}
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
{{- end }}
"github.com/hashicorp/terraform-plugin-framework/types"
)
// Code generated by TOOLS/resource-generator. DO NOT EDIT.
// Service: {{.ServiceName}}
// Subresource: {{.SubName}}
var _ resource.Resource = &{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource{}
// {{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource manages a subresource via operations.
type {{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource struct {
client *core.UniversalClient
}
type {{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Model struct {
ID types.String ` + "`" + `tfsdk:"id"` + "`" + `
{{ToCamel .ServiceName}}ID types.String ` + "`" + `tfsdk:"{{ToSnake .ServiceName}}_id"` + "`" + `
AdoptExistingOnCreate types.Bool ` + "`" + `tfsdk:"adopt_existing_on_create"` + "`" + `
SkipMissingOnDelete types.Bool ` + "`" + `tfsdk:"skip_missing_on_delete"` + "`" + `
KeepOnDestroy types.Bool ` + "`" + `tfsdk:"keep_on_destroy"` + "`" + `
{{- if .VaultUserPassword }}
// Password — пароль пользователя, который генерирует платформа и кладёт в секрет
// Vault родителя. Заполняется в Create сразу после create_user: потребителю не нужно
// читать vault_secrets родителя (внутри одного apply оно ещё пусто).
Password types.String ` + "`" + `tfsdk:"password"` + "`" + `
{{- end }}
OperationTimeout types.String ` + "`" + `tfsdk:"operation_timeout"` + "`" + `
LogLevel types.String ` + "`" + `tfsdk:"log_level"` + "`" + `
{{- range .SchemaParams }}
{{ToCamel .Code}} {{ParamType .}} ` + "`" + `tfsdk:"{{ToSnake .Code}}"` + "`" + `
{{- end }}
}
func New{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource() resource.Resource {
return &{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource{}
}
func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_{{.ServiceName}}_{{.SubName}}"
}
func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}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()}},
"{{ToSnake .ServiceName}}_id": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
},
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
"skip_missing_on_delete": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
"keep_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Режим state_only при destroy: подресурс не удаляется и не меняется в облаке, только убирается из состояния. Нужен там, где родительский инстанс при destroy не удаляется (например suspend_on_destroy = true) и объект должен остаться внутри него. false = обычное удаление."},
{{- if .VaultUserPassword }}
"password": schema.StringAttribute{Computed: true, Sensitive: true, MarkdownDescription: "Пароль пользователя, который генерирует платформа. Провайдер читает его из Vault родительского инстанса сразу после создания пользователя."},
{{- end }}
"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 }}
"{{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 .IsJson }}Computed: true,{{- end }}
{{- if ne (ParamDescription .) "" }}MarkdownDescription: {{ParamDescription .}},{{end}}
{{- if .Sensitive }}Sensitive: true,{{end}}
{{- if .ForceNew }}PlanModifiers: []planmodifier.{{PlanModifierType .}}{ {{PlanModifierExpr .}} },{{else if .IsJson}}PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()},{{end}}
},
{{- end }}
}
resp.Schema = schema.Schema{Attributes: attrs}
}
func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan {{ToCamel (printf "%s_%s" .ServiceName .SubName)}}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 }}
{{- if .HasRefSvcParams }}
{{- range .CreateParams }}
{{- if and (gt .RefSvcId 0) (eq (ParamType .) "types.String") }}
// Подресурсный refSvc-параметр резолвим только в локальную переменную.
// Сам plan не трогаем: UUID нужен лишь для backend-вызова, а в state
// Terraform должен видеть исходное пользовательское значение.
resolved{{ToCamel .Code}} := plan.{{ToCamel .Code}}.ValueString()
if !plan.{{ToCamel .Code}}.IsNull() && !plan.{{ToCamel .Code}}.IsUnknown() {
var err error
resolved{{ToCamel .Code}}, err = r.client.ResolveRefSvcParamValue(ctx, {{.RefSvcId}}, plan.{{ToCamel .Code}}.ValueString())
if err != nil {
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
return
}
}
{{- end }}
{{- end }}
{{- end }}
instanceUID := strings.TrimSpace(plan.{{ToCamel .ServiceName}}ID.ValueString())
if instanceUID == "" {
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
return
}
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
{{- if .VaultUserPassword }}
// password — Computed: в плане он unknown, а Terraform требует КОНКРЕТНОЕ значение после
// apply, иначе «Provider returned invalid result object after apply». Поэтому:
// 1) сразу задаём конкретный null — покрывает ветки, где читать нечего;
// 2) applyPassword() обязана вызываться перед КАЖДЫМ сохранением состояния в Create
// (обычный путь и все ветки усыновления) — она подставляет реальный пароль.
// Замыкание одно, чтобы новая ветка выхода не осталась без заполнения.
plan.Password = types.StringNull()
applyPassword := func() {
var warn string
plan.Password, warn = resources_core.ResolveUserPasswordFromVault(ctx, r.client, instanceUID, plan.Username.ValueString(), plan.Password)
if warn != "" {
resp.Diagnostics.AddWarning("Пароль пользователя не прочитан из Vault", warn)
}
}
{{- end }}
idParams := map[string]string{
{{- range .IdentityParams }}
"{{.Code}}": {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}},
{{- end }}
}
identityCodes := []string{
{{- range .IdentityParams }}
"{{.Code}}",
{{- end }}
}
listKey, idKey := resources_core.ResolveSubresourceStateKeys("{{.SubName}}", identityCodes)
targetValue := strings.TrimSpace(idParams[idKey])
if targetValue == "" {
for _, code := range identityCodes {
candidate := strings.TrimSpace(idParams[code])
if candidate == "" {
continue
}
targetValue = candidate
idKey = code
break
}
}
if targetValue != "" {
found, known, err := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
if err != nil {
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
return
}
if known && found {
if adoptExistingOnCreate {
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "{{.SubName}}", idParams))
{{- if .VaultUserPassword }}
applyPassword()
{{- end }}
resp.Diagnostics.AddWarning("Подресурс уже существует", "Объект уже есть, выполняется усыновление: "+targetValue)
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
return
}
resp.Diagnostics.AddError("Подресурс уже существует", "Найден объект с таким именем: "+targetValue)
return
}
}
params := resources_core.CompactParams(map[string]string{
{{- range .CreateParams }}
{{- if and (gt .RefSvcId 0) (eq (ParamType .) "types.String") }}
"{{.Code}}": resolved{{ToCamel .Code}},
{{- else }}
"{{.Code}}": {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}},
{{- end }}
{{- 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())
}
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "{{.CreateOpName}}", params, operationTimeout)
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "{{.CreateOpName}}", params, operationTimeout)
}
if createErr != nil {
if targetValue != "" {
found, known, checkErr := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
if checkErr != nil {
resp.Diagnostics.AddError("Ошибка клиента", checkErr.Error())
return
}
if known && found {
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "{{.SubName}}", idParams))
{{- if .VaultUserPassword }}
applyPassword()
{{- end }}
resp.Diagnostics.AddWarning("Подресурс подтверждён в state_out", "Операция create вернула ошибку, но объект найден в state_out и принят в state: "+targetValue)
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
return
}
}
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(createErr) {
if targetValue != "" {
found, known, checkErr := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
if checkErr != nil {
resp.Diagnostics.AddError("Ошибка клиента", checkErr.Error())
return
}
if found || !known {
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "{{.SubName}}", idParams))
{{- if .VaultUserPassword }}
applyPassword()
{{- end }}
resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект принят в state")
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
return
}
}
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
return
}
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
return
}
if targetValue != "" {
found, known, err := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
if err != nil {
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
return
}
if known && !found {
resp.Diagnostics.AddError("Нарушена консистентность", "Операция create завершилась успешно, но объект не найден в state_out: "+targetValue)
return
}
}
{{- if .VaultUserPassword }}
// Пароль пользователя генерирует платформа: он лежит в секрете Vault родительского
// инстанса. Читаем его ЗДЕСЬ и кладём в свой выход, чтобы потребитель получил пароль
// в этом же apply. См. TOOLS/ARCHITECTURE.md → «Subresource-born secrets».
// Неудачное чтение create не роняет: значение останется null (known для Terraform).
applyPassword()
{{- end }}
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "{{.SubName}}", idParams))
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state {{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan {{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
{{- if .VaultUserPassword }}
// Пароль меняет платформа, а не пользователь: при modify его не трогаем и оставляем уже
// полученное значение из state (иначе Computed-атрибут станет null/unknown и Terraform
// ругнётся на несовместимый результат после apply).
if plan.Password.IsNull() || plan.Password.IsUnknown() {
var priorState {{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Model
if stateDiags := req.State.Get(ctx, &priorState); !stateDiags.HasError() {
plan.Password = priorState.Password
}
if plan.Password.IsNull() || plan.Password.IsUnknown() {
plan.Password = types.StringNull()
}
}
{{- end }}
{{- if .HasRefSvcParams }}
{{- range .ModifyParams }}
{{- if and (gt .RefSvcId 0) (eq (ParamType .) "types.String") }}
// Update для подресурса следует тому же правилу, что и Create:
// резолвим значение только в локальную переменную и не мутируем plan.
resolved{{ToCamel .Code}} := plan.{{ToCamel .Code}}.ValueString()
if !plan.{{ToCamel .Code}}.IsNull() && !plan.{{ToCamel .Code}}.IsUnknown() {
var err error
resolved{{ToCamel .Code}}, err = r.client.ResolveRefSvcParamValue(ctx, {{.RefSvcId}}, plan.{{ToCamel .Code}}.ValueString())
if err != nil {
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
return
}
}
{{- end }}
{{- end }}
{{- end }}
if "{{.ModifyOpName}}" == "" {
var state {{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
if plan.ID.IsNull() || plan.ID.IsUnknown() {
plan.ID = state.ID
}
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
return
}
instanceUID := strings.TrimSpace(plan.{{ToCamel .ServiceName}}ID.ValueString())
if instanceUID == "" {
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
return
}
params := resources_core.CompactParams(map[string]string{
{{- range .ModifyParams }}
{{- if and (gt .RefSvcId 0) (eq (ParamType .) "types.String") }}
"{{.Code}}": resolved{{ToCamel .Code}},
{{- else }}
"{{.Code}}": {{ParamFormat . (printf "plan.%s" (ToCamel .Code))}},
{{- end }}
{{- end }}
})
idParams := map[string]string{
{{- range .IdentityParams }}
"{{.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, "{{.ModifyOpName}}", params, operationTimeout); err != nil {
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
return
}
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "{{.SubName}}", idParams))
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state {{ToCamel (printf "%s_%s" .ServiceName .SubName)}}Model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
if !state.KeepOnDestroy.IsNull() && !state.KeepOnDestroy.IsUnknown() && state.KeepOnDestroy.ValueBool() {
resp.Diagnostics.AddWarning(
"Подресурс оставлен как есть, а не удалён",
"destroy: {{.ServiceName}}_{{.SubName}} (service_id={{.ServiceID}}) не изменялся в облаке и только убран из состояния (keep_on_destroy = true). Для полного удаления выставьте keep_on_destroy = false.",
)
return
}
{{- if .HasRefSvcParams }}
{{- range .SchemaParams }}
{{- if and (gt .RefSvcId 0) (eq (ParamType .) "types.String") }}
if !state.{{ToCamel .Code}}.IsNull() && !state.{{ToCamel .Code}}.IsUnknown() {
resolved{{ToCamel .Code}}, err := r.client.ResolveRefSvcParamValue(ctx, {{.RefSvcId}}, state.{{ToCamel .Code}}.ValueString())
if err != nil {
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
return
}
if resolved{{ToCamel .Code}} != "" && resolved{{ToCamel .Code}} != state.{{ToCamel .Code}}.ValueString() {
state.{{ToCamel .Code}} = types.StringValue(resolved{{ToCamel .Code}})
}
}
{{- end }}
{{- end }}
{{- end }}
if "{{.DeleteOpName}}" == "" {
return
}
instanceUID := strings.TrimSpace(state.{{ToCamel .ServiceName}}ID.ValueString())
if instanceUID == "" {
return
}
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
idParams := map[string]string{
{{- range .IdentityParams }}
"{{.Code}}": {{ParamFormat . (printf "state.%s" (ToCamel .Code))}},
{{- end }}
}
identityCodes := []string{
{{- range .IdentityParams }}
"{{.Code}}",
{{- end }}
}
listKey, idKey := resources_core.ResolveSubresourceStateKeys("{{.SubName}}", identityCodes)
targetValue := strings.TrimSpace(idParams[idKey])
if targetValue == "" {
for _, code := range identityCodes {
candidate := strings.TrimSpace(idParams[code])
if candidate == "" {
continue
}
targetValue = candidate
idKey = code
break
}
}
if targetValue != "" {
found, known, err := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
if err != nil {
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
return
}
if known && !found {
resp.Diagnostics.AddWarning("Подресурс не найден", "Объект уже удалён, пропускаем: "+targetValue)
return
}
}
params := resources_core.CompactParams(map[string]string{
{{- range .DeleteParams }}
"{{.Code}}": {{ParamFormat . (printf "state.%s" (ToCamel .Code))}},
{{- 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.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "{{.DeleteOpName}}", params, operationTimeout); err != nil {
if resources_core.IsSubresourceMissingError(err) {
if targetValue != "" {
found, known, checkErr := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
if checkErr != nil {
resp.Diagnostics.AddError("Ошибка клиента", checkErr.Error())
return
}
if known && found {
resp.Diagnostics.AddError("Нарушена консистентность", "Операция delete вернула not found, но объект всё ещё присутствует в state_out: "+targetValue)
return
}
}
if skipMissingOnDelete {
resp.Diagnostics.AddWarning("Подресурс не найден", "Операция delete вернула not found, объект отсутствует в state_out, удаление пропущено")
return
}
}
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
return
}
if targetValue != "" {
found, known, err := resources_core.FindSubresourceInStateOut(ctx, r.client, instanceUID, listKey, idKey, targetValue)
if err != nil {
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
return
}
if known && found {
resp.Diagnostics.AddError("Нарушена консистентность", "Операция delete завершилась успешно, но объект всё ещё присутствует в state_out: "+targetValue)
return
}
}
}
func (r *{{ToCamel (printf "%s_%s" .ServiceName .SubName)}}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
}
`