feat: full regeneration from API — 70→88 Go files, 42 YAMLs, +18 new resources (reconcile, backup, users, databases)
This commit is contained in:
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -29,7 +32,7 @@ type OpenwhiskModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Username types.String `tfsdk:"username"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
@@ -53,10 +56,10 @@ func (r *OpenwhiskResource) Metadata(ctx context.Context, req resource.MetadataR
|
||||
|
||||
func (r *OpenwhiskResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Ресурсная платформа Не изменяется после создания."},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"username": schema.StringAttribute{Required: true, MarkdownDescription: "Имя пользователя Openwhisk Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
@@ -108,26 +111,22 @@ func (r *OpenwhiskResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Username.IsNull() && !plan.Username.IsUnknown() && !state.Username.IsNull() && !state.Username.IsUnknown() {
|
||||
if plan.Username.ValueString() != state.Username.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Username.ValueString(), state.Username.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить username", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if config.Username.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "username is required.")
|
||||
return
|
||||
}
|
||||
if config.Username.IsNull() || config.Username.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "username is required.")
|
||||
if config.Username.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -139,11 +138,12 @@ func (r *OpenwhiskResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
params := map[int]string{
|
||||
64: resources_core.FormatString(config.Username),
|
||||
863: resources_core.FormatString(config.ResourceRealm),
|
||||
64: resources_core.FormatString(config.Username),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 100, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 100, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *OpenwhiskResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -152,27 +152,27 @@ func (r *OpenwhiskResource) Create(ctx context.Context, req resource.CreateReque
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if data.Username.IsNull() || data.Username.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "username is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 100, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 100, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
64: resources_core.FormatString(data.Username),
|
||||
863: resources_core.FormatString(data.ResourceRealm),
|
||||
64: resources_core.FormatString(data.Username),
|
||||
}
|
||||
|
||||
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, 100, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -190,7 +190,6 @@ func (r *OpenwhiskResource) Create(ctx context.Context, req resource.CreateReque
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "username", Field: "Username", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
@@ -232,7 +231,6 @@ func (r *OpenwhiskResource) Read(ctx context.Context, req resource.ReadRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "username", Field: "Username", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
@@ -283,6 +281,9 @@ func (r *OpenwhiskResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -299,7 +300,6 @@ func (r *OpenwhiskResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "username", Field: "Username", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
@@ -319,16 +319,15 @@ func (r *OpenwhiskResource) Delete(ctx context.Context, req resource.DeleteReque
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,7 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,7 @@ type DnszoneModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
ZoneEmail types.String `tfsdk:"zone_email"`
|
||||
ZoneKind types.String `tfsdk:"zone_kind"`
|
||||
@@ -61,9 +65,10 @@ func (r *DnszoneResource) Metadata(ctx context.Context, req resource.MetadataReq
|
||||
|
||||
func (r *DnszoneResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"zone_email": schema.StringAttribute{Required: true, MarkdownDescription: "Контактный e-mail Не изменяется после создания."},
|
||||
"zone_kind": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("NATIVE"), MarkdownDescription: "Тип зоны Не изменяется после создания."},
|
||||
@@ -124,73 +129,109 @@ func (r *DnszoneResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ZoneEmail.IsNull() && !plan.ZoneEmail.IsUnknown() && !state.ZoneEmail.IsNull() && !state.ZoneEmail.IsUnknown() {
|
||||
if plan.ZoneEmail.ValueString() != state.ZoneEmail.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ZoneEmail.ValueString(), state.ZoneEmail.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить zone_email", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ZoneKind.IsNull() && !plan.ZoneKind.IsUnknown() && !state.ZoneKind.IsNull() && !state.ZoneKind.IsUnknown() {
|
||||
if plan.ZoneKind.ValueString() != state.ZoneKind.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ZoneKind.ValueString(), state.ZoneKind.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить zone_kind", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ZoneMastersForSlave.IsNull() && !plan.ZoneMastersForSlave.IsUnknown() && !state.ZoneMastersForSlave.IsNull() && !state.ZoneMastersForSlave.IsUnknown() {
|
||||
if plan.ZoneMastersForSlave.ValueString() != state.ZoneMastersForSlave.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ZoneMastersForSlave.ValueString(), state.ZoneMastersForSlave.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить zone_masters_for_slave", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ZoneName.IsNull() && !plan.ZoneName.IsUnknown() && !state.ZoneName.IsNull() && !state.ZoneName.IsUnknown() {
|
||||
if plan.ZoneName.ValueString() != state.ZoneName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ZoneName.ValueString(), state.ZoneName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить zone_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ZoneTimeToExpire.IsNull() && !plan.ZoneTimeToExpire.IsUnknown() && !state.ZoneTimeToExpire.IsNull() && !state.ZoneTimeToExpire.IsUnknown() {
|
||||
if plan.ZoneTimeToExpire.ValueString() != state.ZoneTimeToExpire.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ZoneTimeToExpire.ValueString(), state.ZoneTimeToExpire.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить zone_time_to_expire", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ZoneTimeToLive.IsNull() && !plan.ZoneTimeToLive.IsUnknown() && !state.ZoneTimeToLive.IsNull() && !state.ZoneTimeToLive.IsUnknown() {
|
||||
if plan.ZoneTimeToLive.ValueString() != state.ZoneTimeToLive.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ZoneTimeToLive.ValueString(), state.ZoneTimeToLive.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить zone_time_to_live", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ZoneTimeToRefresh.IsNull() && !plan.ZoneTimeToRefresh.IsUnknown() && !state.ZoneTimeToRefresh.IsNull() && !state.ZoneTimeToRefresh.IsUnknown() {
|
||||
if plan.ZoneTimeToRefresh.ValueString() != state.ZoneTimeToRefresh.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ZoneTimeToRefresh.ValueString(), state.ZoneTimeToRefresh.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить zone_time_to_refresh", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ZoneTimeToRetry.IsNull() && !plan.ZoneTimeToRetry.IsUnknown() && !state.ZoneTimeToRetry.IsNull() && !state.ZoneTimeToRetry.IsUnknown() {
|
||||
if plan.ZoneTimeToRetry.ValueString() != state.ZoneTimeToRetry.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ZoneTimeToRetry.ValueString(), state.ZoneTimeToRetry.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить zone_time_to_retry", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ZoneEmail.IsNull() || config.ZoneEmail.IsUnknown() {
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ZoneEmail.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "zone_email is required.")
|
||||
return
|
||||
}
|
||||
if config.ZoneName.IsNull() || config.ZoneName.IsUnknown() {
|
||||
if config.ZoneEmail.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ZoneName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "zone_name is required.")
|
||||
return
|
||||
}
|
||||
if config.ZoneName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -210,8 +251,10 @@ func (r *DnszoneResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
73: resources_core.FormatString(config.ZoneTimeToLive),
|
||||
284: resources_core.FormatString(config.ResourceRealm),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 110, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 110, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *DnszoneResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -234,7 +277,9 @@ func (r *DnszoneResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 110, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 110, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
66: resources_core.FormatString(data.ZoneEmail),
|
||||
@@ -252,6 +297,9 @@ func (r *DnszoneResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
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, 110, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -376,6 +424,9 @@ func (r *DnszoneResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -419,16 +470,15 @@ func (r *DnszoneResource) Delete(ctx context.Context, req resource.DeleteRequest
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: dnsrecord
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &DnsrecordReconcileResource{}
|
||||
|
||||
// DnsrecordReconcileResource triggers a one-time action.
|
||||
type DnsrecordReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type DnsrecordReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
DnsrecordID types.String `tfsdk:"dnsrecord_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewDnsrecordReconcileResource() resource.Resource {
|
||||
return &DnsrecordReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *DnsrecordReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_dnsrecord_reconcile"
|
||||
}
|
||||
|
||||
func (r *DnsrecordReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"dnsrecord_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *DnsrecordReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan DnsrecordReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.DnsrecordID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *DnsrecordReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state DnsrecordReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *DnsrecordReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan DnsrecordReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.DnsrecordID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *DnsrecordReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *DnsrecordReconcileResource) 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
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,7 @@ type DnsrecordModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
RecordInput types.String `tfsdk:"record_input"`
|
||||
RecordName types.String `tfsdk:"record_name"`
|
||||
RecordTTL types.Int64 `tfsdk:"record_t_t_l"`
|
||||
@@ -57,14 +61,15 @@ func (r *DnsrecordResource) Metadata(ctx context.Context, req resource.MetadataR
|
||||
|
||||
func (r *DnsrecordResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"record_input": schema.StringAttribute{Required: true, MarkdownDescription: "Значение. Необходима валидация, в зависимости от типа записи. Должно быть заменено на UUID-внешнийIP"},
|
||||
"record_name": schema.StringAttribute{Required: true, MarkdownDescription: "Имя записи. Имя зоны в конце проставляется автоматически Не изменяется после создания."},
|
||||
"record_t_t_l": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(3600), MarkdownDescription: "Time-to-Leave записи. Раз в какое время запись будет обновлена Не изменяется после создания."},
|
||||
"record_type": schema.StringAttribute{Required: true, MarkdownDescription: "Тип DNS-записи Не изменяется после создания."},
|
||||
"zone_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UUID экземпляра услуги DNS-зоны Не изменяется после создания."},
|
||||
"zone_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UUID экземпляра услуги DNS-зоны Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -109,22 +114,11 @@ func (r *DnsrecordResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.ZoneUid.IsNull() && !plan.ZoneUid.IsUnknown() {
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 110, plan.ZoneUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve zone_uid", err.Error())
|
||||
} else if resolvedZoneUid != "" && resolvedZoneUid != plan.ZoneUid.ValueString() {
|
||||
plan.ZoneUid = types.StringValue(resolvedZoneUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -132,7 +126,10 @@ func (r *DnsrecordResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
}
|
||||
}
|
||||
if !plan.RecordName.IsNull() && !plan.RecordName.IsUnknown() && !state.RecordName.IsNull() && !state.RecordName.IsUnknown() {
|
||||
if plan.RecordName.ValueString() != state.RecordName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.RecordName.ValueString(), state.RecordName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить record_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -144,35 +141,53 @@ func (r *DnsrecordResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
}
|
||||
}
|
||||
if !plan.RecordType.IsNull() && !plan.RecordType.IsUnknown() && !state.RecordType.IsNull() && !state.RecordType.IsUnknown() {
|
||||
if plan.RecordType.ValueString() != state.RecordType.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.RecordType.ValueString(), state.RecordType.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить record_type", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ZoneUid.IsNull() && !plan.ZoneUid.IsUnknown() && !state.ZoneUid.IsNull() && !state.ZoneUid.IsUnknown() {
|
||||
if plan.ZoneUid.ValueString() != state.ZoneUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ZoneUid.ValueString(), state.ZoneUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить zone_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.RecordInput.IsNull() || config.RecordInput.IsUnknown() {
|
||||
if config.RecordInput.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "record_input is required.")
|
||||
return
|
||||
}
|
||||
if config.RecordName.IsNull() || config.RecordName.IsUnknown() {
|
||||
if config.RecordInput.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.RecordName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "record_name is required.")
|
||||
return
|
||||
}
|
||||
if config.RecordType.IsNull() || config.RecordType.IsUnknown() {
|
||||
if config.RecordName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.RecordType.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "record_type is required.")
|
||||
return
|
||||
}
|
||||
if config.ZoneUid.IsNull() || config.ZoneUid.IsUnknown() {
|
||||
if config.RecordType.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ZoneUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "zone_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.ZoneUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -182,7 +197,7 @@ func (r *DnsrecordResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
if !config.ZoneUid.IsNull() && !config.ZoneUid.IsUnknown() {
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 110, config.ZoneUid.ValueString())
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 109, config.ZoneUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve zone_uid", err.Error())
|
||||
} else if resolvedZoneUid != "" && resolvedZoneUid != config.ZoneUid.ValueString() {
|
||||
@@ -196,8 +211,10 @@ func (r *DnsrecordResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
78: resources_core.FormatString(config.RecordInput),
|
||||
450: resources_core.FormatInt64(config.RecordTTL),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 111, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 111, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *DnsrecordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -222,8 +239,26 @@ func (r *DnsrecordResource) Create(ctx context.Context, req resource.CreateReque
|
||||
resp.Diagnostics.AddError("Missing required attribute", "zone_uid is required.")
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalZoneUid := data.ZoneUid
|
||||
if !data.ZoneUid.IsNull() && !data.ZoneUid.IsUnknown() {
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 110, data.ZoneUid.ValueString())
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 109, data.ZoneUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -234,7 +269,9 @@ func (r *DnsrecordResource) Create(ctx context.Context, req resource.CreateReque
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 111, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 111, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
75: resources_core.FormatString(data.ZoneUid),
|
||||
@@ -248,6 +285,9 @@ func (r *DnsrecordResource) Create(ctx context.Context, req resource.CreateReque
|
||||
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, 111, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -276,13 +316,23 @@ func (r *DnsrecordResource) Create(ctx context.Context, req resource.CreateReque
|
||||
return
|
||||
}
|
||||
if !state.ZoneUid.IsNull() && !state.ZoneUid.IsUnknown() {
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 110, state.ZoneUid.ValueString())
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 109, state.ZoneUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve zone_uid", err.Error())
|
||||
} else if resolvedZoneUid != "" && resolvedZoneUid != state.ZoneUid.ValueString() {
|
||||
state.ZoneUid = types.StringValue(resolvedZoneUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalZoneUid.IsNull() && !originalZoneUid.IsUnknown() && !state.ZoneUid.IsNull() && !state.ZoneUid.IsUnknown() {
|
||||
if strings.EqualFold(state.ZoneUid.ValueString(), originalZoneUid.ValueString()) {
|
||||
state.ZoneUid = originalZoneUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -329,13 +379,23 @@ func (r *DnsrecordResource) Read(ctx context.Context, req resource.ReadRequest,
|
||||
return
|
||||
}
|
||||
if !newState.ZoneUid.IsNull() && !newState.ZoneUid.IsUnknown() {
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 110, newState.ZoneUid.ValueString())
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 109, newState.ZoneUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve zone_uid", err.Error())
|
||||
} else if resolvedZoneUid != "" && resolvedZoneUid != newState.ZoneUid.ValueString() {
|
||||
newState.ZoneUid = types.StringValue(resolvedZoneUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.ZoneUid.IsNull() && !state.ZoneUid.IsUnknown() && !newState.ZoneUid.IsNull() && !newState.ZoneUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.ZoneUid.ValueString(), state.ZoneUid.ValueString()) {
|
||||
newState.ZoneUid = state.ZoneUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -363,7 +423,7 @@ func (r *DnsrecordResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
if plan.RecordInput.IsNull() != state.RecordInput.IsNull() || plan.RecordInput.IsUnknown() != state.RecordInput.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.RecordInput.IsNull() && !plan.RecordInput.IsUnknown() && !state.RecordInput.IsNull() && !state.RecordInput.IsUnknown() {
|
||||
if plan.RecordInput.ValueString() != state.RecordInput.ValueString() {
|
||||
if !strings.EqualFold(plan.RecordInput.ValueString(), state.RecordInput.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -391,6 +451,9 @@ func (r *DnsrecordResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -418,13 +481,22 @@ func (r *DnsrecordResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
return
|
||||
}
|
||||
if !state.ZoneUid.IsNull() && !state.ZoneUid.IsUnknown() {
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 110, state.ZoneUid.ValueString())
|
||||
resolvedZoneUid, err := r.client.ResolveRefSvcParamValue(ctx, 109, state.ZoneUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve zone_uid", err.Error())
|
||||
} else if resolvedZoneUid != "" && resolvedZoneUid != state.ZoneUid.ValueString() {
|
||||
state.ZoneUid = types.StringValue(resolvedZoneUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.ZoneUid.IsNull() && !plan.ZoneUid.IsUnknown() && !state.ZoneUid.IsNull() && !state.ZoneUid.IsUnknown() {
|
||||
if strings.EqualFold(state.ZoneUid.ValueString(), plan.ZoneUid.ValueString()) {
|
||||
state.ZoneUid = plan.ZoneUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -438,16 +510,15 @@ func (r *DnsrecordResource) Delete(ctx context.Context, req resource.DeleteReque
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -29,6 +32,7 @@ type TenantModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
GrafanaOrgName types.String `tfsdk:"grafana_org_name"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
@@ -53,9 +57,10 @@ func (r *TenantResource) Metadata(ctx context.Context, req resource.MetadataRequ
|
||||
|
||||
func (r *TenantResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"grafana_org_name": schema.StringAttribute{Required: true, MarkdownDescription: "WZ-Код клиента Не изменяется после создания."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
@@ -109,27 +114,39 @@ func (r *TenantResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
}
|
||||
}
|
||||
if !plan.GrafanaOrgName.IsNull() && !plan.GrafanaOrgName.IsUnknown() && !state.GrafanaOrgName.IsNull() && !state.GrafanaOrgName.IsUnknown() {
|
||||
if plan.GrafanaOrgName.ValueString() != state.GrafanaOrgName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.GrafanaOrgName.ValueString(), state.GrafanaOrgName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить grafana_org_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.GrafanaOrgName.IsNull() || config.GrafanaOrgName.IsUnknown() {
|
||||
if config.GrafanaOrgName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "grafana_org_name is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.GrafanaOrgName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -142,8 +159,10 @@ func (r *TenantResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
239: resources_core.FormatString(config.GrafanaOrgName),
|
||||
240: resources_core.FormatString(config.ResourceRealm),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 112, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 112, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *TenantResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -162,7 +181,9 @@ func (r *TenantResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 112, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 112, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
239: resources_core.FormatString(data.GrafanaOrgName),
|
||||
@@ -173,6 +194,9 @@ func (r *TenantResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
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, 112, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -283,6 +307,9 @@ func (r *TenantResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -319,16 +346,15 @@ func (r *TenantResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,6 +34,7 @@ type VcComplexModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
CpuAllocated types.String `tfsdk:"cpu_allocated"`
|
||||
CpuGuaranteed types.Int64 `tfsdk:"cpu_guaranteed"`
|
||||
IpSpaceName types.String `tfsdk:"ip_space_name"`
|
||||
@@ -68,9 +72,10 @@ func (r *VcComplexResource) Metadata(ctx context.Context, req resource.MetadataR
|
||||
|
||||
func (r *VcComplexResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"cpu_allocated": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("1"), MarkdownDescription: "шт Не изменяется после создания."},
|
||||
"cpu_guaranteed": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(20), MarkdownDescription: "Не изменяется после создания."},
|
||||
"ip_space_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("internet-ipv4-v1"), MarkdownDescription: "Имя ip Space для внешнего IP. Целесообразнее брать самый дешёвый, так как это vip для исходящего трафика. Необходимо задавать если включен needExternalAddressSNAT Не изменяется после создания."},
|
||||
@@ -80,7 +85,7 @@ func (r *VcComplexResource) Schema(ctx context.Context, req resource.SchemaReque
|
||||
"need_external_address_s_n_a_t": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true), MarkdownDescription: "Выделяет внешний IP на Edge. Используется для SNAT всего трафика Не изменяется после создания."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"segroup_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("SEGROUP-SANDBOX-CL1-SHARED-01"), MarkdownDescription: "Наименование segroup, в котором будут выделяться пулы на AVI Не изменяется после создания."},
|
||||
"storage_config": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{\"name\": \"\", \"size\": 1024}]"), MarkdownDescription: "Описывается как массив объектов, где описывается имя политики и её размер Не изменяется после создания."},
|
||||
"storage_config": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{\"name\": \"\", \"size\": 1024}]"), MarkdownDescription: "Описывается как массив объектов, где описывается имя политики и её размер Не изменяется после создания.", PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()}},
|
||||
"v_i_p_count": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(0), MarkdownDescription: "ш Не изменяется после создания."},
|
||||
"vapp_name": schema.StringAttribute{Required: true, MarkdownDescription: "Имя vApp Не изменяется после создания."},
|
||||
"vdc_network_pool": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("nsxt-sandbox-geneve-np"), MarkdownDescription: "Не изменяется после создания."},
|
||||
@@ -137,7 +142,10 @@ func (r *VcComplexResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
}
|
||||
}
|
||||
if !plan.CpuAllocated.IsNull() && !plan.CpuAllocated.IsUnknown() && !state.CpuAllocated.IsNull() && !state.CpuAllocated.IsUnknown() {
|
||||
if plan.CpuAllocated.ValueString() != state.CpuAllocated.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.CpuAllocated.ValueString(), state.CpuAllocated.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить cpu_allocated", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -149,7 +157,10 @@ func (r *VcComplexResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
}
|
||||
}
|
||||
if !plan.IpSpaceName.IsNull() && !plan.IpSpaceName.IsUnknown() && !state.IpSpaceName.IsNull() && !state.IpSpaceName.IsUnknown() {
|
||||
if plan.IpSpaceName.ValueString() != state.IpSpaceName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.IpSpaceName.ValueString(), state.IpSpaceName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить ip_space_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -179,19 +190,28 @@ func (r *VcComplexResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.SegroupName.IsNull() && !plan.SegroupName.IsUnknown() && !state.SegroupName.IsNull() && !state.SegroupName.IsUnknown() {
|
||||
if plan.SegroupName.ValueString() != state.SegroupName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.SegroupName.ValueString(), state.SegroupName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить segroup_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.StorageConfig.IsNull() && !plan.StorageConfig.IsUnknown() && !state.StorageConfig.IsNull() && !state.StorageConfig.IsUnknown() {
|
||||
if plan.StorageConfig.ValueString() != state.StorageConfig.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StorageConfig.ValueString(), state.StorageConfig.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить storage_config", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -203,39 +223,57 @@ func (r *VcComplexResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
}
|
||||
}
|
||||
if !plan.VappName.IsNull() && !plan.VappName.IsUnknown() && !state.VappName.IsNull() && !state.VappName.IsUnknown() {
|
||||
if plan.VappName.ValueString() != state.VappName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VappName.ValueString(), state.VappName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vapp_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VdcNetworkPool.IsNull() && !plan.VdcNetworkPool.IsUnknown() && !state.VdcNetworkPool.IsNull() && !state.VdcNetworkPool.IsUnknown() {
|
||||
if plan.VdcNetworkPool.ValueString() != state.VdcNetworkPool.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VdcNetworkPool.ValueString(), state.VdcNetworkPool.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vdc_network_pool", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VdcProviderGateway.IsNull() && !plan.VdcProviderGateway.IsUnknown() && !state.VdcProviderGateway.IsNull() && !state.VdcProviderGateway.IsUnknown() {
|
||||
if plan.VdcProviderGateway.ValueString() != state.VdcProviderGateway.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VdcProviderGateway.ValueString(), state.VdcProviderGateway.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vdc_provider_gateway", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VirtualServicesCount.IsNull() && !plan.VirtualServicesCount.IsUnknown() && !state.VirtualServicesCount.IsNull() && !state.VirtualServicesCount.IsUnknown() {
|
||||
if plan.VirtualServicesCount.ValueString() != state.VirtualServicesCount.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VirtualServicesCount.ValueString(), state.VirtualServicesCount.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить virtual_services_count", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.VappName.IsNull() || config.VappName.IsUnknown() {
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.VappName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vapp_name is required.")
|
||||
return
|
||||
}
|
||||
if config.VappName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -261,8 +299,10 @@ func (r *VcComplexResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
567: resources_core.FormatInt64(config.MemAllocated),
|
||||
568: resources_core.FormatInt64(config.VIPCount),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 113, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 113, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcComplexResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -281,7 +321,9 @@ func (r *VcComplexResource) Create(ctx context.Context, req resource.CreateReque
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 113, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 113, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
332: resources_core.FormatString(data.ResourceRealm),
|
||||
@@ -305,6 +347,9 @@ func (r *VcComplexResource) Create(ctx context.Context, req resource.CreateReque
|
||||
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, 113, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -441,6 +486,9 @@ func (r *VcComplexResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -490,16 +538,15 @@ func (r *VcComplexResource) Delete(ctx context.Context, req resource.DeleteReque
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -29,6 +31,7 @@ type GiteaComplexModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
@@ -51,9 +54,10 @@ func (r *GiteaComplexResource) Metadata(ctx context.Context, req resource.Metada
|
||||
|
||||
func (r *GiteaComplexResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -115,8 +119,10 @@ func (r *GiteaComplexResource) ModifyPlan(ctx context.Context, req resource.Modi
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
params := map[int]string{}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 114, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 114, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *GiteaComplexResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -127,7 +133,9 @@ func (r *GiteaComplexResource) Create(ctx context.Context, req resource.CreateRe
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 114, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 114, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{}
|
||||
|
||||
@@ -135,6 +143,9 @@ func (r *GiteaComplexResource) Create(ctx context.Context, req resource.CreateRe
|
||||
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, 114, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -239,6 +250,9 @@ func (r *GiteaComplexResource) Update(ctx context.Context, req resource.UpdateRe
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -272,16 +286,15 @@ func (r *GiteaComplexResource) Delete(ctx context.Context, req resource.DeleteRe
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: mariadb
|
||||
// Subresource: database
|
||||
|
||||
var _ resource.Resource = &MariadbDatabaseResource{}
|
||||
|
||||
// MariadbDatabaseResource manages a subresource via operations.
|
||||
type MariadbDatabaseResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type MariadbDatabaseModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
MariadbID types.String `tfsdk:"mariadb_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
DbName types.String `tfsdk:"db_name"`
|
||||
}
|
||||
|
||||
func NewMariadbDatabaseResource() resource.Resource {
|
||||
return &MariadbDatabaseResource{}
|
||||
}
|
||||
|
||||
func (r *MariadbDatabaseResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_mariadb_database"
|
||||
}
|
||||
|
||||
func (r *MariadbDatabaseResource) 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()}},
|
||||
"mariadb_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(false)},
|
||||
"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."},
|
||||
"db_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("db-"), MarkdownDescription: "Пример: `db01` Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *MariadbDatabaseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan MariadbDatabaseModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.MariadbID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"dbName",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("database", 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, "database", idParams))
|
||||
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{
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
})
|
||||
|
||||
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, "create_database", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_database", 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, "database", idParams))
|
||||
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 known && found {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "database", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект подтверждён в state_out, выполняется усыновление")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "database", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *MariadbDatabaseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state MariadbDatabaseModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *MariadbDatabaseResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan MariadbDatabaseModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
var state MariadbDatabaseModel
|
||||
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.MariadbID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
}
|
||||
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "database", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *MariadbDatabaseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state MariadbDatabaseModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "delete_database" == "" {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(state.MariadbID.ValueString())
|
||||
if instanceUID == "" {
|
||||
return
|
||||
}
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"dbName": resources_core.FormatString(state.DbName),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"dbName",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("database", 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 skipMissingOnDelete {
|
||||
resp.Diagnostics.AddWarning("Подресурс не найден", "Объект отсутствует и будет пропущен: "+targetValue)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Подресурс не найден", "Объект отсутствует: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{
|
||||
"dbName": resources_core.FormatString(state.DbName),
|
||||
})
|
||||
|
||||
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, "delete_database", 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 *MariadbDatabaseResource) 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
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: mariadb
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &MariadbReconcileResource{}
|
||||
|
||||
// MariadbReconcileResource triggers a one-time action.
|
||||
type MariadbReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type MariadbReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
MariadbID types.String `tfsdk:"mariadb_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewMariadbReconcileResource() resource.Resource {
|
||||
return &MariadbReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *MariadbReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_mariadb_reconcile"
|
||||
}
|
||||
|
||||
func (r *MariadbReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"mariadb_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *MariadbReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan MariadbReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.MariadbID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *MariadbReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state MariadbReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *MariadbReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan MariadbReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.MariadbID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *MariadbReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *MariadbReconcileResource) 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
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,8 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -28,33 +29,26 @@ type MariadbResource struct {
|
||||
}
|
||||
|
||||
type MariadbModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
AppVersion types.String `tfsdk:"app_version"`
|
||||
AutoScale types.Bool `tfsdk:"auto_scale"`
|
||||
AutoScalePercentage types.Int64 `tfsdk:"auto_scale_percentage"`
|
||||
AutoScaleQuotaGb types.Int64 `tfsdk:"auto_scale_quota_gb"`
|
||||
AutoScaleTechWindow types.Int64 `tfsdk:"auto_scale_tech_window"`
|
||||
ExtBACKUPSCHEDULE types.String `tfsdk:"ext__b_a_c_k_u_p__s_c_h_e_d_u_l_e"`
|
||||
IpSpaceNameMaster types.String `tfsdk:"ip_space_name_master"`
|
||||
NeedExternalAddressMaster types.Bool `tfsdk:"need_external_address_master"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
S3Uid types.String `tfsdk:"s3_uid"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
AutoscaleConfiguration types.String `tfsdk:"autoscale_configuration"`
|
||||
BackupConfiguration types.String `tfsdk:"backup_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
MariadbConfiguration types.String `tfsdk:"mariadb_configuration"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
}
|
||||
|
||||
func NewMariadbResource() resource.Resource {
|
||||
@@ -67,33 +61,26 @@ func (r *MariadbResource) Metadata(ctx context.Context, req resource.MetadataReq
|
||||
|
||||
func (r *MariadbResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"app_version": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("9.4.0"), MarkdownDescription: "Версия MySQL Не изменяется после создания."},
|
||||
"auto_scale": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Включает автоскейлинг PV. При включении параметра необходимо настроить autoScalePercentage и autoScaleTechWindow. Алерт при срабатывании вызывает modify у текущей услуги"},
|
||||
"auto_scale_percentage": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(10), MarkdownDescription: "Требует включения autoScale. Позволяет указать в процентах расширение от текущего макс объема"},
|
||||
"auto_scale_quota_gb": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Квота для autoScale"},
|
||||
"auto_scale_tech_window": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(0), MarkdownDescription: "Требует включения autoScale. Окно обновления (пока не реализовано). Указывается час в виде Integer (0 / 5 / 12 / 23)"},
|
||||
"ext__b_a_c_k_u_p__s_c_h_e_d_u_l_e": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("0 * * * *"), MarkdownDescription: "Не обязательный. Время запуска бекапа"},
|
||||
"ip_space_name_master": schema.StringAttribute{Optional: true, MarkdownDescription: "Имя ipSpace для публикации VIP master. Не может быть пустым, если включен needExternalAddressMaster"},
|
||||
"need_external_address_master": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Выделяет внешний (белый) IP для доступа к master"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Квота ядра пода (milicores)"},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Квота диска (Указывается в GIGAbytes)"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1024), MarkdownDescription: "Квота памяти пода (MBytes)"},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"s3_uid": schema.StringAttribute{Required: true, MarkdownDescription: "Экземпляр (учетная запись) S3 для резервного копирования. Резервное копирование обязательно Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
"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."},
|
||||
"access_configuration": schema.StringAttribute{Required: true},
|
||||
"autoscale_configuration": schema.StringAttribute{Required: true},
|
||||
"backup_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"mariadb_configuration": schema.StringAttribute{Required: true},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
}
|
||||
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
@@ -128,54 +115,63 @@ func (r *MariadbResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.S3Uid.IsNull() && !plan.S3Uid.IsUnknown() {
|
||||
resolvedS3Uid, err := r.client.ResolveRefSvcParamValue(ctx, 12, plan.S3Uid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve s3_uid", err.Error())
|
||||
} else if resolvedS3Uid != "" && resolvedS3Uid != plan.S3Uid.ValueString() {
|
||||
plan.S3Uid = types.StringValue(resolvedS3Uid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
if !plan.AppVersion.IsNull() && !plan.AppVersion.IsUnknown() && !state.AppVersion.IsNull() && !state.AppVersion.IsUnknown() {
|
||||
if plan.AppVersion.ValueString() != state.AppVersion.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить app_version", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.S3Uid.IsNull() && !plan.S3Uid.IsUnknown() && !state.S3Uid.IsNull() && !state.S3Uid.IsUnknown() {
|
||||
if plan.S3Uid.ValueString() != state.S3Uid.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить s3_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.S3Uid.IsNull() || config.S3Uid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "s3_uid is required.")
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.AutoscaleConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "autoscale_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AutoscaleConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.BackupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "backup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.BackupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.MariadbConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "mariadb_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.MariadbConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -186,32 +182,18 @@ func (r *MariadbResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
if !config.AdoptExistingOnCreate.IsNull() && !config.AdoptExistingOnCreate.IsUnknown() {
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
if !config.S3Uid.IsNull() && !config.S3Uid.IsUnknown() {
|
||||
resolvedS3Uid, err := r.client.ResolveRefSvcParamValue(ctx, 12, config.S3Uid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve s3_uid", err.Error())
|
||||
} else if resolvedS3Uid != "" && resolvedS3Uid != config.S3Uid.ValueString() {
|
||||
config.S3Uid = types.StringValue(resolvedS3Uid)
|
||||
}
|
||||
}
|
||||
params := map[int]string{
|
||||
422: resources_core.FormatString(config.ResourceRealm),
|
||||
424: resources_core.FormatInt64(config.ResourceCPU),
|
||||
425: resources_core.FormatInt64(config.ResourceMemory),
|
||||
426: resources_core.FormatInt64(config.ResourceDisk),
|
||||
427: resources_core.FormatInt64(config.ResourceInstances),
|
||||
428: resources_core.FormatBool(config.NeedExternalAddressMaster),
|
||||
429: resources_core.FormatString(config.IpSpaceNameMaster),
|
||||
430: resources_core.FormatString(config.ExtBACKUPSCHEDULE),
|
||||
431: resources_core.FormatString(config.AppVersion),
|
||||
432: resources_core.FormatBool(config.AutoScale),
|
||||
433: resources_core.FormatInt64(config.AutoScalePercentage),
|
||||
434: resources_core.FormatInt64(config.AutoScaleTechWindow),
|
||||
435: resources_core.FormatInt64(config.AutoScaleQuotaGb),
|
||||
459: resources_core.FormatString(config.S3Uid),
|
||||
877: resources_core.FormatString(config.StartupConfiguration),
|
||||
878: resources_core.FormatString(config.ClusterConfiguration),
|
||||
879: resources_core.FormatString(config.AccessConfiguration),
|
||||
880: resources_core.FormatString(config.MariadbConfiguration),
|
||||
881: resources_core.FormatString(config.BackupConfiguration),
|
||||
882: resources_core.FormatString(config.AutoscaleConfiguration),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 115, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 115, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *MariadbResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -220,39 +202,52 @@ func (r *MariadbResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.S3Uid.IsNull() || data.S3Uid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "s3_uid is required.")
|
||||
if data.AutoscaleConfiguration.IsNull() || data.AutoscaleConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "autoscale_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.BackupConfiguration.IsNull() || data.BackupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "backup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.MariadbConfiguration.IsNull() || data.MariadbConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "mariadb_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 115, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 115, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
422: resources_core.FormatString(data.ResourceRealm),
|
||||
424: resources_core.FormatInt64(data.ResourceCPU),
|
||||
425: resources_core.FormatInt64(data.ResourceMemory),
|
||||
426: resources_core.FormatInt64(data.ResourceDisk),
|
||||
427: resources_core.FormatInt64(data.ResourceInstances),
|
||||
428: resources_core.FormatBool(data.NeedExternalAddressMaster),
|
||||
429: resources_core.FormatString(data.IpSpaceNameMaster),
|
||||
430: resources_core.FormatString(data.ExtBACKUPSCHEDULE),
|
||||
431: resources_core.FormatString(data.AppVersion),
|
||||
432: resources_core.FormatBool(data.AutoScale),
|
||||
433: resources_core.FormatInt64(data.AutoScalePercentage),
|
||||
434: resources_core.FormatInt64(data.AutoScaleTechWindow),
|
||||
435: resources_core.FormatInt64(data.AutoScaleQuotaGb),
|
||||
459: resources_core.FormatString(data.S3Uid),
|
||||
877: resources_core.FormatString(data.StartupConfiguration),
|
||||
878: resources_core.FormatString(data.ClusterConfiguration),
|
||||
879: resources_core.FormatString(data.AccessConfiguration),
|
||||
880: resources_core.FormatString(data.MariadbConfiguration),
|
||||
881: resources_core.FormatString(data.BackupConfiguration),
|
||||
882: resources_core.FormatString(data.AutoscaleConfiguration),
|
||||
}
|
||||
|
||||
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, 115, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -270,19 +265,12 @@ func (r *MariadbResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "autoScale", Field: "AutoScale", Type: "bool"},
|
||||
{Code: "autoScalePercentage", Field: "AutoScalePercentage", Type: "int64"},
|
||||
{Code: "autoScaleQuotaGb", Field: "AutoScaleQuotaGb", Type: "int64"},
|
||||
{Code: "autoScaleTechWindow", Field: "AutoScaleTechWindow", Type: "int64"},
|
||||
{Code: "ext_BACKUP_SCHEDULE", Field: "ExtBACKUPSCHEDULE", Type: "string"},
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "autoscaleConfiguration", Field: "AutoscaleConfiguration", Type: "string"},
|
||||
{Code: "backupConfiguration", Field: "BackupConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "mariadbConfiguration", Field: "MariadbConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -323,19 +311,12 @@ func (r *MariadbResource) Read(ctx context.Context, req resource.ReadRequest, re
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "autoScale", Field: "AutoScale", Type: "bool"},
|
||||
{Code: "autoScalePercentage", Field: "AutoScalePercentage", Type: "int64"},
|
||||
{Code: "autoScaleQuotaGb", Field: "AutoScaleQuotaGb", Type: "int64"},
|
||||
{Code: "autoScaleTechWindow", Field: "AutoScaleTechWindow", Type: "int64"},
|
||||
{Code: "ext_BACKUP_SCHEDULE", Field: "ExtBACKUPSCHEDULE", Type: "string"},
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "autoscaleConfiguration", Field: "AutoscaleConfiguration", Type: "string"},
|
||||
{Code: "backupConfiguration", Field: "BackupConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "mariadbConfiguration", Field: "MariadbConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -365,100 +346,46 @@ func (r *MariadbResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPU.IsNull() != state.ResourceCPU.IsNull() || plan.ResourceCPU.IsUnknown() != state.ResourceCPU.IsUnknown() {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemory.IsNull() != state.ResourceMemory.IsNull() || plan.ResourceMemory.IsUnknown() != state.ResourceMemory.IsUnknown() {
|
||||
if plan.AccessConfiguration.IsNull() != state.AccessConfiguration.IsNull() || plan.AccessConfiguration.IsUnknown() != state.AccessConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
} else if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstances.IsNull() != state.ResourceInstances.IsNull() || plan.ResourceInstances.IsUnknown() != state.ResourceInstances.IsUnknown() {
|
||||
if plan.MariadbConfiguration.IsNull() != state.MariadbConfiguration.IsNull() || plan.MariadbConfiguration.IsUnknown() != state.MariadbConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
} else if !plan.MariadbConfiguration.IsNull() && !plan.MariadbConfiguration.IsUnknown() && !state.MariadbConfiguration.IsNull() && !state.MariadbConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.MariadbConfiguration.ValueString(), state.MariadbConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceDisk.IsNull() != state.ResourceDisk.IsNull() || plan.ResourceDisk.IsUnknown() != state.ResourceDisk.IsUnknown() {
|
||||
if plan.BackupConfiguration.IsNull() != state.BackupConfiguration.IsNull() || plan.BackupConfiguration.IsUnknown() != state.BackupConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceDisk.IsNull() && !plan.ResourceDisk.IsUnknown() && !state.ResourceDisk.IsNull() && !state.ResourceDisk.IsUnknown() {
|
||||
if plan.ResourceDisk.ValueInt64() != state.ResourceDisk.ValueInt64() {
|
||||
} else if !plan.BackupConfiguration.IsNull() && !plan.BackupConfiguration.IsUnknown() && !state.BackupConfiguration.IsNull() && !state.BackupConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.BackupConfiguration.ValueString(), state.BackupConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.NeedExternalAddressMaster.IsNull() != state.NeedExternalAddressMaster.IsNull() || plan.NeedExternalAddressMaster.IsUnknown() != state.NeedExternalAddressMaster.IsUnknown() {
|
||||
if plan.AutoscaleConfiguration.IsNull() != state.AutoscaleConfiguration.IsNull() || plan.AutoscaleConfiguration.IsUnknown() != state.AutoscaleConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.NeedExternalAddressMaster.IsNull() && !plan.NeedExternalAddressMaster.IsUnknown() && !state.NeedExternalAddressMaster.IsNull() && !state.NeedExternalAddressMaster.IsUnknown() {
|
||||
if plan.NeedExternalAddressMaster.ValueBool() != state.NeedExternalAddressMaster.ValueBool() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IpSpaceNameMaster.IsNull() != state.IpSpaceNameMaster.IsNull() || plan.IpSpaceNameMaster.IsUnknown() != state.IpSpaceNameMaster.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceNameMaster.IsNull() && !plan.IpSpaceNameMaster.IsUnknown() && !state.IpSpaceNameMaster.IsNull() && !state.IpSpaceNameMaster.IsUnknown() {
|
||||
if plan.IpSpaceNameMaster.ValueString() != state.IpSpaceNameMaster.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ExtBACKUPSCHEDULE.IsNull() != state.ExtBACKUPSCHEDULE.IsNull() || plan.ExtBACKUPSCHEDULE.IsUnknown() != state.ExtBACKUPSCHEDULE.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ExtBACKUPSCHEDULE.IsNull() && !plan.ExtBACKUPSCHEDULE.IsUnknown() && !state.ExtBACKUPSCHEDULE.IsNull() && !state.ExtBACKUPSCHEDULE.IsUnknown() {
|
||||
if plan.ExtBACKUPSCHEDULE.ValueString() != state.ExtBACKUPSCHEDULE.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AutoScale.IsNull() != state.AutoScale.IsNull() || plan.AutoScale.IsUnknown() != state.AutoScale.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AutoScale.IsNull() && !plan.AutoScale.IsUnknown() && !state.AutoScale.IsNull() && !state.AutoScale.IsUnknown() {
|
||||
if plan.AutoScale.ValueBool() != state.AutoScale.ValueBool() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AutoScalePercentage.IsNull() != state.AutoScalePercentage.IsNull() || plan.AutoScalePercentage.IsUnknown() != state.AutoScalePercentage.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AutoScalePercentage.IsNull() && !plan.AutoScalePercentage.IsUnknown() && !state.AutoScalePercentage.IsNull() && !state.AutoScalePercentage.IsUnknown() {
|
||||
if plan.AutoScalePercentage.ValueInt64() != state.AutoScalePercentage.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AutoScaleTechWindow.IsNull() != state.AutoScaleTechWindow.IsNull() || plan.AutoScaleTechWindow.IsUnknown() != state.AutoScaleTechWindow.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AutoScaleTechWindow.IsNull() && !plan.AutoScaleTechWindow.IsUnknown() && !state.AutoScaleTechWindow.IsNull() && !state.AutoScaleTechWindow.IsUnknown() {
|
||||
if plan.AutoScaleTechWindow.ValueInt64() != state.AutoScaleTechWindow.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AutoScaleQuotaGb.IsNull() != state.AutoScaleQuotaGb.IsNull() || plan.AutoScaleQuotaGb.IsUnknown() != state.AutoScaleQuotaGb.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AutoScaleQuotaGb.IsNull() && !plan.AutoScaleQuotaGb.IsUnknown() && !state.AutoScaleQuotaGb.IsNull() && !state.AutoScaleQuotaGb.IsUnknown() {
|
||||
if plan.AutoScaleQuotaGb.ValueInt64() != state.AutoScaleQuotaGb.ValueInt64() {
|
||||
} else if !plan.AutoscaleConfiguration.IsNull() && !plan.AutoscaleConfiguration.IsUnknown() && !state.AutoscaleConfiguration.IsNull() && !state.AutoscaleConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AutoscaleConfiguration.ValueString(), state.AutoscaleConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -479,23 +406,20 @@ func (r *MariadbResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
}
|
||||
|
||||
params := map[int]string{
|
||||
436: resources_core.FormatInt64(plan.ResourceCPU),
|
||||
437: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
438: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
439: resources_core.FormatInt64(plan.ResourceDisk),
|
||||
440: resources_core.FormatBool(plan.NeedExternalAddressMaster),
|
||||
441: resources_core.FormatString(plan.IpSpaceNameMaster),
|
||||
442: resources_core.FormatString(plan.ExtBACKUPSCHEDULE),
|
||||
443: resources_core.FormatBool(plan.AutoScale),
|
||||
444: resources_core.FormatInt64(plan.AutoScalePercentage),
|
||||
445: resources_core.FormatInt64(plan.AutoScaleTechWindow),
|
||||
446: resources_core.FormatInt64(plan.AutoScaleQuotaGb),
|
||||
883: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
884: resources_core.FormatString(plan.AccessConfiguration),
|
||||
885: resources_core.FormatString(plan.MariadbConfiguration),
|
||||
886: resources_core.FormatString(plan.BackupConfiguration),
|
||||
887: resources_core.FormatString(plan.AutoscaleConfiguration),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -512,19 +436,12 @@ func (r *MariadbResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "autoScale", Field: "AutoScale", Type: "bool"},
|
||||
{Code: "autoScalePercentage", Field: "AutoScalePercentage", Type: "int64"},
|
||||
{Code: "autoScaleQuotaGb", Field: "AutoScaleQuotaGb", Type: "int64"},
|
||||
{Code: "autoScaleTechWindow", Field: "AutoScaleTechWindow", Type: "int64"},
|
||||
{Code: "ext_BACKUP_SCHEDULE", Field: "ExtBACKUPSCHEDULE", Type: "string"},
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "autoscaleConfiguration", Field: "AutoscaleConfiguration", Type: "string"},
|
||||
{Code: "backupConfiguration", Field: "BackupConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "mariadbConfiguration", Field: "MariadbConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -543,7 +460,6 @@ func (r *MariadbResource) Delete(ctx context.Context, req resource.DeleteRequest
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -553,6 +469,9 @@ func (r *MariadbResource) Delete(ctx context.Context, req resource.DeleteRequest
|
||||
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
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: mariadb
|
||||
// Subresource: user
|
||||
|
||||
var _ resource.Resource = &MariadbUserResource{}
|
||||
|
||||
// MariadbUserResource manages a subresource via operations.
|
||||
type MariadbUserResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type MariadbUserModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
MariadbID types.String `tfsdk:"mariadb_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessHosts types.String `tfsdk:"access_hosts"`
|
||||
DbName types.String `tfsdk:"db_name"`
|
||||
Password types.String `tfsdk:"password"`
|
||||
Username types.String `tfsdk:"username"`
|
||||
}
|
||||
|
||||
func NewMariadbUserResource() resource.Resource {
|
||||
return &MariadbUserResource{}
|
||||
}
|
||||
|
||||
func (r *MariadbUserResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_mariadb_user"
|
||||
}
|
||||
|
||||
func (r *MariadbUserResource) 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()}},
|
||||
"mariadb_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(false)},
|
||||
"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."},
|
||||
"access_hosts": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("%"), MarkdownDescription: "Пример: `%` Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"db_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{}]"), MarkdownDescription: "Необходимо указать массив существующих баз Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"password": schema.StringAttribute{Required: true, MarkdownDescription: "Секрет будет записан в хранилище секретов и не будет отображаться публично Не изменяется после создания. Изменение требует пересоздания.", Sensitive: true, PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"username": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("user"), MarkdownDescription: "Пример: `myusername` Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *MariadbUserResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan MariadbUserModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.Password.IsNull() || plan.Password.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "password is required.")
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.MariadbID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"accessHosts": resources_core.FormatString(plan.AccessHosts),
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
"password": resources_core.FormatString(plan.Password),
|
||||
"username": resources_core.FormatString(plan.Username),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"accessHosts",
|
||||
"dbName",
|
||||
"password",
|
||||
"username",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("user", 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, "user", idParams))
|
||||
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{
|
||||
"username": resources_core.FormatString(plan.Username),
|
||||
"password": resources_core.FormatString(plan.Password),
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
"accessHosts": resources_core.FormatString(plan.AccessHosts),
|
||||
})
|
||||
|
||||
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, "create_user", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", 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, "user", idParams))
|
||||
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 known && found {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "user", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект подтверждён в state_out, выполняется усыновление")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "user", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *MariadbUserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state MariadbUserModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *MariadbUserResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan MariadbUserModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
var state MariadbUserModel
|
||||
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.MariadbID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{
|
||||
"accessHosts": resources_core.FormatString(plan.AccessHosts),
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
"password": resources_core.FormatString(plan.Password),
|
||||
"username": resources_core.FormatString(plan.Username),
|
||||
}
|
||||
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "user", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *MariadbUserResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state MariadbUserModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "delete_user" == "" {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(state.MariadbID.ValueString())
|
||||
if instanceUID == "" {
|
||||
return
|
||||
}
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"accessHosts": resources_core.FormatString(state.AccessHosts),
|
||||
"dbName": resources_core.FormatString(state.DbName),
|
||||
"password": resources_core.FormatString(state.Password),
|
||||
"username": resources_core.FormatString(state.Username),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"accessHosts",
|
||||
"dbName",
|
||||
"password",
|
||||
"username",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("user", 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 skipMissingOnDelete {
|
||||
resp.Diagnostics.AddWarning("Подресурс не найден", "Объект отсутствует и будет пропущен: "+targetValue)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Подресурс не найден", "Объект отсутствует: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{
|
||||
"username": resources_core.FormatString(state.Username),
|
||||
})
|
||||
|
||||
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, "delete_user", 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 *MariadbUserResource) 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
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -29,6 +32,7 @@ type KafkaModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
IpSpaceNameMaster types.String `tfsdk:"ip_space_name_master"`
|
||||
NeedExternalAddressMaster types.Bool `tfsdk:"need_external_address_master"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
@@ -58,9 +62,10 @@ func (r *KafkaResource) Metadata(ctx context.Context, req resource.MetadataReque
|
||||
|
||||
func (r *KafkaResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"ip_space_name_master": schema.StringAttribute{Optional: true, MarkdownDescription: "Если включен предыдущий параметр, то поле необходимо заполнить. Пример: `internet-no-antiddos-v1`"},
|
||||
"need_external_address_master": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Флаг, определяющий, нужно ли выделять внешний IP для доступа к master-брокеру. Пример: `true` или `false`"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Required: true, MarkdownDescription: "Пример: `2000`"},
|
||||
@@ -119,33 +124,51 @@ func (r *KafkaResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceCPU.IsNull() || config.ResourceCPU.IsUnknown() {
|
||||
if config.ResourceCPU.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_c_p_u is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceDisk.IsNull() || config.ResourceDisk.IsUnknown() {
|
||||
if config.ResourceCPU.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceDisk.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_disk is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceInstances.IsNull() || config.ResourceInstances.IsUnknown() {
|
||||
if config.ResourceDisk.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceInstances.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_instances is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceMemory.IsNull() || config.ResourceMemory.IsUnknown() {
|
||||
if config.ResourceInstances.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceMemory.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_memory is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceMemory.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -163,8 +186,10 @@ func (r *KafkaResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
466: resources_core.FormatString(config.IpSpaceNameMaster),
|
||||
469: resources_core.FormatString(config.ResourceRealm),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 116, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 116, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *KafkaResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -195,7 +220,9 @@ func (r *KafkaResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 116, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 116, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
461: resources_core.FormatInt64(data.ResourceInstances),
|
||||
@@ -211,6 +238,9 @@ func (r *KafkaResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
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, 116, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -359,7 +389,7 @@ func (r *KafkaResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.IpSpaceNameMaster.IsNull() != state.IpSpaceNameMaster.IsNull() || plan.IpSpaceNameMaster.IsUnknown() != state.IpSpaceNameMaster.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceNameMaster.IsNull() && !plan.IpSpaceNameMaster.IsUnknown() && !state.IpSpaceNameMaster.IsNull() && !state.IpSpaceNameMaster.IsUnknown() {
|
||||
if plan.IpSpaceNameMaster.ValueString() != state.IpSpaceNameMaster.ValueString() {
|
||||
if !strings.EqualFold(plan.IpSpaceNameMaster.ValueString(), state.IpSpaceNameMaster.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -392,6 +422,9 @@ func (r *KafkaResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -433,7 +466,6 @@ func (r *KafkaResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -443,6 +475,9 @@ func (r *KafkaResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -32,6 +32,7 @@ type KafkaTopicModel struct {
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
NameTopic types.String `tfsdk:"name_topic"`
|
||||
Partitions types.Int64 `tfsdk:"partitions"`
|
||||
Replicas types.Int64 `tfsdk:"replicas"`
|
||||
@@ -47,7 +48,7 @@ func (r *KafkaTopicResource) Metadata(ctx context.Context, req resource.Metadata
|
||||
|
||||
func (r *KafkaTopicResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"kafka_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
@@ -55,6 +56,7 @@ func (r *KafkaTopicResource) Schema(ctx context.Context, req resource.SchemaRequ
|
||||
"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(false)},
|
||||
"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."},
|
||||
"name_topic": schema.StringAttribute{Required: true, MarkdownDescription: "Пример: `orders-events` Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"partitions": schema.Int64Attribute{Required: true, MarkdownDescription: "Число партиций можно увеличить, но уменьшить их нельзя."},
|
||||
"replicas": schema.Int64Attribute{Required: true, MarkdownDescription: "Количество реплик не может привышать количество реплик брокера"},
|
||||
@@ -136,8 +138,28 @@ func (r *KafkaTopicResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_topic", params, operationTimeout); err != nil {
|
||||
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(err) {
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_topic", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_topic", 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, "topic", idParams))
|
||||
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 {
|
||||
@@ -154,7 +176,7 @@ func (r *KafkaTopicResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
|
||||
return
|
||||
}
|
||||
if targetValue != "" {
|
||||
@@ -222,6 +244,9 @@ func (r *KafkaTopicResource) Update(ctx context.Context, req resource.UpdateRequ
|
||||
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, "modify_topic", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -292,6 +317,9 @@ func (r *KafkaTopicResource) Delete(ctx context.Context, req resource.DeleteRequ
|
||||
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, "delete_topic", params, operationTimeout); err != nil {
|
||||
if resources_core.IsSubresourceMissingError(err) {
|
||||
if targetValue != "" {
|
||||
|
||||
@@ -32,6 +32,7 @@ type KafkaUserModel struct {
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessHosts types.String `tfsdk:"access_hosts"`
|
||||
Group types.String `tfsdk:"group"`
|
||||
NameTopic types.String `tfsdk:"name_topic"`
|
||||
@@ -49,7 +50,7 @@ func (r *KafkaUserResource) Metadata(ctx context.Context, req resource.MetadataR
|
||||
|
||||
func (r *KafkaUserResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"kafka_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
@@ -57,6 +58,7 @@ func (r *KafkaUserResource) Schema(ctx context.Context, req resource.SchemaReque
|
||||
"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(false)},
|
||||
"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."},
|
||||
"access_hosts": schema.StringAttribute{Required: true, MarkdownDescription: "Пример: `192.168.1.0/24` или `*`"},
|
||||
"group": schema.StringAttribute{Required: true, MarkdownDescription: "Пример: `order-consumers` или `*`"},
|
||||
"name_topic": schema.StringAttribute{Required: true, MarkdownDescription: "Пример: `orders-events`. Использовать маску нельзя(`orders*`), только полное название или `*`"},
|
||||
@@ -150,8 +152,28 @@ func (r *KafkaUserResource) Create(ctx context.Context, req resource.CreateReque
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", params, operationTimeout); err != nil {
|
||||
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(err) {
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", 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, "user", idParams))
|
||||
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 {
|
||||
@@ -168,7 +190,7 @@ func (r *KafkaUserResource) Create(ctx context.Context, req resource.CreateReque
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
|
||||
return
|
||||
}
|
||||
if targetValue != "" {
|
||||
@@ -238,6 +260,9 @@ func (r *KafkaUserResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
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, "modify_user", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -308,6 +333,9 @@ func (r *KafkaUserResource) Delete(ctx context.Context, req resource.DeleteReque
|
||||
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, "delete_user", params, operationTimeout); err != nil {
|
||||
if resources_core.IsSubresourceMissingError(err) {
|
||||
if targetValue != "" {
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -29,6 +32,7 @@ type NifiModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
KafkaUid types.String `tfsdk:"kafka_uid"`
|
||||
NameTopic types.String `tfsdk:"name_topic"`
|
||||
Partitions types.Int64 `tfsdk:"partitions"`
|
||||
@@ -55,10 +59,11 @@ func (r *NifiResource) Metadata(ctx context.Context, req resource.MetadataReques
|
||||
|
||||
func (r *NifiResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"kafka_uid": schema.StringAttribute{Required: true, MarkdownDescription: "Выбрать кластер из списка Не изменяется после создания."},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"kafka_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Выбрать кластер из списка Не изменяется после создания."},
|
||||
"name_topic": schema.StringAttribute{Required: true, MarkdownDescription: "Пример: `orders-events` Не изменяется после создания."},
|
||||
"partitions": schema.Int64Attribute{Required: true, MarkdownDescription: "Число партиций на кластер ≤ 100 × (число брокеров) × (фактор репликации). Пример `50`"},
|
||||
"replicas": schema.Int64Attribute{Required: true, MarkdownDescription: "Количество партиций ≤ (число брокеров). Пример `2`"},
|
||||
@@ -106,22 +111,11 @@ func (r *NifiResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.KafkaUid.IsNull() && !plan.KafkaUid.IsUnknown() {
|
||||
resolvedKafkaUid, err := r.client.ResolveRefSvcParamValue(ctx, 116, plan.KafkaUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve kafka_uid", err.Error())
|
||||
} else if resolvedKafkaUid != "" && resolvedKafkaUid != plan.KafkaUid.ValueString() {
|
||||
plan.KafkaUid = types.StringValue(resolvedKafkaUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -129,35 +123,53 @@ func (r *NifiResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
}
|
||||
}
|
||||
if !plan.KafkaUid.IsNull() && !plan.KafkaUid.IsUnknown() && !state.KafkaUid.IsNull() && !state.KafkaUid.IsUnknown() {
|
||||
if plan.KafkaUid.ValueString() != state.KafkaUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.KafkaUid.ValueString(), state.KafkaUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить kafka_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.NameTopic.IsNull() && !plan.NameTopic.IsUnknown() && !state.NameTopic.IsNull() && !state.NameTopic.IsUnknown() {
|
||||
if plan.NameTopic.ValueString() != state.NameTopic.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.NameTopic.ValueString(), state.NameTopic.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить name_topic", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.KafkaUid.IsNull() || config.KafkaUid.IsUnknown() {
|
||||
if config.KafkaUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "kafka_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.NameTopic.IsNull() || config.NameTopic.IsUnknown() {
|
||||
if config.KafkaUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.NameTopic.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "name_topic is required.")
|
||||
return
|
||||
}
|
||||
if config.Partitions.IsNull() || config.Partitions.IsUnknown() {
|
||||
if config.NameTopic.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.Partitions.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "partitions is required.")
|
||||
return
|
||||
}
|
||||
if config.Replicas.IsNull() || config.Replicas.IsUnknown() {
|
||||
if config.Partitions.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.Replicas.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "replicas is required.")
|
||||
return
|
||||
}
|
||||
if config.Replicas.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -180,8 +192,10 @@ func (r *NifiResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
473: resources_core.FormatInt64(config.Replicas),
|
||||
474: resources_core.FormatString(config.NameTopic),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 117, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 117, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *NifiResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -206,6 +220,24 @@ func (r *NifiResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
resp.Diagnostics.AddError("Missing required attribute", "replicas is required.")
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalKafkaUid := data.KafkaUid
|
||||
if !data.KafkaUid.IsNull() && !data.KafkaUid.IsUnknown() {
|
||||
resolvedKafkaUid, err := r.client.ResolveRefSvcParamValue(ctx, 116, data.KafkaUid.ValueString())
|
||||
if err != nil {
|
||||
@@ -218,7 +250,9 @@ func (r *NifiResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 117, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 117, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
471: resources_core.FormatString(data.KafkaUid),
|
||||
@@ -231,6 +265,9 @@ func (r *NifiResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
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, 117, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -265,6 +302,16 @@ func (r *NifiResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
state.KafkaUid = types.StringValue(resolvedKafkaUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalKafkaUid.IsNull() && !originalKafkaUid.IsUnknown() && !state.KafkaUid.IsNull() && !state.KafkaUid.IsUnknown() {
|
||||
if strings.EqualFold(state.KafkaUid.ValueString(), originalKafkaUid.ValueString()) {
|
||||
state.KafkaUid = originalKafkaUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -317,6 +364,16 @@ func (r *NifiResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
newState.KafkaUid = types.StringValue(resolvedKafkaUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.KafkaUid.IsNull() && !state.KafkaUid.IsUnknown() && !newState.KafkaUid.IsNull() && !newState.KafkaUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.KafkaUid.ValueString(), state.KafkaUid.ValueString()) {
|
||||
newState.KafkaUid = state.KafkaUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -382,6 +439,9 @@ func (r *NifiResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -415,6 +475,15 @@ func (r *NifiResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
state.KafkaUid = types.StringValue(resolvedKafkaUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.KafkaUid.IsNull() && !plan.KafkaUid.IsUnknown() && !state.KafkaUid.IsNull() && !state.KafkaUid.IsUnknown() {
|
||||
if strings.EqualFold(state.KafkaUid.ValueString(), plan.KafkaUid.ValueString()) {
|
||||
state.KafkaUid = plan.KafkaUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -428,16 +497,15 @@ func (r *NifiResource) Delete(ctx context.Context, req resource.DeleteRequest, r
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,7 @@ type AkhqModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
KafkaUid types.String `tfsdk:"kafka_uid"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
@@ -58,11 +62,12 @@ func (r *AkhqResource) Metadata(ctx context.Context, req resource.MetadataReques
|
||||
|
||||
func (r *AkhqResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"domain": schema.StringAttribute{Required: true, MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"kafka_uid": schema.StringAttribute{Required: true, MarkdownDescription: "Выбрать кластер из списка"},
|
||||
"kafka_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Выбрать кластер из списка"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Пример: `1000` Не изменяется после создания."},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Не изменяется после создания."},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(512), MarkdownDescription: "Пример: `1024` Не изменяется после создания."},
|
||||
@@ -111,22 +116,11 @@ func (r *AkhqResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.KafkaUid.IsNull() && !plan.KafkaUid.IsUnknown() {
|
||||
resolvedKafkaUid, err := r.client.ResolveRefSvcParamValue(ctx, 116, plan.KafkaUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve kafka_uid", err.Error())
|
||||
} else if resolvedKafkaUid != "" && resolvedKafkaUid != plan.KafkaUid.ValueString() {
|
||||
plan.KafkaUid = types.StringValue(resolvedKafkaUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -134,7 +128,10 @@ func (r *AkhqResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Domain.ValueString(), state.Domain.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -158,25 +155,37 @@ func (r *AkhqResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.Domain.IsNull() || config.Domain.IsUnknown() {
|
||||
if config.Domain.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "domain is required.")
|
||||
return
|
||||
}
|
||||
if config.KafkaUid.IsNull() || config.KafkaUid.IsUnknown() {
|
||||
if config.Domain.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.KafkaUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "kafka_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.KafkaUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -201,8 +210,13 @@ func (r *AkhqResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
554: resources_core.FormatInt64(config.ResourceInstances),
|
||||
555: resources_core.FormatString(config.KafkaUid),
|
||||
}
|
||||
desiredDomain := ""
|
||||
if !config.Domain.IsNull() && !config.Domain.IsUnknown() {
|
||||
desiredDomain = config.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 119, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 119, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *AkhqResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -223,6 +237,24 @@ func (r *AkhqResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalKafkaUid := data.KafkaUid
|
||||
if !data.KafkaUid.IsNull() && !data.KafkaUid.IsUnknown() {
|
||||
resolvedKafkaUid, err := r.client.ResolveRefSvcParamValue(ctx, 116, data.KafkaUid.ValueString())
|
||||
if err != nil {
|
||||
@@ -235,7 +267,12 @@ func (r *AkhqResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 119, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||||
desiredDomain = data.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 119, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
550: resources_core.FormatString(data.ResourceRealm),
|
||||
@@ -250,6 +287,9 @@ func (r *AkhqResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
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, 119, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -286,6 +326,16 @@ func (r *AkhqResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
state.KafkaUid = types.StringValue(resolvedKafkaUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalKafkaUid.IsNull() && !originalKafkaUid.IsUnknown() && !state.KafkaUid.IsNull() && !state.KafkaUid.IsUnknown() {
|
||||
if strings.EqualFold(state.KafkaUid.ValueString(), originalKafkaUid.ValueString()) {
|
||||
state.KafkaUid = originalKafkaUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -340,6 +390,16 @@ func (r *AkhqResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
newState.KafkaUid = types.StringValue(resolvedKafkaUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.KafkaUid.IsNull() && !state.KafkaUid.IsUnknown() && !newState.KafkaUid.IsNull() && !newState.KafkaUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.KafkaUid.ValueString(), state.KafkaUid.ValueString()) {
|
||||
newState.KafkaUid = state.KafkaUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -367,7 +427,7 @@ func (r *AkhqResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
if plan.KafkaUid.IsNull() != state.KafkaUid.IsNull() || plan.KafkaUid.IsUnknown() != state.KafkaUid.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.KafkaUid.IsNull() && !plan.KafkaUid.IsUnknown() && !state.KafkaUid.IsNull() && !state.KafkaUid.IsUnknown() {
|
||||
if plan.KafkaUid.ValueString() != state.KafkaUid.ValueString() {
|
||||
if !strings.EqualFold(plan.KafkaUid.ValueString(), state.KafkaUid.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -395,6 +455,9 @@ func (r *AkhqResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -430,6 +493,15 @@ func (r *AkhqResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
state.KafkaUid = types.StringValue(resolvedKafkaUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.KafkaUid.IsNull() && !plan.KafkaUid.IsUnknown() && !state.KafkaUid.IsNull() && !state.KafkaUid.IsUnknown() {
|
||||
if strings.EqualFold(state.KafkaUid.ValueString(), plan.KafkaUid.ValueString()) {
|
||||
state.KafkaUid = plan.KafkaUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -443,7 +515,6 @@ func (r *AkhqResource) Delete(ctx context.Context, req resource.DeleteRequest, r
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -453,6 +524,9 @@ func (r *AkhqResource) Delete(ctx context.Context, req resource.DeleteRequest, r
|
||||
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
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
@@ -32,7 +33,8 @@ type ClickhouseDatabaseModel struct {
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
DatabaseName types.String `tfsdk:"database_name"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
DbName types.String `tfsdk:"db_name"`
|
||||
}
|
||||
|
||||
func NewClickhouseDatabaseResource() resource.Resource {
|
||||
@@ -45,7 +47,7 @@ func (r *ClickhouseDatabaseResource) Metadata(ctx context.Context, req resource.
|
||||
|
||||
func (r *ClickhouseDatabaseResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"clickhouse_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
@@ -53,7 +55,8 @@ func (r *ClickhouseDatabaseResource) Schema(ctx context.Context, req resource.Sc
|
||||
"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(false)},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"database_name": schema.StringAttribute{Required: true, MarkdownDescription: "Пример: `db01` Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"db_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("db01"), MarkdownDescription: "Пример: `db01` Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -64,10 +67,6 @@ func (r *ClickhouseDatabaseResource) Create(ctx context.Context, req resource.Cr
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.DatabaseName.IsNull() || plan.DatabaseName.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "database_name is required.")
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.ClickhouseID.ValueString())
|
||||
if instanceUID == "" {
|
||||
@@ -77,10 +76,10 @@ func (r *ClickhouseDatabaseResource) Create(ctx context.Context, req resource.Cr
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"databaseName": resources_core.FormatString(plan.DatabaseName),
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"databaseName",
|
||||
"dbName",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("database", identityCodes)
|
||||
targetValue := strings.TrimSpace(idParams[idKey])
|
||||
@@ -115,15 +114,35 @@ func (r *ClickhouseDatabaseResource) Create(ctx context.Context, req resource.Cr
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{
|
||||
"databaseName": resources_core.FormatString(plan.DatabaseName),
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
})
|
||||
|
||||
operationTimeout := ""
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_database", params, operationTimeout); err != nil {
|
||||
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(err) {
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_database", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_database", 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, "database", idParams))
|
||||
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 {
|
||||
@@ -140,7 +159,7 @@ func (r *ClickhouseDatabaseResource) Create(ctx context.Context, req resource.Cr
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
|
||||
return
|
||||
}
|
||||
if targetValue != "" {
|
||||
@@ -197,13 +216,16 @@ func (r *ClickhouseDatabaseResource) Update(ctx context.Context, req resource.Up
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{
|
||||
"databaseName": resources_core.FormatString(plan.DatabaseName),
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
}
|
||||
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -231,10 +253,10 @@ func (r *ClickhouseDatabaseResource) Delete(ctx context.Context, req resource.De
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"databaseName": resources_core.FormatString(state.DatabaseName),
|
||||
"dbName": resources_core.FormatString(state.DbName),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"databaseName",
|
||||
"dbName",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("database", identityCodes)
|
||||
targetValue := strings.TrimSpace(idParams[idKey])
|
||||
@@ -267,13 +289,16 @@ func (r *ClickhouseDatabaseResource) Delete(ctx context.Context, req resource.De
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{
|
||||
"databaseName": resources_core.FormatString(state.DatabaseName),
|
||||
"dbName": resources_core.FormatString(state.DbName),
|
||||
})
|
||||
|
||||
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, "delete_database", params, operationTimeout); err != nil {
|
||||
if resources_core.IsSubresourceMissingError(err) {
|
||||
if targetValue != "" {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: clickhouse
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &ClickhouseReconcileResource{}
|
||||
|
||||
// ClickhouseReconcileResource triggers a one-time action.
|
||||
type ClickhouseReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type ClickhouseReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ClickhouseID types.String `tfsdk:"clickhouse_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewClickhouseReconcileResource() resource.Resource {
|
||||
return &ClickhouseReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *ClickhouseReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_clickhouse_reconcile"
|
||||
}
|
||||
|
||||
func (r *ClickhouseReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"clickhouse_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *ClickhouseReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan ClickhouseReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.ClickhouseID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *ClickhouseReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state ClickhouseReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *ClickhouseReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan ClickhouseReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.ClickhouseID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *ClickhouseReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *ClickhouseReconcileResource) 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
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,8 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -28,35 +29,28 @@ type ClickhouseResource struct {
|
||||
}
|
||||
|
||||
type ClickhouseModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
AppVersion types.String `tfsdk:"app_version"`
|
||||
ExtBACKUPNUMTORETAIN types.Int64 `tfsdk:"ext__b_a_c_k_u_p__n_u_m__t_o__r_e_t_a_i_n"`
|
||||
ExtBACKUPSCHEDULE types.String `tfsdk:"ext__b_a_c_k_u_p__s_c_h_e_d_u_l_e"`
|
||||
IpSpaceNameMaster types.String `tfsdk:"ip_space_name_master"`
|
||||
NeedCreateKeeper types.Bool `tfsdk:"need_create_keeper"`
|
||||
NeedExternalAddressMaster types.Bool `tfsdk:"need_external_address_master"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceCPUKeeper types.Int64 `tfsdk:"resource_c_p_u_keeper"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceInstancesKeeper types.Int64 `tfsdk:"resource_instances_keeper"`
|
||||
ResourceInstancesShards types.Int64 `tfsdk:"resource_instances_shards"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceMemoryKeeper types.Int64 `tfsdk:"resource_memory_keeper"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
S3Uid types.String `tfsdk:"s3_uid"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
AutoscaleConfiguration types.String `tfsdk:"autoscale_configuration"`
|
||||
BackupConfiguration types.String `tfsdk:"backup_configuration"`
|
||||
ClickhouseConfiguration types.String `tfsdk:"clickhouse_configuration"`
|
||||
ClickhouseFilesConfiguration types.String `tfsdk:"clickhouse_files_configuration"`
|
||||
ClickhouseKeeperConfiguration types.String `tfsdk:"clickhouse_keeper_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
}
|
||||
|
||||
func NewClickhouseResource() resource.Resource {
|
||||
@@ -69,35 +63,28 @@ func (r *ClickhouseResource) Metadata(ctx context.Context, req resource.Metadata
|
||||
|
||||
func (r *ClickhouseResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"app_version": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("25.10"), MarkdownDescription: "Версия `clickhouse-server`"},
|
||||
"ext__b_a_c_k_u_p__n_u_m__t_o__r_e_t_a_i_n": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(7), MarkdownDescription: "Пример: `7`"},
|
||||
"ext__b_a_c_k_u_p__s_c_h_e_d_u_l_e": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("0 0 * * *"), MarkdownDescription: "Принимает формат `CRON`"},
|
||||
"ip_space_name_master": schema.StringAttribute{Optional: true, MarkdownDescription: "Пример: `internet-no-antiddos-v1`"},
|
||||
"need_create_keeper": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true), MarkdownDescription: "Необходим для кластеризации ClickHouse"},
|
||||
"need_external_address_master": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Позволяет подключиться извне к ClickHouse"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1000), MarkdownDescription: "Пример: `1000`"},
|
||||
"resource_c_p_u_keeper": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(50), MarkdownDescription: "Пример: `100`"},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(10), MarkdownDescription: "Пример: `100`"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Пример: `3`"},
|
||||
"resource_instances_keeper": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Пример: `1`"},
|
||||
"resource_instances_shards": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Пример: `1`"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1536), MarkdownDescription: "Пример: `2048`"},
|
||||
"resource_memory_keeper": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(152), MarkdownDescription: "Пример: `256`"},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Кластер Kubernetes в котором будет развёрнуто приложение Не изменяется после создания."},
|
||||
"s3_uid": schema.StringAttribute{Required: true, MarkdownDescription: "Экземпляр (учетная запись) S3 для резервного копирования. Резервное копирование обязательно Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
"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."},
|
||||
"access_configuration": schema.StringAttribute{Required: true},
|
||||
"autoscale_configuration": schema.StringAttribute{Required: true},
|
||||
"backup_configuration": schema.StringAttribute{Required: true},
|
||||
"clickhouse_configuration": schema.StringAttribute{Required: true},
|
||||
"clickhouse_files_configuration": schema.StringAttribute{Optional: true, MarkdownDescription: "Формат:<br/> - path: somethings3.xml<br/> xmlConfig: '<clickhouse>......</clickhouse>'"},
|
||||
"clickhouse_keeper_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
}
|
||||
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
@@ -132,48 +119,70 @@ func (r *ClickhouseResource) ModifyPlan(ctx context.Context, req resource.Modify
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.S3Uid.IsNull() && !plan.S3Uid.IsUnknown() {
|
||||
resolvedS3Uid, err := r.client.ResolveRefSvcParamValue(ctx, 12, plan.S3Uid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve s3_uid", err.Error())
|
||||
} else if resolvedS3Uid != "" && resolvedS3Uid != plan.S3Uid.ValueString() {
|
||||
plan.S3Uid = types.StringValue(resolvedS3Uid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.S3Uid.IsNull() && !plan.S3Uid.IsUnknown() && !state.S3Uid.IsNull() && !state.S3Uid.IsUnknown() {
|
||||
if plan.S3Uid.ValueString() != state.S3Uid.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить s3_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.S3Uid.IsNull() || config.S3Uid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "s3_uid is required.")
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.AutoscaleConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "autoscale_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AutoscaleConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.BackupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "backup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.BackupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClickhouseConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "clickhouse_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClickhouseConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClickhouseKeeperConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "clickhouse_keeper_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClickhouseKeeperConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -184,34 +193,20 @@ func (r *ClickhouseResource) ModifyPlan(ctx context.Context, req resource.Modify
|
||||
if !config.AdoptExistingOnCreate.IsNull() && !config.AdoptExistingOnCreate.IsUnknown() {
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
if !config.S3Uid.IsNull() && !config.S3Uid.IsUnknown() {
|
||||
resolvedS3Uid, err := r.client.ResolveRefSvcParamValue(ctx, 12, config.S3Uid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve s3_uid", err.Error())
|
||||
} else if resolvedS3Uid != "" && resolvedS3Uid != config.S3Uid.ValueString() {
|
||||
config.S3Uid = types.StringValue(resolvedS3Uid)
|
||||
}
|
||||
}
|
||||
params := map[int]string{
|
||||
572: resources_core.FormatString(config.ResourceRealm),
|
||||
573: resources_core.FormatString(config.S3Uid),
|
||||
574: resources_core.FormatInt64(config.ResourceInstances),
|
||||
575: resources_core.FormatInt64(config.ResourceInstancesShards),
|
||||
576: resources_core.FormatInt64(config.ResourceCPU),
|
||||
577: resources_core.FormatInt64(config.ResourceMemory),
|
||||
578: resources_core.FormatString(config.AppVersion),
|
||||
579: resources_core.FormatInt64(config.ResourceDisk),
|
||||
580: resources_core.FormatBool(config.NeedExternalAddressMaster),
|
||||
581: resources_core.FormatString(config.IpSpaceNameMaster),
|
||||
582: resources_core.FormatBool(config.NeedCreateKeeper),
|
||||
583: resources_core.FormatInt64(config.ResourceCPUKeeper),
|
||||
584: resources_core.FormatInt64(config.ResourceMemoryKeeper),
|
||||
585: resources_core.FormatInt64(config.ResourceInstancesKeeper),
|
||||
586: resources_core.FormatString(config.ExtBACKUPSCHEDULE),
|
||||
587: resources_core.FormatInt64(config.ExtBACKUPNUMTORETAIN),
|
||||
837: resources_core.FormatString(config.StartupConfiguration),
|
||||
838: resources_core.FormatString(config.ClusterConfiguration),
|
||||
839: resources_core.FormatString(config.AccessConfiguration),
|
||||
840: resources_core.FormatString(config.ClickhouseKeeperConfiguration),
|
||||
841: resources_core.FormatString(config.ClickhouseConfiguration),
|
||||
842: resources_core.FormatString(config.BackupConfiguration),
|
||||
843: resources_core.FormatString(config.AutoscaleConfiguration),
|
||||
851: resources_core.FormatString(config.ClickhouseFilesConfiguration),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 120, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 120, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *ClickhouseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -220,41 +215,58 @@ func (r *ClickhouseResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.S3Uid.IsNull() || data.S3Uid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "s3_uid is required.")
|
||||
if data.AutoscaleConfiguration.IsNull() || data.AutoscaleConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "autoscale_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.BackupConfiguration.IsNull() || data.BackupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "backup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClickhouseConfiguration.IsNull() || data.ClickhouseConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "clickhouse_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClickhouseKeeperConfiguration.IsNull() || data.ClickhouseKeeperConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "clickhouse_keeper_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 120, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 120, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
572: resources_core.FormatString(data.ResourceRealm),
|
||||
573: resources_core.FormatString(data.S3Uid),
|
||||
574: resources_core.FormatInt64(data.ResourceInstances),
|
||||
575: resources_core.FormatInt64(data.ResourceInstancesShards),
|
||||
576: resources_core.FormatInt64(data.ResourceCPU),
|
||||
577: resources_core.FormatInt64(data.ResourceMemory),
|
||||
578: resources_core.FormatString(data.AppVersion),
|
||||
579: resources_core.FormatInt64(data.ResourceDisk),
|
||||
580: resources_core.FormatBool(data.NeedExternalAddressMaster),
|
||||
581: resources_core.FormatString(data.IpSpaceNameMaster),
|
||||
582: resources_core.FormatBool(data.NeedCreateKeeper),
|
||||
583: resources_core.FormatInt64(data.ResourceCPUKeeper),
|
||||
584: resources_core.FormatInt64(data.ResourceMemoryKeeper),
|
||||
585: resources_core.FormatInt64(data.ResourceInstancesKeeper),
|
||||
586: resources_core.FormatString(data.ExtBACKUPSCHEDULE),
|
||||
587: resources_core.FormatInt64(data.ExtBACKUPNUMTORETAIN),
|
||||
837: resources_core.FormatString(data.StartupConfiguration),
|
||||
838: resources_core.FormatString(data.ClusterConfiguration),
|
||||
839: resources_core.FormatString(data.AccessConfiguration),
|
||||
840: resources_core.FormatString(data.ClickhouseKeeperConfiguration),
|
||||
841: resources_core.FormatString(data.ClickhouseConfiguration),
|
||||
842: resources_core.FormatString(data.BackupConfiguration),
|
||||
843: resources_core.FormatString(data.AutoscaleConfiguration),
|
||||
851: resources_core.FormatString(data.ClickhouseFilesConfiguration),
|
||||
}
|
||||
|
||||
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, 120, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -272,21 +284,14 @@ func (r *ClickhouseResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "ext_BACKUP_NUM_TO_RETAIN", Field: "ExtBACKUPNUMTORETAIN", Type: "int64"},
|
||||
{Code: "ext_BACKUP_SCHEDULE", Field: "ExtBACKUPSCHEDULE", Type: "string"},
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "needCreateKeeper", Field: "NeedCreateKeeper", Type: "bool"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceCPUKeeper", Field: "ResourceCPUKeeper", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceInstancesKeeper", Field: "ResourceInstancesKeeper", Type: "int64"},
|
||||
{Code: "resourceInstancesShards", Field: "ResourceInstancesShards", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceMemoryKeeper", Field: "ResourceMemoryKeeper", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "autoscaleConfiguration", Field: "AutoscaleConfiguration", Type: "string"},
|
||||
{Code: "backupConfiguration", Field: "BackupConfiguration", Type: "string"},
|
||||
{Code: "clickhouseConfiguration", Field: "ClickhouseConfiguration", Type: "string"},
|
||||
{Code: "clickhouseFilesConfiguration", Field: "ClickhouseFilesConfiguration", Type: "string"},
|
||||
{Code: "clickhouseKeeperConfiguration", Field: "ClickhouseKeeperConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -327,21 +332,14 @@ func (r *ClickhouseResource) Read(ctx context.Context, req resource.ReadRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "ext_BACKUP_NUM_TO_RETAIN", Field: "ExtBACKUPNUMTORETAIN", Type: "int64"},
|
||||
{Code: "ext_BACKUP_SCHEDULE", Field: "ExtBACKUPSCHEDULE", Type: "string"},
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "needCreateKeeper", Field: "NeedCreateKeeper", Type: "bool"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceCPUKeeper", Field: "ResourceCPUKeeper", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceInstancesKeeper", Field: "ResourceInstancesKeeper", Type: "int64"},
|
||||
{Code: "resourceInstancesShards", Field: "ResourceInstancesShards", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceMemoryKeeper", Field: "ResourceMemoryKeeper", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "autoscaleConfiguration", Field: "AutoscaleConfiguration", Type: "string"},
|
||||
{Code: "backupConfiguration", Field: "BackupConfiguration", Type: "string"},
|
||||
{Code: "clickhouseConfiguration", Field: "ClickhouseConfiguration", Type: "string"},
|
||||
{Code: "clickhouseFilesConfiguration", Field: "ClickhouseFilesConfiguration", Type: "string"},
|
||||
{Code: "clickhouseKeeperConfiguration", Field: "ClickhouseKeeperConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -371,127 +369,64 @@ func (r *ClickhouseResource) Update(ctx context.Context, req resource.UpdateRequ
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstances.IsNull() != state.ResourceInstances.IsNull() || plan.ResourceInstances.IsUnknown() != state.ResourceInstances.IsUnknown() {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstancesShards.IsNull() != state.ResourceInstancesShards.IsNull() || plan.ResourceInstancesShards.IsUnknown() != state.ResourceInstancesShards.IsUnknown() {
|
||||
if plan.ClickhouseKeeperConfiguration.IsNull() != state.ClickhouseKeeperConfiguration.IsNull() || plan.ClickhouseKeeperConfiguration.IsUnknown() != state.ClickhouseKeeperConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstancesShards.IsNull() && !plan.ResourceInstancesShards.IsUnknown() && !state.ResourceInstancesShards.IsNull() && !state.ResourceInstancesShards.IsUnknown() {
|
||||
if plan.ResourceInstancesShards.ValueInt64() != state.ResourceInstancesShards.ValueInt64() {
|
||||
} else if !plan.ClickhouseKeeperConfiguration.IsNull() && !plan.ClickhouseKeeperConfiguration.IsUnknown() && !state.ClickhouseKeeperConfiguration.IsNull() && !state.ClickhouseKeeperConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClickhouseKeeperConfiguration.ValueString(), state.ClickhouseKeeperConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPU.IsNull() != state.ResourceCPU.IsNull() || plan.ResourceCPU.IsUnknown() != state.ResourceCPU.IsUnknown() {
|
||||
if plan.AccessConfiguration.IsNull() != state.AccessConfiguration.IsNull() || plan.AccessConfiguration.IsUnknown() != state.AccessConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
} else if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemory.IsNull() != state.ResourceMemory.IsNull() || plan.ResourceMemory.IsUnknown() != state.ResourceMemory.IsUnknown() {
|
||||
if plan.ClickhouseConfiguration.IsNull() != state.ClickhouseConfiguration.IsNull() || plan.ClickhouseConfiguration.IsUnknown() != state.ClickhouseConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
} else if !plan.ClickhouseConfiguration.IsNull() && !plan.ClickhouseConfiguration.IsUnknown() && !state.ClickhouseConfiguration.IsNull() && !state.ClickhouseConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClickhouseConfiguration.ValueString(), state.ClickhouseConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AppVersion.IsNull() != state.AppVersion.IsNull() || plan.AppVersion.IsUnknown() != state.AppVersion.IsUnknown() {
|
||||
if plan.BackupConfiguration.IsNull() != state.BackupConfiguration.IsNull() || plan.BackupConfiguration.IsUnknown() != state.BackupConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AppVersion.IsNull() && !plan.AppVersion.IsUnknown() && !state.AppVersion.IsNull() && !state.AppVersion.IsUnknown() {
|
||||
if plan.AppVersion.ValueString() != state.AppVersion.ValueString() {
|
||||
} else if !plan.BackupConfiguration.IsNull() && !plan.BackupConfiguration.IsUnknown() && !state.BackupConfiguration.IsNull() && !state.BackupConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.BackupConfiguration.ValueString(), state.BackupConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceDisk.IsNull() != state.ResourceDisk.IsNull() || plan.ResourceDisk.IsUnknown() != state.ResourceDisk.IsUnknown() {
|
||||
if plan.AutoscaleConfiguration.IsNull() != state.AutoscaleConfiguration.IsNull() || plan.AutoscaleConfiguration.IsUnknown() != state.AutoscaleConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceDisk.IsNull() && !plan.ResourceDisk.IsUnknown() && !state.ResourceDisk.IsNull() && !state.ResourceDisk.IsUnknown() {
|
||||
if plan.ResourceDisk.ValueInt64() != state.ResourceDisk.ValueInt64() {
|
||||
} else if !plan.AutoscaleConfiguration.IsNull() && !plan.AutoscaleConfiguration.IsUnknown() && !state.AutoscaleConfiguration.IsNull() && !state.AutoscaleConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AutoscaleConfiguration.ValueString(), state.AutoscaleConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.NeedExternalAddressMaster.IsNull() != state.NeedExternalAddressMaster.IsNull() || plan.NeedExternalAddressMaster.IsUnknown() != state.NeedExternalAddressMaster.IsUnknown() {
|
||||
if plan.ClickhouseFilesConfiguration.IsNull() != state.ClickhouseFilesConfiguration.IsNull() || plan.ClickhouseFilesConfiguration.IsUnknown() != state.ClickhouseFilesConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.NeedExternalAddressMaster.IsNull() && !plan.NeedExternalAddressMaster.IsUnknown() && !state.NeedExternalAddressMaster.IsNull() && !state.NeedExternalAddressMaster.IsUnknown() {
|
||||
if plan.NeedExternalAddressMaster.ValueBool() != state.NeedExternalAddressMaster.ValueBool() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IpSpaceNameMaster.IsNull() != state.IpSpaceNameMaster.IsNull() || plan.IpSpaceNameMaster.IsUnknown() != state.IpSpaceNameMaster.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceNameMaster.IsNull() && !plan.IpSpaceNameMaster.IsUnknown() && !state.IpSpaceNameMaster.IsNull() && !state.IpSpaceNameMaster.IsUnknown() {
|
||||
if plan.IpSpaceNameMaster.ValueString() != state.IpSpaceNameMaster.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.NeedCreateKeeper.IsNull() != state.NeedCreateKeeper.IsNull() || plan.NeedCreateKeeper.IsUnknown() != state.NeedCreateKeeper.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.NeedCreateKeeper.IsNull() && !plan.NeedCreateKeeper.IsUnknown() && !state.NeedCreateKeeper.IsNull() && !state.NeedCreateKeeper.IsUnknown() {
|
||||
if plan.NeedCreateKeeper.ValueBool() != state.NeedCreateKeeper.ValueBool() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPUKeeper.IsNull() != state.ResourceCPUKeeper.IsNull() || plan.ResourceCPUKeeper.IsUnknown() != state.ResourceCPUKeeper.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPUKeeper.IsNull() && !plan.ResourceCPUKeeper.IsUnknown() && !state.ResourceCPUKeeper.IsNull() && !state.ResourceCPUKeeper.IsUnknown() {
|
||||
if plan.ResourceCPUKeeper.ValueInt64() != state.ResourceCPUKeeper.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemoryKeeper.IsNull() != state.ResourceMemoryKeeper.IsNull() || plan.ResourceMemoryKeeper.IsUnknown() != state.ResourceMemoryKeeper.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemoryKeeper.IsNull() && !plan.ResourceMemoryKeeper.IsUnknown() && !state.ResourceMemoryKeeper.IsNull() && !state.ResourceMemoryKeeper.IsUnknown() {
|
||||
if plan.ResourceMemoryKeeper.ValueInt64() != state.ResourceMemoryKeeper.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstancesKeeper.IsNull() != state.ResourceInstancesKeeper.IsNull() || plan.ResourceInstancesKeeper.IsUnknown() != state.ResourceInstancesKeeper.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstancesKeeper.IsNull() && !plan.ResourceInstancesKeeper.IsUnknown() && !state.ResourceInstancesKeeper.IsNull() && !state.ResourceInstancesKeeper.IsUnknown() {
|
||||
if plan.ResourceInstancesKeeper.ValueInt64() != state.ResourceInstancesKeeper.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ExtBACKUPSCHEDULE.IsNull() != state.ExtBACKUPSCHEDULE.IsNull() || plan.ExtBACKUPSCHEDULE.IsUnknown() != state.ExtBACKUPSCHEDULE.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ExtBACKUPSCHEDULE.IsNull() && !plan.ExtBACKUPSCHEDULE.IsUnknown() && !state.ExtBACKUPSCHEDULE.IsNull() && !state.ExtBACKUPSCHEDULE.IsUnknown() {
|
||||
if plan.ExtBACKUPSCHEDULE.ValueString() != state.ExtBACKUPSCHEDULE.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ExtBACKUPNUMTORETAIN.IsNull() != state.ExtBACKUPNUMTORETAIN.IsNull() || plan.ExtBACKUPNUMTORETAIN.IsUnknown() != state.ExtBACKUPNUMTORETAIN.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ExtBACKUPNUMTORETAIN.IsNull() && !plan.ExtBACKUPNUMTORETAIN.IsUnknown() && !state.ExtBACKUPNUMTORETAIN.IsNull() && !state.ExtBACKUPNUMTORETAIN.IsUnknown() {
|
||||
if plan.ExtBACKUPNUMTORETAIN.ValueInt64() != state.ExtBACKUPNUMTORETAIN.ValueInt64() {
|
||||
} else if !plan.ClickhouseFilesConfiguration.IsNull() && !plan.ClickhouseFilesConfiguration.IsUnknown() && !state.ClickhouseFilesConfiguration.IsNull() && !state.ClickhouseFilesConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClickhouseFilesConfiguration.ValueString(), state.ClickhouseFilesConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -512,26 +447,22 @@ func (r *ClickhouseResource) Update(ctx context.Context, req resource.UpdateRequ
|
||||
}
|
||||
|
||||
params := map[int]string{
|
||||
603: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
604: resources_core.FormatInt64(plan.ResourceInstancesShards),
|
||||
605: resources_core.FormatInt64(plan.ResourceCPU),
|
||||
606: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
607: resources_core.FormatString(plan.AppVersion),
|
||||
608: resources_core.FormatInt64(plan.ResourceDisk),
|
||||
609: resources_core.FormatBool(plan.NeedExternalAddressMaster),
|
||||
610: resources_core.FormatString(plan.IpSpaceNameMaster),
|
||||
611: resources_core.FormatBool(plan.NeedCreateKeeper),
|
||||
612: resources_core.FormatInt64(plan.ResourceCPUKeeper),
|
||||
613: resources_core.FormatInt64(plan.ResourceMemoryKeeper),
|
||||
614: resources_core.FormatInt64(plan.ResourceInstancesKeeper),
|
||||
615: resources_core.FormatString(plan.ExtBACKUPSCHEDULE),
|
||||
616: resources_core.FormatInt64(plan.ExtBACKUPNUMTORETAIN),
|
||||
844: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
845: resources_core.FormatString(plan.ClickhouseKeeperConfiguration),
|
||||
846: resources_core.FormatString(plan.AccessConfiguration),
|
||||
847: resources_core.FormatString(plan.ClickhouseConfiguration),
|
||||
848: resources_core.FormatString(plan.BackupConfiguration),
|
||||
849: resources_core.FormatString(plan.AutoscaleConfiguration),
|
||||
850: resources_core.FormatString(plan.ClickhouseFilesConfiguration),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -548,21 +479,14 @@ func (r *ClickhouseResource) Update(ctx context.Context, req resource.UpdateRequ
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "ext_BACKUP_NUM_TO_RETAIN", Field: "ExtBACKUPNUMTORETAIN", Type: "int64"},
|
||||
{Code: "ext_BACKUP_SCHEDULE", Field: "ExtBACKUPSCHEDULE", Type: "string"},
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "needCreateKeeper", Field: "NeedCreateKeeper", Type: "bool"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceCPUKeeper", Field: "ResourceCPUKeeper", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceInstancesKeeper", Field: "ResourceInstancesKeeper", Type: "int64"},
|
||||
{Code: "resourceInstancesShards", Field: "ResourceInstancesShards", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceMemoryKeeper", Field: "ResourceMemoryKeeper", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "autoscaleConfiguration", Field: "AutoscaleConfiguration", Type: "string"},
|
||||
{Code: "backupConfiguration", Field: "BackupConfiguration", Type: "string"},
|
||||
{Code: "clickhouseConfiguration", Field: "ClickhouseConfiguration", Type: "string"},
|
||||
{Code: "clickhouseFilesConfiguration", Field: "ClickhouseFilesConfiguration", Type: "string"},
|
||||
{Code: "clickhouseKeeperConfiguration", Field: "ClickhouseKeeperConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -581,7 +505,6 @@ func (r *ClickhouseResource) Delete(ctx context.Context, req resource.DeleteRequ
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -591,6 +514,9 @@ func (r *ClickhouseResource) Delete(ctx context.Context, req resource.DeleteRequ
|
||||
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
|
||||
|
||||
@@ -33,8 +33,9 @@ type ClickhouseUserModel struct {
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessHosts types.String `tfsdk:"access_hosts"`
|
||||
DatabaseName types.String `tfsdk:"database_name"`
|
||||
DbName types.String `tfsdk:"db_name"`
|
||||
Username types.String `tfsdk:"username"`
|
||||
}
|
||||
|
||||
@@ -48,7 +49,7 @@ func (r *ClickhouseUserResource) Metadata(ctx context.Context, req resource.Meta
|
||||
|
||||
func (r *ClickhouseUserResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"clickhouse_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
@@ -56,9 +57,10 @@ func (r *ClickhouseUserResource) Schema(ctx context.Context, req resource.Schema
|
||||
"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(false)},
|
||||
"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."},
|
||||
"access_hosts": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("::/0"), MarkdownDescription: "Пример: `::/0` Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"database_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[\"database\"]"), MarkdownDescription: "Необходимо указать массив существующих баз Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"username": schema.StringAttribute{Required: true, MarkdownDescription: "Пример: `myusername` Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"db_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{}]"), MarkdownDescription: "Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"username": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("myusername"), MarkdownDescription: "Пример: `myusername` Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -69,10 +71,6 @@ func (r *ClickhouseUserResource) Create(ctx context.Context, req resource.Create
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.Username.IsNull() || plan.Username.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "username is required.")
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.ClickhouseID.ValueString())
|
||||
if instanceUID == "" {
|
||||
@@ -82,13 +80,13 @@ func (r *ClickhouseUserResource) Create(ctx context.Context, req resource.Create
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"accessHosts": resources_core.FormatString(plan.AccessHosts),
|
||||
"databaseName": resources_core.FormatString(plan.DatabaseName),
|
||||
"username": resources_core.FormatString(plan.Username),
|
||||
"accessHosts": resources_core.FormatString(plan.AccessHosts),
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
"username": resources_core.FormatString(plan.Username),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"accessHosts",
|
||||
"databaseName",
|
||||
"dbName",
|
||||
"username",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("user", identityCodes)
|
||||
@@ -124,17 +122,37 @@ func (r *ClickhouseUserResource) Create(ctx context.Context, req resource.Create
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{
|
||||
"username": resources_core.FormatString(plan.Username),
|
||||
"databaseName": resources_core.FormatString(plan.DatabaseName),
|
||||
"accessHosts": resources_core.FormatString(plan.AccessHosts),
|
||||
"username": resources_core.FormatString(plan.Username),
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
"accessHosts": resources_core.FormatString(plan.AccessHosts),
|
||||
})
|
||||
|
||||
operationTimeout := ""
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", params, operationTimeout); err != nil {
|
||||
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(err) {
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", 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, "user", idParams))
|
||||
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 {
|
||||
@@ -151,7 +169,7 @@ func (r *ClickhouseUserResource) Create(ctx context.Context, req resource.Create
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
|
||||
return
|
||||
}
|
||||
if targetValue != "" {
|
||||
@@ -208,15 +226,18 @@ func (r *ClickhouseUserResource) Update(ctx context.Context, req resource.Update
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{
|
||||
"accessHosts": resources_core.FormatString(plan.AccessHosts),
|
||||
"databaseName": resources_core.FormatString(plan.DatabaseName),
|
||||
"username": resources_core.FormatString(plan.Username),
|
||||
"accessHosts": resources_core.FormatString(plan.AccessHosts),
|
||||
"dbName": resources_core.FormatString(plan.DbName),
|
||||
"username": resources_core.FormatString(plan.Username),
|
||||
}
|
||||
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -244,13 +265,13 @@ func (r *ClickhouseUserResource) Delete(ctx context.Context, req resource.Delete
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"accessHosts": resources_core.FormatString(state.AccessHosts),
|
||||
"databaseName": resources_core.FormatString(state.DatabaseName),
|
||||
"username": resources_core.FormatString(state.Username),
|
||||
"accessHosts": resources_core.FormatString(state.AccessHosts),
|
||||
"dbName": resources_core.FormatString(state.DbName),
|
||||
"username": resources_core.FormatString(state.Username),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"accessHosts",
|
||||
"databaseName",
|
||||
"dbName",
|
||||
"username",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("user", identityCodes)
|
||||
@@ -291,6 +312,9 @@ func (r *ClickhouseUserResource) Delete(ctx context.Context, req resource.Delete
|
||||
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, "delete_user", params, operationTimeout); err != nil {
|
||||
if resources_core.IsSubresourceMissingError(err) {
|
||||
if targetValue != "" {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: s3
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &S3ReconcileResource{}
|
||||
|
||||
// S3ReconcileResource triggers a one-time action.
|
||||
type S3ReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type S3ReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
S3ID types.String `tfsdk:"s3_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewS3ReconcileResource() resource.Resource {
|
||||
return &S3ReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *S3ReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_s3_reconcile"
|
||||
}
|
||||
|
||||
func (r *S3ReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"s3_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *S3ReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan S3ReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.S3ID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *S3ReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state S3ReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *S3ReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan S3ReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.S3ID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *S3ReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *S3ReconcileResource) 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
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -29,6 +32,7 @@ type S3Model struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
DisplayName types.String `tfsdk:"display_name"`
|
||||
MaxBucketsPerUser types.Int64 `tfsdk:"max_buckets_per_user"`
|
||||
MaxObjectsPerBucket types.Int64 `tfsdk:"max_objects_per_bucket"`
|
||||
@@ -56,9 +60,10 @@ func (r *S3Resource) Metadata(ctx context.Context, req resource.MetadataRequest,
|
||||
|
||||
func (r *S3Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"display_name": schema.StringAttribute{Required: true, MarkdownDescription: "Человеко-читаемое имя (транслит из Elma) - передаётся имя клиента Не изменяется после создания."},
|
||||
"max_buckets_per_user": schema.Int64Attribute{Optional: true, MarkdownDescription: "Квота на пользователя. Количество бакетов пользователя. Передаём руками"},
|
||||
"max_objects_per_bucket": schema.Int64Attribute{Optional: true, MarkdownDescription: "Квота на бакет. Кол-во объектов. Передаём руками"},
|
||||
@@ -115,27 +120,39 @@ func (r *S3Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequ
|
||||
}
|
||||
}
|
||||
if !plan.DisplayName.IsNull() && !plan.DisplayName.IsUnknown() && !state.DisplayName.IsNull() && !state.DisplayName.IsUnknown() {
|
||||
if plan.DisplayName.ValueString() != state.DisplayName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.DisplayName.ValueString(), state.DisplayName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить display_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.DisplayName.IsNull() || config.DisplayName.IsUnknown() {
|
||||
if config.DisplayName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "display_name is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.DisplayName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -148,8 +165,10 @@ func (r *S3Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequ
|
||||
163: resources_core.FormatString(config.ResourceRealm),
|
||||
320: resources_core.FormatString(config.DisplayName),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 12, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 12, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *S3Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -168,7 +187,9 @@ func (r *S3Resource) Create(ctx context.Context, req resource.CreateRequest, res
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 12, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 12, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
163: resources_core.FormatString(data.ResourceRealm),
|
||||
@@ -179,6 +200,9 @@ func (r *S3Resource) Create(ctx context.Context, req resource.CreateRequest, res
|
||||
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, 12, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -326,6 +350,9 @@ func (r *S3Resource) Update(ctx context.Context, req resource.UpdateRequest, res
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -365,7 +392,6 @@ func (r *S3Resource) Delete(ctx context.Context, req resource.DeleteRequest, res
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -375,6 +401,9 @@ func (r *S3Resource) Delete(ctx context.Context, req resource.DeleteRequest, res
|
||||
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
|
||||
|
||||
@@ -32,6 +32,7 @@ type S3SubUserModel struct {
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
SubUserAccess types.String `tfsdk:"sub_user_access"`
|
||||
SubUserName types.String `tfsdk:"sub_user_name"`
|
||||
}
|
||||
@@ -46,7 +47,7 @@ func (r *S3SubUserResource) Metadata(ctx context.Context, req resource.MetadataR
|
||||
|
||||
func (r *S3SubUserResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"s3_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
@@ -54,6 +55,7 @@ func (r *S3SubUserResource) Schema(ctx context.Context, req resource.SchemaReque
|
||||
"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(false)},
|
||||
"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."},
|
||||
"sub_user_access": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"sub_user_name": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
@@ -131,8 +133,28 @@ func (r *S3SubUserResource) Create(ctx context.Context, req resource.CreateReque
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_sub_user", params, operationTimeout); err != nil {
|
||||
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(err) {
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_sub_user", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_sub_user", 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, "sub_user", idParams))
|
||||
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 {
|
||||
@@ -149,7 +171,7 @@ func (r *S3SubUserResource) Create(ctx context.Context, req resource.CreateReque
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
|
||||
return
|
||||
}
|
||||
if targetValue != "" {
|
||||
@@ -214,6 +236,9 @@ func (r *S3SubUserResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -286,6 +311,9 @@ func (r *S3SubUserResource) Delete(ctx context.Context, req resource.DeleteReque
|
||||
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, "delete_sub_user", params, operationTimeout); err != nil {
|
||||
if resources_core.IsSubresourceMissingError(err) {
|
||||
if targetValue != "" {
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,7 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,7 @@ type S3bucketModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
BucketName types.String `tfsdk:"bucket_name"`
|
||||
CorsAll types.Bool `tfsdk:"cors_all"`
|
||||
ListAll types.Bool `tfsdk:"list_all"`
|
||||
@@ -59,16 +63,17 @@ func (r *S3bucketResource) Metadata(ctx context.Context, req resource.MetadataRe
|
||||
|
||||
func (r *S3bucketResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"bucket_name": schema.StringAttribute{Required: true, MarkdownDescription: "Имя бакета s3 Не изменяется после создания."},
|
||||
"cors_all": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Публичный доступ к чтению настроек позволяет любому пользователю интернета читать конфигурации CORS Не изменяется после создания."},
|
||||
"list_all": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Публичный доступ к листингу позволяет любому пользователю интернета получить список объектов в бакете. Не изменяется после создания."},
|
||||
"max_size": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("-1"), MarkdownDescription: "Максимальный размер бакета s3. Если поставить -1, будет безлимит. Размер указывается в Bytes Не изменяется после создания."},
|
||||
"placement": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("HOT"), MarkdownDescription: "Класс хранилища, который используется по умолчанию при загрузке объектов. Чем холоднее, тем дешевле место, но дороже обращения Не изменяется после создания."},
|
||||
"read_all": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Публичный доступ на чтение позволяет любому пользователю интернета скачивать объекты из бакета. Не изменяется после создания."},
|
||||
"s3_user_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UID Корневой услуги S3. На основе него будет создан бакет. Не изменяется после создания."},
|
||||
"s3_user_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UID Корневой услуги S3. На основе него будет создан бакет. Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -113,22 +118,11 @@ func (r *S3bucketResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.S3UserUid.IsNull() && !plan.S3UserUid.IsUnknown() {
|
||||
resolvedS3UserUid, err := r.client.ResolveRefSvcParamValue(ctx, 12, plan.S3UserUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve s3_user_uid", err.Error())
|
||||
} else if resolvedS3UserUid != "" && resolvedS3UserUid != plan.S3UserUid.ValueString() {
|
||||
plan.S3UserUid = types.StringValue(resolvedS3UserUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -136,7 +130,10 @@ func (r *S3bucketResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
}
|
||||
}
|
||||
if !plan.BucketName.IsNull() && !plan.BucketName.IsUnknown() && !state.BucketName.IsNull() && !state.BucketName.IsUnknown() {
|
||||
if plan.BucketName.ValueString() != state.BucketName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.BucketName.ValueString(), state.BucketName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить bucket_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -154,13 +151,19 @@ func (r *S3bucketResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
}
|
||||
}
|
||||
if !plan.MaxSize.IsNull() && !plan.MaxSize.IsUnknown() && !state.MaxSize.IsNull() && !state.MaxSize.IsUnknown() {
|
||||
if plan.MaxSize.ValueString() != state.MaxSize.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.MaxSize.ValueString(), state.MaxSize.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить max_size", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Placement.IsNull() && !plan.Placement.IsUnknown() && !state.Placement.IsNull() && !state.Placement.IsUnknown() {
|
||||
if plan.Placement.ValueString() != state.Placement.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Placement.ValueString(), state.Placement.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить placement", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -172,21 +175,30 @@ func (r *S3bucketResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
}
|
||||
}
|
||||
if !plan.S3UserUid.IsNull() && !plan.S3UserUid.IsUnknown() && !state.S3UserUid.IsNull() && !state.S3UserUid.IsUnknown() {
|
||||
if plan.S3UserUid.ValueString() != state.S3UserUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.S3UserUid.ValueString(), state.S3UserUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить s3_user_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.BucketName.IsNull() || config.BucketName.IsUnknown() {
|
||||
if config.BucketName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "bucket_name is required.")
|
||||
return
|
||||
}
|
||||
if config.S3UserUid.IsNull() || config.S3UserUid.IsUnknown() {
|
||||
if config.BucketName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.S3UserUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "s3_user_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.S3UserUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -212,8 +224,10 @@ func (r *S3bucketResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
129: resources_core.FormatBool(config.CorsAll),
|
||||
130: resources_core.FormatString(config.Placement),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 13, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 13, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *S3bucketResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -232,7 +246,9 @@ func (r *S3bucketResource) Create(ctx context.Context, req resource.CreateReques
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 13, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 13, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
124: resources_core.FormatString(data.S3UserUid),
|
||||
@@ -248,6 +264,9 @@ func (r *S3bucketResource) Create(ctx context.Context, req resource.CreateReques
|
||||
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, 13, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -276,6 +295,11 @@ func (r *S3bucketResource) Create(ctx context.Context, req resource.CreateReques
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -322,6 +346,11 @@ func (r *S3bucketResource) Read(ctx context.Context, req resource.ReadRequest, r
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -366,6 +395,9 @@ func (r *S3bucketResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -393,6 +425,10 @@ func (r *S3bucketResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -406,16 +442,15 @@ func (r *S3bucketResource) Delete(ctx context.Context, req resource.DeleteReques
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,6 +34,7 @@ type ValoTenantModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
S3Storage types.Int64 `tfsdk:"s3_storage"`
|
||||
@@ -57,9 +61,10 @@ func (r *ValoTenantResource) Metadata(ctx context.Context, req resource.Metadata
|
||||
|
||||
func (r *ValoTenantResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"domain": schema.StringAttribute{Required: true, MarkdownDescription: "Будет добавлено в зону valo.ngcloud.ru Не изменяется после создания."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"s3_storage": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(10), MarkdownDescription: "Параметр указывается в `Gb`"},
|
||||
@@ -115,27 +120,39 @@ func (r *ValoTenantResource) ModifyPlan(ctx context.Context, req resource.Modify
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Domain.ValueString(), state.Domain.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.Domain.IsNull() || config.Domain.IsUnknown() {
|
||||
if config.Domain.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "domain is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.Domain.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -150,8 +167,13 @@ func (r *ValoTenantResource) ModifyPlan(ctx context.Context, req resource.Modify
|
||||
657: resources_core.FormatString(config.ValoOptions),
|
||||
658: resources_core.FormatInt64(config.S3Storage),
|
||||
}
|
||||
desiredDomain := ""
|
||||
if !config.Domain.IsNull() && !config.Domain.IsUnknown() {
|
||||
desiredDomain = config.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 149, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 149, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *ValoTenantResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -170,7 +192,12 @@ func (r *ValoTenantResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 149, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||||
desiredDomain = data.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 149, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
655: resources_core.FormatString(data.ResourceRealm),
|
||||
@@ -183,6 +210,9 @@ func (r *ValoTenantResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
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, 149, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -280,7 +310,7 @@ func (r *ValoTenantResource) Update(ctx context.Context, req resource.UpdateRequ
|
||||
if plan.ValoOptions.IsNull() != state.ValoOptions.IsNull() || plan.ValoOptions.IsUnknown() != state.ValoOptions.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ValoOptions.IsNull() && !plan.ValoOptions.IsUnknown() && !state.ValoOptions.IsNull() && !state.ValoOptions.IsUnknown() {
|
||||
if plan.ValoOptions.ValueString() != state.ValoOptions.ValueString() {
|
||||
if !strings.EqualFold(plan.ValoOptions.ValueString(), state.ValoOptions.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -318,6 +348,9 @@ func (r *ValoTenantResource) Update(ctx context.Context, req resource.UpdateRequ
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -356,7 +389,6 @@ func (r *ValoTenantResource) Delete(ctx context.Context, req resource.DeleteRequ
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -366,6 +398,9 @@ func (r *ValoTenantResource) Delete(ctx context.Context, req resource.DeleteRequ
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -27,7 +28,9 @@ type K8sSthutrvalClusterReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
K8sSthutrvalClusterID types.String `tfsdk:"k8s_sthutrval_cluster_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewK8sSthutrvalClusterReconcileResource() resource.Resource {
|
||||
@@ -43,7 +46,9 @@ func (r *K8sSthutrvalClusterReconcileResource) Schema(ctx context.Context, req r
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"k8s_sthutrval_cluster_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -68,6 +73,9 @@ func (r *K8sSthutrvalClusterReconcileResource) Create(ctx context.Context, req r
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,8 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -28,33 +30,25 @@ type K8sSthutrvalClusterResource struct {
|
||||
}
|
||||
|
||||
type K8sSthutrvalClusterModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
AccessIpListAPI types.String `tfsdk:"access_ip_list_a_p_i"`
|
||||
AccessIpListIngress types.String `tfsdk:"access_ip_list_ingress"`
|
||||
AppVersion types.String `tfsdk:"app_version"`
|
||||
ClusterName types.String `tfsdk:"cluster_name"`
|
||||
ControlPlaneCount types.Int64 `tfsdk:"control_plane_count"`
|
||||
ControlPlaneSizingDisk types.Int64 `tfsdk:"control_plane_sizing_disk"`
|
||||
ControlPlaneSizingPolicy types.String `tfsdk:"control_plane_sizing_policy"`
|
||||
NeedExternalAddressAPI types.Bool `tfsdk:"need_external_address_a_p_i"`
|
||||
NeedExternalAddressIngress types.Bool `tfsdk:"need_external_address_ingress"`
|
||||
NsxtUid types.String `tfsdk:"nsxt_uid"`
|
||||
VdcUid types.String `tfsdk:"vdc_uid"`
|
||||
WorkersCount types.Int64 `tfsdk:"workers_count"`
|
||||
WorkersSizingDisk types.Int64 `tfsdk:"workers_sizing_disk"`
|
||||
WorkersSizingPolicy types.String `tfsdk:"workers_sizing_policy"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
ControlPlaneConfiguration types.String `tfsdk:"control_plane_configuration"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
WorkerConfiguration types.String `tfsdk:"worker_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
}
|
||||
|
||||
func NewK8sSthutrvalClusterResource() resource.Resource {
|
||||
@@ -67,33 +61,25 @@ func (r *K8sSthutrvalClusterResource) Metadata(ctx context.Context, req resource
|
||||
|
||||
func (r *K8sSthutrvalClusterResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"access_ip_list_a_p_i": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[]"), MarkdownDescription: "Access IP List для подключения к Kubernetes API. Указывается массив. Если передан пустой массив, доступ выделяется всем"},
|
||||
"access_ip_list_ingress": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[]"), MarkdownDescription: "Access IP List для подключения к Ingress. Указывается массив. Если передан пустой массив, доступ выделяется всем"},
|
||||
"app_version": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("2.12.1"), MarkdownDescription: "Версия Штурвал (не является версией Kubernetes)"},
|
||||
"cluster_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("example1"), MarkdownDescription: "Наименование кластера Kubernetes. Имя будет использоваться в fqdn (svc.$CLUSTER_NAME) Не изменяется после создания."},
|
||||
"control_plane_count": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1)},
|
||||
"control_plane_sizing_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(30), MarkdownDescription: "Настройка Control Plane. Disk (Gb)"},
|
||||
"control_plane_sizing_policy": schema.StringAttribute{Required: true, MarkdownDescription: "Настройка Control Plane. CPU (шт) / MEM (gb).<br/>Наверное, должна подтягиваться из свойств ресурсной платформы Не изменяется после создания."},
|
||||
"need_external_address_a_p_i": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true), MarkdownDescription: "Выделяет внешний IP для доступа к kubernetes API. Пока нельзя выставить false Не изменяется после создания."},
|
||||
"need_external_address_ingress": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true), MarkdownDescription: "Выделяет внешний IP для доступа к Ingress Не изменяется после создания."},
|
||||
"nsxt_uid": schema.StringAttribute{Required: true, MarkdownDescription: "InstanceUid nsxt. Nsxt должен быть создан Не изменяется после создания."},
|
||||
"vdc_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UI display_name for service_id=21 (mapped to UUID in core) Не изменяется после создания."},
|
||||
"workers_count": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(2), MarkdownDescription: "Настройка workers. Кол-во нод"},
|
||||
"workers_sizing_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(40), MarkdownDescription: "Настройка workers. Disk (Gb)"},
|
||||
"workers_sizing_policy": schema.StringAttribute{Required: true, MarkdownDescription: "Настройка workers. CPU (шт) / MEM (gb) Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
"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."},
|
||||
"access_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"control_plane_configuration": schema.StringAttribute{Required: true},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"worker_configuration": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{}]")},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
}
|
||||
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
@@ -128,95 +114,49 @@ func (r *K8sSthutrvalClusterResource) ModifyPlan(ctx context.Context, req resour
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.NsxtUid.IsNull() && !plan.NsxtUid.IsUnknown() {
|
||||
resolvedNsxtUid, err := r.client.ResolveRefSvcParamValue(ctx, 22, plan.NsxtUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve nsxt_uid", err.Error())
|
||||
} else if resolvedNsxtUid != "" && resolvedNsxtUid != plan.NsxtUid.ValueString() {
|
||||
plan.NsxtUid = types.StringValue(resolvedNsxtUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, plan.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vdc_uid", err.Error())
|
||||
} else if resolvedVdcUid != "" && resolvedVdcUid != plan.VdcUid.ValueString() {
|
||||
plan.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
if !plan.ClusterName.IsNull() && !plan.ClusterName.IsUnknown() && !state.ClusterName.IsNull() && !state.ClusterName.IsUnknown() {
|
||||
if plan.ClusterName.ValueString() != state.ClusterName.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить cluster_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ControlPlaneSizingPolicy.IsNull() && !plan.ControlPlaneSizingPolicy.IsUnknown() && !state.ControlPlaneSizingPolicy.IsNull() && !state.ControlPlaneSizingPolicy.IsUnknown() {
|
||||
if plan.ControlPlaneSizingPolicy.ValueString() != state.ControlPlaneSizingPolicy.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить control_plane_sizing_policy", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.NeedExternalAddressAPI.IsNull() && !plan.NeedExternalAddressAPI.IsUnknown() && !state.NeedExternalAddressAPI.IsNull() && !state.NeedExternalAddressAPI.IsUnknown() {
|
||||
if plan.NeedExternalAddressAPI.ValueBool() != state.NeedExternalAddressAPI.ValueBool() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить need_external_address_a_p_i", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.NeedExternalAddressIngress.IsNull() && !plan.NeedExternalAddressIngress.IsUnknown() && !state.NeedExternalAddressIngress.IsNull() && !state.NeedExternalAddressIngress.IsUnknown() {
|
||||
if plan.NeedExternalAddressIngress.ValueBool() != state.NeedExternalAddressIngress.ValueBool() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить need_external_address_ingress", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.NsxtUid.IsNull() && !plan.NsxtUid.IsUnknown() && !state.NsxtUid.IsNull() && !state.NsxtUid.IsUnknown() {
|
||||
if plan.NsxtUid.ValueString() != state.NsxtUid.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить nsxt_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if plan.VdcUid.ValueString() != state.VdcUid.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vdc_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.WorkersSizingPolicy.IsNull() && !plan.WorkersSizingPolicy.IsUnknown() && !state.WorkersSizingPolicy.IsNull() && !state.WorkersSizingPolicy.IsUnknown() {
|
||||
if plan.WorkersSizingPolicy.ValueString() != state.WorkersSizingPolicy.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить workers_sizing_policy", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ControlPlaneSizingPolicy.IsNull() || config.ControlPlaneSizingPolicy.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "control_plane_sizing_policy is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.NsxtUid.IsNull() || config.NsxtUid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "nsxt_uid is required.")
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.VdcUid.IsNull() || config.VdcUid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vdc_uid is required.")
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.WorkersSizingPolicy.IsNull() || config.WorkersSizingPolicy.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "workers_sizing_policy is required.")
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ControlPlaneConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "control_plane_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ControlPlaneConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -227,40 +167,17 @@ func (r *K8sSthutrvalClusterResource) ModifyPlan(ctx context.Context, req resour
|
||||
if !config.AdoptExistingOnCreate.IsNull() && !config.AdoptExistingOnCreate.IsUnknown() {
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
if !config.NsxtUid.IsNull() && !config.NsxtUid.IsUnknown() {
|
||||
resolvedNsxtUid, err := r.client.ResolveRefSvcParamValue(ctx, 22, config.NsxtUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve nsxt_uid", err.Error())
|
||||
} else if resolvedNsxtUid != "" && resolvedNsxtUid != config.NsxtUid.ValueString() {
|
||||
config.NsxtUid = types.StringValue(resolvedNsxtUid)
|
||||
}
|
||||
}
|
||||
if !config.VdcUid.IsNull() && !config.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, config.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vdc_uid", err.Error())
|
||||
} else if resolvedVdcUid != "" && resolvedVdcUid != config.VdcUid.ValueString() {
|
||||
config.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
params := map[int]string{
|
||||
344: resources_core.FormatString(config.NsxtUid),
|
||||
345: resources_core.FormatString(config.ClusterName),
|
||||
346: resources_core.FormatString(config.ControlPlaneSizingPolicy),
|
||||
347: resources_core.FormatInt64(config.ControlPlaneSizingDisk),
|
||||
348: resources_core.FormatInt64(config.ControlPlaneCount),
|
||||
349: resources_core.FormatString(config.WorkersSizingPolicy),
|
||||
350: resources_core.FormatInt64(config.WorkersSizingDisk),
|
||||
351: resources_core.FormatInt64(config.WorkersCount),
|
||||
352: resources_core.FormatString(config.AppVersion),
|
||||
353: resources_core.FormatBool(config.NeedExternalAddressAPI),
|
||||
354: resources_core.FormatString(config.AccessIpListAPI),
|
||||
355: resources_core.FormatBool(config.NeedExternalAddressIngress),
|
||||
356: resources_core.FormatString(config.AccessIpListIngress),
|
||||
747: resources_core.FormatString(config.VdcUid),
|
||||
750: resources_core.FormatString(config.StartupConfiguration),
|
||||
751: resources_core.FormatString(config.ControlPlaneConfiguration),
|
||||
752: resources_core.FormatString(config.WorkerConfiguration),
|
||||
753: resources_core.FormatString(config.AccessConfiguration),
|
||||
755: resources_core.FormatString(config.ClusterConfiguration),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 150, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 150, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *K8sSthutrvalClusterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -269,67 +186,43 @@ func (r *K8sSthutrvalClusterResource) Create(ctx context.Context, req resource.C
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ControlPlaneSizingPolicy.IsNull() || data.ControlPlaneSizingPolicy.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "control_plane_sizing_policy is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.NsxtUid.IsNull() || data.NsxtUid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "nsxt_uid is required.")
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.VdcUid.IsNull() || data.VdcUid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vdc_uid is required.")
|
||||
if data.ControlPlaneConfiguration.IsNull() || data.ControlPlaneConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "control_plane_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.WorkersSizingPolicy.IsNull() || data.WorkersSizingPolicy.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "workers_sizing_policy is required.")
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if !data.NsxtUid.IsNull() && !data.NsxtUid.IsUnknown() {
|
||||
resolvedNsxtUid, err := r.client.ResolveRefSvcParamValue(ctx, 22, data.NsxtUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
if resolvedNsxtUid != "" && resolvedNsxtUid != data.NsxtUid.ValueString() {
|
||||
data.NsxtUid = types.StringValue(resolvedNsxtUid)
|
||||
}
|
||||
}
|
||||
if !data.VdcUid.IsNull() && !data.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, data.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
if resolvedVdcUid != "" && resolvedVdcUid != data.VdcUid.ValueString() {
|
||||
data.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 150, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 150, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
344: resources_core.FormatString(data.NsxtUid),
|
||||
345: resources_core.FormatString(data.ClusterName),
|
||||
346: resources_core.FormatString(data.ControlPlaneSizingPolicy),
|
||||
347: resources_core.FormatInt64(data.ControlPlaneSizingDisk),
|
||||
348: resources_core.FormatInt64(data.ControlPlaneCount),
|
||||
349: resources_core.FormatString(data.WorkersSizingPolicy),
|
||||
350: resources_core.FormatInt64(data.WorkersSizingDisk),
|
||||
351: resources_core.FormatInt64(data.WorkersCount),
|
||||
352: resources_core.FormatString(data.AppVersion),
|
||||
353: resources_core.FormatBool(data.NeedExternalAddressAPI),
|
||||
354: resources_core.FormatString(data.AccessIpListAPI),
|
||||
355: resources_core.FormatBool(data.NeedExternalAddressIngress),
|
||||
356: resources_core.FormatString(data.AccessIpListIngress),
|
||||
747: resources_core.FormatString(data.VdcUid),
|
||||
750: resources_core.FormatString(data.StartupConfiguration),
|
||||
751: resources_core.FormatString(data.ControlPlaneConfiguration),
|
||||
752: resources_core.FormatString(data.WorkerConfiguration),
|
||||
753: resources_core.FormatString(data.AccessConfiguration),
|
||||
755: resources_core.FormatString(data.ClusterConfiguration),
|
||||
}
|
||||
|
||||
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, 150, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -347,41 +240,16 @@ func (r *K8sSthutrvalClusterResource) Create(ctx context.Context, req resource.C
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "accessIpListAPI", Field: "AccessIpListAPI", Type: "string"},
|
||||
{Code: "accessIpListIngress", Field: "AccessIpListIngress", Type: "string"},
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "clusterName", Field: "ClusterName", Type: "string"},
|
||||
{Code: "controlPlaneCount", Field: "ControlPlaneCount", Type: "int64"},
|
||||
{Code: "controlPlaneSizingDisk", Field: "ControlPlaneSizingDisk", Type: "int64"},
|
||||
{Code: "controlPlaneSizingPolicy", Field: "ControlPlaneSizingPolicy", Type: "string"},
|
||||
{Code: "needExternalAddressAPI", Field: "NeedExternalAddressAPI", Type: "bool"},
|
||||
{Code: "needExternalAddressIngress", Field: "NeedExternalAddressIngress", Type: "bool"},
|
||||
{Code: "nsxtUid", Field: "NsxtUid", Type: "string"},
|
||||
{Code: "vdcUid", Field: "VdcUid", Type: "string"},
|
||||
{Code: "workersCount", Field: "WorkersCount", Type: "int64"},
|
||||
{Code: "workersSizingDisk", Field: "WorkersSizingDisk", Type: "int64"},
|
||||
{Code: "workersSizingPolicy", Field: "WorkersSizingPolicy", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "controlPlaneConfiguration", Field: "ControlPlaneConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
{Code: "workerConfiguration", Field: "WorkerConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if !state.NsxtUid.IsNull() && !state.NsxtUid.IsUnknown() {
|
||||
resolvedNsxtUid, err := r.client.ResolveRefSvcParamValue(ctx, 22, state.NsxtUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve nsxt_uid", err.Error())
|
||||
} else if resolvedNsxtUid != "" && resolvedNsxtUid != state.NsxtUid.ValueString() {
|
||||
state.NsxtUid = types.StringValue(resolvedNsxtUid)
|
||||
}
|
||||
}
|
||||
if !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, state.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vdc_uid", err.Error())
|
||||
} else if resolvedVdcUid != "" && resolvedVdcUid != state.VdcUid.ValueString() {
|
||||
state.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -417,41 +285,16 @@ func (r *K8sSthutrvalClusterResource) Read(ctx context.Context, req resource.Rea
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "accessIpListAPI", Field: "AccessIpListAPI", Type: "string"},
|
||||
{Code: "accessIpListIngress", Field: "AccessIpListIngress", Type: "string"},
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "clusterName", Field: "ClusterName", Type: "string"},
|
||||
{Code: "controlPlaneCount", Field: "ControlPlaneCount", Type: "int64"},
|
||||
{Code: "controlPlaneSizingDisk", Field: "ControlPlaneSizingDisk", Type: "int64"},
|
||||
{Code: "controlPlaneSizingPolicy", Field: "ControlPlaneSizingPolicy", Type: "string"},
|
||||
{Code: "needExternalAddressAPI", Field: "NeedExternalAddressAPI", Type: "bool"},
|
||||
{Code: "needExternalAddressIngress", Field: "NeedExternalAddressIngress", Type: "bool"},
|
||||
{Code: "nsxtUid", Field: "NsxtUid", Type: "string"},
|
||||
{Code: "vdcUid", Field: "VdcUid", Type: "string"},
|
||||
{Code: "workersCount", Field: "WorkersCount", Type: "int64"},
|
||||
{Code: "workersSizingDisk", Field: "WorkersSizingDisk", Type: "int64"},
|
||||
{Code: "workersSizingPolicy", Field: "WorkersSizingPolicy", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "controlPlaneConfiguration", Field: "ControlPlaneConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
{Code: "workerConfiguration", Field: "WorkerConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if !newState.NsxtUid.IsNull() && !newState.NsxtUid.IsUnknown() {
|
||||
resolvedNsxtUid, err := r.client.ResolveRefSvcParamValue(ctx, 22, newState.NsxtUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve nsxt_uid", err.Error())
|
||||
} else if resolvedNsxtUid != "" && resolvedNsxtUid != newState.NsxtUid.ValueString() {
|
||||
newState.NsxtUid = types.StringValue(resolvedNsxtUid)
|
||||
}
|
||||
}
|
||||
if !newState.VdcUid.IsNull() && !newState.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, newState.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vdc_uid", err.Error())
|
||||
} else if resolvedVdcUid != "" && resolvedVdcUid != newState.VdcUid.ValueString() {
|
||||
newState.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -476,64 +319,37 @@ func (r *K8sSthutrvalClusterResource) Update(ctx context.Context, req resource.U
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ControlPlaneCount.IsNull() != state.ControlPlaneCount.IsNull() || plan.ControlPlaneCount.IsUnknown() != state.ControlPlaneCount.IsUnknown() {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ControlPlaneCount.IsNull() && !plan.ControlPlaneCount.IsUnknown() && !state.ControlPlaneCount.IsNull() && !state.ControlPlaneCount.IsUnknown() {
|
||||
if plan.ControlPlaneCount.ValueInt64() != state.ControlPlaneCount.ValueInt64() {
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.WorkersCount.IsNull() != state.WorkersCount.IsNull() || plan.WorkersCount.IsUnknown() != state.WorkersCount.IsUnknown() {
|
||||
if plan.ControlPlaneConfiguration.IsNull() != state.ControlPlaneConfiguration.IsNull() || plan.ControlPlaneConfiguration.IsUnknown() != state.ControlPlaneConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.WorkersCount.IsNull() && !plan.WorkersCount.IsUnknown() && !state.WorkersCount.IsNull() && !state.WorkersCount.IsUnknown() {
|
||||
if plan.WorkersCount.ValueInt64() != state.WorkersCount.ValueInt64() {
|
||||
} else if !plan.ControlPlaneConfiguration.IsNull() && !plan.ControlPlaneConfiguration.IsUnknown() && !state.ControlPlaneConfiguration.IsNull() && !state.ControlPlaneConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ControlPlaneConfiguration.ValueString(), state.ControlPlaneConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AccessIpListAPI.IsNull() != state.AccessIpListAPI.IsNull() || plan.AccessIpListAPI.IsUnknown() != state.AccessIpListAPI.IsUnknown() {
|
||||
if plan.WorkerConfiguration.IsNull() != state.WorkerConfiguration.IsNull() || plan.WorkerConfiguration.IsUnknown() != state.WorkerConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AccessIpListAPI.IsNull() && !plan.AccessIpListAPI.IsUnknown() && !state.AccessIpListAPI.IsNull() && !state.AccessIpListAPI.IsUnknown() {
|
||||
if plan.AccessIpListAPI.ValueString() != state.AccessIpListAPI.ValueString() {
|
||||
} else if !plan.WorkerConfiguration.IsNull() && !plan.WorkerConfiguration.IsUnknown() && !state.WorkerConfiguration.IsNull() && !state.WorkerConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.WorkerConfiguration.ValueString(), state.WorkerConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AccessIpListIngress.IsNull() != state.AccessIpListIngress.IsNull() || plan.AccessIpListIngress.IsUnknown() != state.AccessIpListIngress.IsUnknown() {
|
||||
if plan.AccessConfiguration.IsNull() != state.AccessConfiguration.IsNull() || plan.AccessConfiguration.IsUnknown() != state.AccessConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AccessIpListIngress.IsNull() && !plan.AccessIpListIngress.IsUnknown() && !state.AccessIpListIngress.IsNull() && !state.AccessIpListIngress.IsUnknown() {
|
||||
if plan.AccessIpListIngress.ValueString() != state.AccessIpListIngress.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AppVersion.IsNull() != state.AppVersion.IsNull() || plan.AppVersion.IsUnknown() != state.AppVersion.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AppVersion.IsNull() && !plan.AppVersion.IsUnknown() && !state.AppVersion.IsNull() && !state.AppVersion.IsUnknown() {
|
||||
if plan.AppVersion.ValueString() != state.AppVersion.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ControlPlaneSizingDisk.IsNull() != state.ControlPlaneSizingDisk.IsNull() || plan.ControlPlaneSizingDisk.IsUnknown() != state.ControlPlaneSizingDisk.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ControlPlaneSizingDisk.IsNull() && !plan.ControlPlaneSizingDisk.IsUnknown() && !state.ControlPlaneSizingDisk.IsNull() && !state.ControlPlaneSizingDisk.IsUnknown() {
|
||||
if plan.ControlPlaneSizingDisk.ValueInt64() != state.ControlPlaneSizingDisk.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.WorkersSizingDisk.IsNull() != state.WorkersSizingDisk.IsNull() || plan.WorkersSizingDisk.IsUnknown() != state.WorkersSizingDisk.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.WorkersSizingDisk.IsNull() && !plan.WorkersSizingDisk.IsUnknown() && !state.WorkersSizingDisk.IsNull() && !state.WorkersSizingDisk.IsUnknown() {
|
||||
if plan.WorkersSizingDisk.ValueInt64() != state.WorkersSizingDisk.ValueInt64() {
|
||||
} else if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -554,19 +370,19 @@ func (r *K8sSthutrvalClusterResource) Update(ctx context.Context, req resource.U
|
||||
}
|
||||
|
||||
params := map[int]string{
|
||||
362: resources_core.FormatInt64(plan.ControlPlaneCount),
|
||||
363: resources_core.FormatInt64(plan.WorkersCount),
|
||||
364: resources_core.FormatString(plan.AccessIpListAPI),
|
||||
365: resources_core.FormatString(plan.AccessIpListIngress),
|
||||
563: resources_core.FormatString(plan.AppVersion),
|
||||
637: resources_core.FormatInt64(plan.ControlPlaneSizingDisk),
|
||||
638: resources_core.FormatInt64(plan.WorkersSizingDisk),
|
||||
756: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
757: resources_core.FormatString(plan.ControlPlaneConfiguration),
|
||||
758: resources_core.FormatString(plan.WorkerConfiguration),
|
||||
759: resources_core.FormatString(plan.AccessConfiguration),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -583,41 +399,16 @@ func (r *K8sSthutrvalClusterResource) Update(ctx context.Context, req resource.U
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "accessIpListAPI", Field: "AccessIpListAPI", Type: "string"},
|
||||
{Code: "accessIpListIngress", Field: "AccessIpListIngress", Type: "string"},
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "clusterName", Field: "ClusterName", Type: "string"},
|
||||
{Code: "controlPlaneCount", Field: "ControlPlaneCount", Type: "int64"},
|
||||
{Code: "controlPlaneSizingDisk", Field: "ControlPlaneSizingDisk", Type: "int64"},
|
||||
{Code: "controlPlaneSizingPolicy", Field: "ControlPlaneSizingPolicy", Type: "string"},
|
||||
{Code: "needExternalAddressAPI", Field: "NeedExternalAddressAPI", Type: "bool"},
|
||||
{Code: "needExternalAddressIngress", Field: "NeedExternalAddressIngress", Type: "bool"},
|
||||
{Code: "nsxtUid", Field: "NsxtUid", Type: "string"},
|
||||
{Code: "vdcUid", Field: "VdcUid", Type: "string"},
|
||||
{Code: "workersCount", Field: "WorkersCount", Type: "int64"},
|
||||
{Code: "workersSizingDisk", Field: "WorkersSizingDisk", Type: "int64"},
|
||||
{Code: "workersSizingPolicy", Field: "WorkersSizingPolicy", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "controlPlaneConfiguration", Field: "ControlPlaneConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
{Code: "workerConfiguration", Field: "WorkerConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if !state.NsxtUid.IsNull() && !state.NsxtUid.IsUnknown() {
|
||||
resolvedNsxtUid, err := r.client.ResolveRefSvcParamValue(ctx, 22, state.NsxtUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve nsxt_uid", err.Error())
|
||||
} else if resolvedNsxtUid != "" && resolvedNsxtUid != state.NsxtUid.ValueString() {
|
||||
state.NsxtUid = types.StringValue(resolvedNsxtUid)
|
||||
}
|
||||
}
|
||||
if !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, state.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vdc_uid", err.Error())
|
||||
} else if resolvedVdcUid != "" && resolvedVdcUid != state.VdcUid.ValueString() {
|
||||
state.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -631,7 +422,6 @@ func (r *K8sSthutrvalClusterResource) Delete(ctx context.Context, req resource.D
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -641,6 +431,9 @@ func (r *K8sSthutrvalClusterResource) Delete(ctx context.Context, req resource.D
|
||||
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
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: k8s_sthutrval_cluster
|
||||
// Subresource: user
|
||||
|
||||
var _ resource.Resource = &K8sSthutrvalClusterUserResource{}
|
||||
|
||||
// K8sSthutrvalClusterUserResource manages a subresource via operations.
|
||||
type K8sSthutrvalClusterUserResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type K8sSthutrvalClusterUserModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
K8sSthutrvalClusterID types.String `tfsdk:"k8s_sthutrval_cluster_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Admins types.String `tfsdk:"admins"`
|
||||
}
|
||||
|
||||
func NewK8sSthutrvalClusterUserResource() resource.Resource {
|
||||
return &K8sSthutrvalClusterUserResource{}
|
||||
}
|
||||
|
||||
func (r *K8sSthutrvalClusterUserResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_k8s_sthutrval_cluster_user"
|
||||
}
|
||||
|
||||
func (r *K8sSthutrvalClusterUserResource) 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()}},
|
||||
"k8s_sthutrval_cluster_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(false)},
|
||||
"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."},
|
||||
"admins": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{}]"), MarkdownDescription: "Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *K8sSthutrvalClusterUserResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan K8sSthutrvalClusterUserModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.K8sSthutrvalClusterID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"admins": resources_core.FormatString(plan.Admins),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"admins",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("user", 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, "user", idParams))
|
||||
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{
|
||||
"admins": resources_core.FormatString(plan.Admins),
|
||||
})
|
||||
|
||||
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, "create_user", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", 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, "user", idParams))
|
||||
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 known && found {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "user", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект подтверждён в state_out, выполняется усыновление")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "user", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *K8sSthutrvalClusterUserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state K8sSthutrvalClusterUserModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *K8sSthutrvalClusterUserResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan K8sSthutrvalClusterUserModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
var state K8sSthutrvalClusterUserModel
|
||||
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.K8sSthutrvalClusterID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{
|
||||
"admins": resources_core.FormatString(plan.Admins),
|
||||
}
|
||||
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "user", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *K8sSthutrvalClusterUserResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state K8sSthutrvalClusterUserModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "delete_user" == "" {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(state.K8sSthutrvalClusterID.ValueString())
|
||||
if instanceUID == "" {
|
||||
return
|
||||
}
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"admins": resources_core.FormatString(state.Admins),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"admins",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("user", 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 skipMissingOnDelete {
|
||||
resp.Diagnostics.AddWarning("Подресурс не найден", "Объект отсутствует и будет пропущен: "+targetValue)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Подресурс не найден", "Объект отсутствует: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{
|
||||
"admins": resources_core.FormatString(state.Admins),
|
||||
})
|
||||
|
||||
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, "delete_user", 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 *K8sSthutrvalClusterUserResource) 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
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,12 @@ type VcOrgReconcileResource struct {
|
||||
}
|
||||
|
||||
type VcOrgReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcOrgID types.String `tfsdk:"vc_org_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcOrgID types.String `tfsdk:"vc_org_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewVcOrgReconcileResource() resource.Resource {
|
||||
@@ -40,10 +43,12 @@ func (r *VcOrgReconcileResource) Metadata(ctx context.Context, req resource.Meta
|
||||
|
||||
func (r *VcOrgReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_org_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_org_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -68,6 +73,9 @@ func (r *VcOrgReconcileResource) Create(ctx context.Context, req resource.Create
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,7 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,7 @@ type VcOrgModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
OrganizationType types.String `tfsdk:"organization_type"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
VIPConfigure types.String `tfsdk:"v_i_p_configure"`
|
||||
@@ -55,9 +59,10 @@ func (r *VcOrgResource) Metadata(ctx context.Context, req resource.MetadataReque
|
||||
|
||||
func (r *VcOrgResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"organization_type": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("iaas"), MarkdownDescription: "Доступ к организации предоставляется только при выборе `iaas` Не изменяется после создания."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Облако в котором будет развёрнут тенант Не изменяется после создания."},
|
||||
"v_i_p_configure": schema.StringAttribute{Required: true},
|
||||
@@ -112,27 +117,39 @@ func (r *VcOrgResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
}
|
||||
}
|
||||
if !plan.OrganizationType.IsNull() && !plan.OrganizationType.IsUnknown() && !state.OrganizationType.IsNull() && !state.OrganizationType.IsUnknown() {
|
||||
if plan.OrganizationType.ValueString() != state.OrganizationType.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.OrganizationType.ValueString(), state.OrganizationType.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить organization_type", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.VIPConfigure.IsNull() || config.VIPConfigure.IsUnknown() {
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.VIPConfigure.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "v_i_p_configure is required.")
|
||||
return
|
||||
}
|
||||
if config.VIPConfigure.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -145,8 +162,10 @@ func (r *VcOrgResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
418: resources_core.FormatString(config.ResourceRealm),
|
||||
556: resources_core.FormatString(config.OrganizationType),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 19, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 19, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcOrgResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -165,7 +184,9 @@ func (r *VcOrgResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 19, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 19, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
418: resources_core.FormatString(data.ResourceRealm),
|
||||
@@ -176,6 +197,9 @@ func (r *VcOrgResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
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, 19, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -271,7 +295,7 @@ func (r *VcOrgResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.VIPConfigure.IsNull() != state.VIPConfigure.IsNull() || plan.VIPConfigure.IsUnknown() != state.VIPConfigure.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.VIPConfigure.IsNull() && !plan.VIPConfigure.IsUnknown() && !state.VIPConfigure.IsNull() && !state.VIPConfigure.IsUnknown() {
|
||||
if plan.VIPConfigure.ValueString() != state.VIPConfigure.ValueString() {
|
||||
if !strings.EqualFold(plan.VIPConfigure.ValueString(), state.VIPConfigure.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -299,6 +323,9 @@ func (r *VcOrgResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -336,7 +363,6 @@ func (r *VcOrgResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -346,6 +372,9 @@ func (r *VcOrgResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: vc_org
|
||||
// Subresource: user
|
||||
|
||||
var _ resource.Resource = &VcOrgUserResource{}
|
||||
|
||||
// VcOrgUserResource manages a subresource via operations.
|
||||
type VcOrgUserResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type VcOrgUserModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcOrgID types.String `tfsdk:"vc_org_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Admins types.String `tfsdk:"admins"`
|
||||
}
|
||||
|
||||
func NewVcOrgUserResource() resource.Resource {
|
||||
return &VcOrgUserResource{}
|
||||
}
|
||||
|
||||
func (r *VcOrgUserResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_vc_org_user"
|
||||
}
|
||||
|
||||
func (r *VcOrgUserResource) 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()}},
|
||||
"vc_org_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(false)},
|
||||
"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."},
|
||||
"admins": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{}]"), MarkdownDescription: "Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *VcOrgUserResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan VcOrgUserModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.VcOrgID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"admins": resources_core.FormatString(plan.Admins),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"admins",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("user", 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, "user", idParams))
|
||||
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{
|
||||
"admins": resources_core.FormatString(plan.Admins),
|
||||
})
|
||||
|
||||
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, "create_user", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", 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, "user", idParams))
|
||||
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 known && found {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "user", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект подтверждён в state_out, выполняется усыновление")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "user", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *VcOrgUserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state VcOrgUserModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *VcOrgUserResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan VcOrgUserModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
var state VcOrgUserModel
|
||||
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.VcOrgID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{
|
||||
"admins": resources_core.FormatString(plan.Admins),
|
||||
}
|
||||
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "user", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *VcOrgUserResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state VcOrgUserModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "delete_user" == "" {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(state.VcOrgID.ValueString())
|
||||
if instanceUID == "" {
|
||||
return
|
||||
}
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"admins": resources_core.FormatString(state.Admins),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"admins",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("user", 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 skipMissingOnDelete {
|
||||
resp.Diagnostics.AddWarning("Подресурс не найден", "Объект отсутствует и будет пропущен: "+targetValue)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Подресурс не найден", "Объект отсутствует: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{
|
||||
"admins": resources_core.FormatString(state.Admins),
|
||||
})
|
||||
|
||||
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, "delete_user", 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 *VcOrgUserResource) 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
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: dummy
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &DummyReconcileResource{}
|
||||
|
||||
// DummyReconcileResource triggers a one-time action.
|
||||
type DummyReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type DummyReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
DummyID types.String `tfsdk:"dummy_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewDummyReconcileResource() resource.Resource {
|
||||
return &DummyReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *DummyReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_dummy_reconcile"
|
||||
}
|
||||
|
||||
func (r *DummyReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"dummy_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *DummyReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan DummyReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.DummyID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *DummyReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state DummyReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *DummyReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan DummyReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.DummyID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *DummyReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *DummyReconcileResource) 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
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,12 @@ type DummyRedeployResource struct {
|
||||
}
|
||||
|
||||
type DummyRedeployModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
DummyID types.String `tfsdk:"dummy_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
DummyID types.String `tfsdk:"dummy_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewDummyRedeployResource() resource.Resource {
|
||||
@@ -40,10 +43,12 @@ func (r *DummyRedeployResource) Metadata(ctx context.Context, req resource.Metad
|
||||
|
||||
func (r *DummyRedeployResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"dummy_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"dummy_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -68,6 +73,9 @@ func (r *DummyRedeployResource) Create(ctx context.Context, req resource.CreateR
|
||||
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, "redeploy", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,8 @@ type DummyModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Arr types.String `tfsdk:"arr"`
|
||||
ArrayMapFixedExample types.String `tfsdk:"array_map_fixed_example"`
|
||||
Bodymessage types.String `tfsdk:"bodymessage"`
|
||||
DurationMs types.Int64 `tfsdk:"duration_ms"`
|
||||
@@ -41,7 +46,6 @@ type DummyModel struct {
|
||||
NestedRefExample types.String `tfsdk:"nested_ref_example"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
WhereFail types.Int64 `tfsdk:"where_fail"`
|
||||
YamlExample types.String `tfsdk:"yaml_example"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
@@ -64,21 +68,22 @@ func (r *DummyResource) Metadata(ctx context.Context, req resource.MetadataReque
|
||||
|
||||
func (r *DummyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"arr": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"array_map_fixed_example": schema.StringAttribute{Optional: true, MarkdownDescription: "Array of map-fixed Не изменяется после создания."},
|
||||
"bodymessage": schema.StringAttribute{Optional: true, MarkdownDescription: "Дата, уходящая в Vault"},
|
||||
"duration_ms": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(0), MarkdownDescription: "перед завершением подождать (sleep) столько миллисекунд"},
|
||||
"fail_at_start": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "если true, завершить джоб, ничего не записывая в cmdb"},
|
||||
"fail_in_progress": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "если true, корректно начать выполнение джоба, но в процессе завершить аварийно, ничего не записывая в cmdb"},
|
||||
"json_example": schema.StringAttribute{Optional: true, MarkdownDescription: "Для тестирования интерфейса"},
|
||||
"json_example": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Для тестирования интерфейса", PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()}},
|
||||
"map_example": schema.StringAttribute{Optional: true},
|
||||
"map_fixed": schema.StringAttribute{Optional: true, MarkdownDescription: "Новый тип поля Не изменяется после создания."},
|
||||
"nested_ref_example": schema.StringAttribute{Optional: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"where_fail": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "принимает 1,2,3. Работает с failInProgress. 1 - упасть в prepare. 2 - упасть внутри блока Data fill. 3 - упасть после vault write"},
|
||||
"yaml_example": schema.StringAttribute{Optional: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -129,42 +134,67 @@ func (r *DummyResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Arr.IsNull() && !plan.Arr.IsUnknown() && !state.Arr.IsNull() && !state.Arr.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Arr.ValueString(), state.Arr.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить arr", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ArrayMapFixedExample.IsNull() && !plan.ArrayMapFixedExample.IsUnknown() && !state.ArrayMapFixedExample.IsNull() && !state.ArrayMapFixedExample.IsUnknown() {
|
||||
if plan.ArrayMapFixedExample.ValueString() != state.ArrayMapFixedExample.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ArrayMapFixedExample.ValueString(), state.ArrayMapFixedExample.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить array_map_fixed_example", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.MapFixed.IsNull() && !plan.MapFixed.IsUnknown() && !state.MapFixed.IsNull() && !state.MapFixed.IsUnknown() {
|
||||
if plan.MapFixed.ValueString() != state.MapFixed.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.MapFixed.ValueString(), state.MapFixed.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить map_fixed", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.NestedRefExample.IsNull() && !plan.NestedRefExample.IsUnknown() && !state.NestedRefExample.IsNull() && !state.NestedRefExample.IsUnknown() {
|
||||
if plan.NestedRefExample.ValueString() != state.NestedRefExample.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.NestedRefExample.ValueString(), state.NestedRefExample.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить nested_ref_example", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.YamlExample.IsNull() && !plan.YamlExample.IsUnknown() && !state.YamlExample.IsNull() && !state.YamlExample.IsUnknown() {
|
||||
if plan.YamlExample.ValueString() != state.YamlExample.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить yaml_example", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.Arr.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "arr is required.")
|
||||
return
|
||||
}
|
||||
if config.Arr.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -183,12 +213,14 @@ func (r *DummyResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
321: resources_core.FormatString(config.MapExample),
|
||||
322: resources_core.FormatString(config.JsonExample),
|
||||
396: resources_core.FormatString(config.NestedRefExample),
|
||||
447: resources_core.FormatString(config.YamlExample),
|
||||
647: resources_core.FormatString(config.MapFixed),
|
||||
654: resources_core.FormatString(config.ArrayMapFixedExample),
|
||||
863: resources_core.FormatString(config.Arr),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 1, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 1, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *DummyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -197,13 +229,19 @@ func (r *DummyResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.Arr.IsNull() || data.Arr.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "arr is required.")
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 1, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 1, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
198: resources_core.FormatInt64(data.DurationMs),
|
||||
@@ -215,15 +253,18 @@ func (r *DummyResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
321: resources_core.FormatString(data.MapExample),
|
||||
322: resources_core.FormatString(data.JsonExample),
|
||||
396: resources_core.FormatString(data.NestedRefExample),
|
||||
447: resources_core.FormatString(data.YamlExample),
|
||||
647: resources_core.FormatString(data.MapFixed),
|
||||
654: resources_core.FormatString(data.ArrayMapFixedExample),
|
||||
863: resources_core.FormatString(data.Arr),
|
||||
}
|
||||
|
||||
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, 1, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -241,6 +282,7 @@ func (r *DummyResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "arr", Field: "Arr", Type: "string"},
|
||||
{Code: "arrayMapFixedExample", Field: "ArrayMapFixedExample", Type: "string"},
|
||||
{Code: "bodymessage", Field: "Bodymessage", Type: "string"},
|
||||
{Code: "durationMs", Field: "DurationMs", Type: "int64"},
|
||||
@@ -252,7 +294,6 @@ func (r *DummyResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
{Code: "nestedRefExample", Field: "NestedRefExample", Type: "string"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "whereFail", Field: "WhereFail", Type: "int64"},
|
||||
{Code: "yamlExample", Field: "YamlExample", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -293,6 +334,7 @@ func (r *DummyResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "arr", Field: "Arr", Type: "string"},
|
||||
{Code: "arrayMapFixedExample", Field: "ArrayMapFixedExample", Type: "string"},
|
||||
{Code: "bodymessage", Field: "Bodymessage", Type: "string"},
|
||||
{Code: "durationMs", Field: "DurationMs", Type: "int64"},
|
||||
@@ -304,7 +346,6 @@ func (r *DummyResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
{Code: "nestedRefExample", Field: "NestedRefExample", Type: "string"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "whereFail", Field: "WhereFail", Type: "int64"},
|
||||
{Code: "yamlExample", Field: "YamlExample", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -373,7 +414,7 @@ func (r *DummyResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.Bodymessage.IsNull() != state.Bodymessage.IsNull() || plan.Bodymessage.IsUnknown() != state.Bodymessage.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.Bodymessage.IsNull() && !plan.Bodymessage.IsUnknown() && !state.Bodymessage.IsNull() && !state.Bodymessage.IsUnknown() {
|
||||
if plan.Bodymessage.ValueString() != state.Bodymessage.ValueString() {
|
||||
if !strings.EqualFold(plan.Bodymessage.ValueString(), state.Bodymessage.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -382,7 +423,7 @@ func (r *DummyResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.MapExample.IsNull() != state.MapExample.IsNull() || plan.MapExample.IsUnknown() != state.MapExample.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.MapExample.IsNull() && !plan.MapExample.IsUnknown() && !state.MapExample.IsNull() && !state.MapExample.IsUnknown() {
|
||||
if plan.MapExample.ValueString() != state.MapExample.ValueString() {
|
||||
if !strings.EqualFold(plan.MapExample.ValueString(), state.MapExample.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -391,7 +432,7 @@ func (r *DummyResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.JsonExample.IsNull() != state.JsonExample.IsNull() || plan.JsonExample.IsUnknown() != state.JsonExample.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.JsonExample.IsNull() && !plan.JsonExample.IsUnknown() && !state.JsonExample.IsNull() && !state.JsonExample.IsUnknown() {
|
||||
if plan.JsonExample.ValueString() != state.JsonExample.ValueString() {
|
||||
if !strings.EqualFold(plan.JsonExample.ValueString(), state.JsonExample.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -425,6 +466,9 @@ func (r *DummyResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -441,6 +485,7 @@ func (r *DummyResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "arr", Field: "Arr", Type: "string"},
|
||||
{Code: "arrayMapFixedExample", Field: "ArrayMapFixedExample", Type: "string"},
|
||||
{Code: "bodymessage", Field: "Bodymessage", Type: "string"},
|
||||
{Code: "durationMs", Field: "DurationMs", Type: "int64"},
|
||||
@@ -452,7 +497,6 @@ func (r *DummyResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
{Code: "nestedRefExample", Field: "NestedRefExample", Type: "string"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "whereFail", Field: "WhereFail", Type: "int64"},
|
||||
{Code: "yamlExample", Field: "YamlExample", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -471,7 +515,6 @@ func (r *DummyResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -481,6 +524,9 @@ func (r *DummyResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -29,6 +32,7 @@ type VcOrgSaasModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
@@ -52,9 +56,10 @@ func (r *VcOrgSaasResource) Metadata(ctx context.Context, req resource.MetadataR
|
||||
|
||||
func (r *VcOrgSaasResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
@@ -107,17 +112,23 @@ func (r *VcOrgSaasResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -129,8 +140,10 @@ func (r *VcOrgSaasResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
params := map[int]string{
|
||||
185: resources_core.FormatString(config.ResourceRealm),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 20, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 20, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcOrgSaasResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -145,7 +158,9 @@ func (r *VcOrgSaasResource) Create(ctx context.Context, req resource.CreateReque
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 20, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 20, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
185: resources_core.FormatString(data.ResourceRealm),
|
||||
@@ -155,6 +170,9 @@ func (r *VcOrgSaasResource) Create(ctx context.Context, req resource.CreateReque
|
||||
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, 20, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -263,6 +281,9 @@ func (r *VcOrgSaasResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -298,7 +319,6 @@ func (r *VcOrgSaasResource) Delete(ctx context.Context, req resource.DeleteReque
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -308,6 +328,9 @@ func (r *VcOrgSaasResource) Delete(ctx context.Context, req resource.DeleteReque
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,12 @@ type VcVdcReconcileResource struct {
|
||||
}
|
||||
|
||||
type VcVdcReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcVdcID types.String `tfsdk:"vc_vdc_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcVdcID types.String `tfsdk:"vc_vdc_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewVcVdcReconcileResource() resource.Resource {
|
||||
@@ -40,10 +43,12 @@ func (r *VcVdcReconcileResource) Metadata(ctx context.Context, req resource.Meta
|
||||
|
||||
func (r *VcVdcReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_vdc_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_vdc_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -68,6 +73,9 @@ func (r *VcVdcReconcileResource) Create(ctx context.Context, req resource.Create
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,6 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,10 +34,10 @@ type VcVdcModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
CpuAllocated types.Int64 `tfsdk:"cpu_allocated"`
|
||||
CpuGuaranteed types.Int64 `tfsdk:"cpu_guaranteed"`
|
||||
MemAllocated types.Int64 `tfsdk:"mem_allocated"`
|
||||
MemGuaranteed types.Int64 `tfsdk:"mem_guaranteed"`
|
||||
NetworkProvider types.String `tfsdk:"network_provider"`
|
||||
OrganizationUid types.String `tfsdk:"organization_uid"`
|
||||
ProviderVdc types.String `tfsdk:"provider_vdc"`
|
||||
@@ -60,17 +64,17 @@ func (r *VcVdcResource) Metadata(ctx context.Context, req resource.MetadataReque
|
||||
|
||||
func (r *VcVdcResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"cpu_allocated": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(10), MarkdownDescription: "шт"},
|
||||
"cpu_guaranteed": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(20), MarkdownDescription: "% резервирования Не изменяется после создания."},
|
||||
"mem_allocated": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(20), MarkdownDescription: "gb"},
|
||||
"mem_guaranteed": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(20), MarkdownDescription: "% резервирования Не изменяется после создания."},
|
||||
"network_provider": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"organization_uid": schema.StringAttribute{Required: true, MarkdownDescription: "Для создания VDC предварительно необходимо создать организацию через услугу `Организация в Cloud Director` Не изменяется после создания."},
|
||||
"organization_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Для создания VDC предварительно необходимо создать организацию через услугу `Организация в Cloud Director` Не изменяется после создания."},
|
||||
"provider_vdc": schema.StringAttribute{Required: true, MarkdownDescription: "Приписка `2.8` - Скорость vCPU - 2800MHz<br/>И так далее...<br/> Не изменяется после создания."},
|
||||
"storage_config": schema.StringAttribute{Required: true, MarkdownDescription: "Первая политика в списке указывается как "По умолчанию""},
|
||||
"storage_config": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{}]"), MarkdownDescription: "Первая политика в списке указывается как "По умолчанию""},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -115,22 +119,11 @@ func (r *VcVdcResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.OrganizationUid.IsNull() && !plan.OrganizationUid.IsUnknown() {
|
||||
resolvedOrganizationUid, err := r.client.ResolveRefSvcParamValue(ctx, 19, plan.OrganizationUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve organization_uid", err.Error())
|
||||
} else if resolvedOrganizationUid != "" && resolvedOrganizationUid != plan.OrganizationUid.ValueString() {
|
||||
plan.OrganizationUid = types.StringValue(resolvedOrganizationUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -143,46 +136,54 @@ func (r *VcVdcResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.MemGuaranteed.IsNull() && !plan.MemGuaranteed.IsUnknown() && !state.MemGuaranteed.IsNull() && !state.MemGuaranteed.IsUnknown() {
|
||||
if plan.MemGuaranteed.ValueInt64() != state.MemGuaranteed.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить mem_guaranteed", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.NetworkProvider.IsNull() && !plan.NetworkProvider.IsUnknown() && !state.NetworkProvider.IsNull() && !state.NetworkProvider.IsUnknown() {
|
||||
if plan.NetworkProvider.ValueString() != state.NetworkProvider.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.NetworkProvider.ValueString(), state.NetworkProvider.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить network_provider", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.OrganizationUid.IsNull() && !plan.OrganizationUid.IsUnknown() && !state.OrganizationUid.IsNull() && !state.OrganizationUid.IsUnknown() {
|
||||
if plan.OrganizationUid.ValueString() != state.OrganizationUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.OrganizationUid.ValueString(), state.OrganizationUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить organization_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ProviderVdc.IsNull() && !plan.ProviderVdc.IsUnknown() && !state.ProviderVdc.IsNull() && !state.ProviderVdc.IsUnknown() {
|
||||
if plan.ProviderVdc.ValueString() != state.ProviderVdc.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ProviderVdc.ValueString(), state.ProviderVdc.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить provider_vdc", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.NetworkProvider.IsNull() || config.NetworkProvider.IsUnknown() {
|
||||
if config.NetworkProvider.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "network_provider is required.")
|
||||
return
|
||||
}
|
||||
if config.OrganizationUid.IsNull() || config.OrganizationUid.IsUnknown() {
|
||||
if config.NetworkProvider.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.OrganizationUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "organization_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.ProviderVdc.IsNull() || config.ProviderVdc.IsUnknown() {
|
||||
if config.OrganizationUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ProviderVdc.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "provider_vdc is required.")
|
||||
return
|
||||
}
|
||||
if config.StorageConfig.IsNull() || config.StorageConfig.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "storage_config is required.")
|
||||
if config.ProviderVdc.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -206,13 +207,14 @@ func (r *VcVdcResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
335: resources_core.FormatString(config.ProviderVdc),
|
||||
361: resources_core.FormatString(config.StorageConfig),
|
||||
397: resources_core.FormatInt64(config.CpuGuaranteed),
|
||||
398: resources_core.FormatInt64(config.MemGuaranteed),
|
||||
557: resources_core.FormatInt64(config.CpuAllocated),
|
||||
558: resources_core.FormatInt64(config.MemAllocated),
|
||||
746: resources_core.FormatString(config.NetworkProvider),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 21, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 21, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcVdcResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -233,10 +235,24 @@ func (r *VcVdcResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
resp.Diagnostics.AddError("Missing required attribute", "provider_vdc is required.")
|
||||
return
|
||||
}
|
||||
if data.StorageConfig.IsNull() || data.StorageConfig.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "storage_config is required.")
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalOrganizationUid := data.OrganizationUid
|
||||
if !data.OrganizationUid.IsNull() && !data.OrganizationUid.IsUnknown() {
|
||||
resolvedOrganizationUid, err := r.client.ResolveRefSvcParamValue(ctx, 19, data.OrganizationUid.ValueString())
|
||||
if err != nil {
|
||||
@@ -249,14 +265,15 @@ func (r *VcVdcResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 21, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 21, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
30: resources_core.FormatString(data.OrganizationUid),
|
||||
335: resources_core.FormatString(data.ProviderVdc),
|
||||
361: resources_core.FormatString(data.StorageConfig),
|
||||
397: resources_core.FormatInt64(data.CpuGuaranteed),
|
||||
398: resources_core.FormatInt64(data.MemGuaranteed),
|
||||
557: resources_core.FormatInt64(data.CpuAllocated),
|
||||
558: resources_core.FormatInt64(data.MemAllocated),
|
||||
746: resources_core.FormatString(data.NetworkProvider),
|
||||
@@ -266,6 +283,9 @@ func (r *VcVdcResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
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, 21, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -286,7 +306,6 @@ func (r *VcVdcResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
{Code: "cpuAllocated", Field: "CpuAllocated", Type: "int64"},
|
||||
{Code: "cpuGuaranteed", Field: "CpuGuaranteed", Type: "int64"},
|
||||
{Code: "memAllocated", Field: "MemAllocated", Type: "int64"},
|
||||
{Code: "memGuaranteed", Field: "MemGuaranteed", Type: "int64"},
|
||||
{Code: "networkProvider", Field: "NetworkProvider", Type: "string"},
|
||||
{Code: "organizationUid", Field: "OrganizationUid", Type: "string"},
|
||||
{Code: "providerVdc", Field: "ProviderVdc", Type: "string"},
|
||||
@@ -304,6 +323,16 @@ func (r *VcVdcResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
state.OrganizationUid = types.StringValue(resolvedOrganizationUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalOrganizationUid.IsNull() && !originalOrganizationUid.IsUnknown() && !state.OrganizationUid.IsNull() && !state.OrganizationUid.IsUnknown() {
|
||||
if strings.EqualFold(state.OrganizationUid.ValueString(), originalOrganizationUid.ValueString()) {
|
||||
state.OrganizationUid = originalOrganizationUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -342,7 +371,6 @@ func (r *VcVdcResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
{Code: "cpuAllocated", Field: "CpuAllocated", Type: "int64"},
|
||||
{Code: "cpuGuaranteed", Field: "CpuGuaranteed", Type: "int64"},
|
||||
{Code: "memAllocated", Field: "MemAllocated", Type: "int64"},
|
||||
{Code: "memGuaranteed", Field: "MemGuaranteed", Type: "int64"},
|
||||
{Code: "networkProvider", Field: "NetworkProvider", Type: "string"},
|
||||
{Code: "organizationUid", Field: "OrganizationUid", Type: "string"},
|
||||
{Code: "providerVdc", Field: "ProviderVdc", Type: "string"},
|
||||
@@ -360,6 +388,16 @@ func (r *VcVdcResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
newState.OrganizationUid = types.StringValue(resolvedOrganizationUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.OrganizationUid.IsNull() && !state.OrganizationUid.IsUnknown() && !newState.OrganizationUid.IsNull() && !newState.OrganizationUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.OrganizationUid.ValueString(), state.OrganizationUid.ValueString()) {
|
||||
newState.OrganizationUid = state.OrganizationUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -405,7 +443,7 @@ func (r *VcVdcResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.StorageConfig.IsNull() != state.StorageConfig.IsNull() || plan.StorageConfig.IsUnknown() != state.StorageConfig.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.StorageConfig.IsNull() && !plan.StorageConfig.IsUnknown() && !state.StorageConfig.IsNull() && !state.StorageConfig.IsUnknown() {
|
||||
if plan.StorageConfig.ValueString() != state.StorageConfig.ValueString() {
|
||||
if !strings.EqualFold(plan.StorageConfig.ValueString(), state.StorageConfig.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -435,6 +473,9 @@ func (r *VcVdcResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -454,7 +495,6 @@ func (r *VcVdcResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
{Code: "cpuAllocated", Field: "CpuAllocated", Type: "int64"},
|
||||
{Code: "cpuGuaranteed", Field: "CpuGuaranteed", Type: "int64"},
|
||||
{Code: "memAllocated", Field: "MemAllocated", Type: "int64"},
|
||||
{Code: "memGuaranteed", Field: "MemGuaranteed", Type: "int64"},
|
||||
{Code: "networkProvider", Field: "NetworkProvider", Type: "string"},
|
||||
{Code: "organizationUid", Field: "OrganizationUid", Type: "string"},
|
||||
{Code: "providerVdc", Field: "ProviderVdc", Type: "string"},
|
||||
@@ -472,6 +512,15 @@ func (r *VcVdcResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
state.OrganizationUid = types.StringValue(resolvedOrganizationUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.OrganizationUid.IsNull() && !plan.OrganizationUid.IsUnknown() && !state.OrganizationUid.IsNull() && !state.OrganizationUid.IsUnknown() {
|
||||
if strings.EqualFold(state.OrganizationUid.ValueString(), plan.OrganizationUid.ValueString()) {
|
||||
state.OrganizationUid = plan.OrganizationUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -485,7 +534,6 @@ func (r *VcVdcResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -495,6 +543,9 @@ func (r *VcVdcResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,12 @@ type VcNsxtReconcileResource struct {
|
||||
}
|
||||
|
||||
type VcNsxtReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcNsxtID types.String `tfsdk:"vc_nsxt_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcNsxtID types.String `tfsdk:"vc_nsxt_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewVcNsxtReconcileResource() resource.Resource {
|
||||
@@ -40,10 +43,12 @@ func (r *VcNsxtReconcileResource) Metadata(ctx context.Context, req resource.Met
|
||||
|
||||
func (r *VcNsxtReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_nsxt_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_nsxt_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -68,6 +73,9 @@ func (r *VcNsxtReconcileResource) Create(ctx context.Context, req resource.Creat
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -28,26 +31,27 @@ type VcNsxtResource struct {
|
||||
}
|
||||
|
||||
type VcNsxtModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
IpSpaceName types.String `tfsdk:"ip_space_name"`
|
||||
NeedEnableAVI types.Bool `tfsdk:"need_enable_a_v_i"`
|
||||
NeedExternalAddressSNAT types.Bool `tfsdk:"need_external_address_s_n_a_t"`
|
||||
VdcGroupUid types.String `tfsdk:"vdc_group_uid"`
|
||||
VdcType types.String `tfsdk:"vdc_type"`
|
||||
VdcUid types.String `tfsdk:"vdc_uid"`
|
||||
VirtualServicesCount types.Int64 `tfsdk:"virtual_services_count"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
IpSpaceName types.String `tfsdk:"ip_space_name"`
|
||||
NeedEnableAVI types.Bool `tfsdk:"need_enable_a_v_i"`
|
||||
QosProfile types.String `tfsdk:"qos_profile"`
|
||||
VdcGroupUid types.String `tfsdk:"vdc_group_uid"`
|
||||
VdcType types.String `tfsdk:"vdc_type"`
|
||||
VdcUid types.String `tfsdk:"vdc_uid"`
|
||||
VirtualServicesCount types.Int64 `tfsdk:"virtual_services_count"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
}
|
||||
|
||||
func NewVcNsxtResource() resource.Resource {
|
||||
@@ -60,26 +64,27 @@ func (r *VcNsxtResource) Metadata(ctx context.Context, req resource.MetadataRequ
|
||||
|
||||
func (r *VcNsxtResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"ip_space_name": schema.StringAttribute{Optional: true, MarkdownDescription: "Имя ip Space для внешнего IP<br/><br/>getKeyListFromStruct(getInstanceState(getValueFromStruct(getInstanceState(getValueFromStruct(getInstanceState(), "params.vdcUid")), "params.organizationUid")), "params.vIPConfigure")"},
|
||||
"need_enable_a_v_i": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Включение Load Balancer"},
|
||||
"need_external_address_s_n_a_t": schema.BoolAttribute{Optional: true, MarkdownDescription: "Создать правило SNAT на подсеть `10.10.102.0/24`"},
|
||||
"vdc_group_uid": schema.StringAttribute{Optional: true, MarkdownDescription: "UUID Услуги `Группа виртуальных датацентров (groupvDC)` Не изменяется после создания."},
|
||||
"vdc_type": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("vdc"), MarkdownDescription: "Тип родительской услуги Не изменяется после создания."},
|
||||
"vdc_uid": schema.StringAttribute{Optional: true, MarkdownDescription: "UUID Услуги `Виртуальный датацентр (vDC)` Не изменяется после создания."},
|
||||
"virtual_services_count": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Кол-во виртуальных сервисов, которые выделяются на AVI"},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
"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."},
|
||||
"ip_space_name": schema.StringAttribute{Optional: true, MarkdownDescription: "Имя ip Space для внешнего IP"},
|
||||
"need_enable_a_v_i": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Включение Load Balancer"},
|
||||
"qos_profile": schema.StringAttribute{Optional: true, MarkdownDescription: "Параметр пока не работает<br/>Должен возвращать поле из ресурсной платформы типа vc из .vcd.hardware.gatewayQoSProfiles<br/><br/>Как по идее должен работать.<br/>Нужно заполнить или CFS vdcUid, или vdcGroupUid<br/>Идеально конечно проверять `if (params.vdcType == "vdc" && params.vdcUid != "")` или `if (params.vdcType == "vdcGroup" && params.vdcGroupUid != "")`<br/><br/>Далее надо пойти по стейту vdc -> org -> resPlatform, взять gatewayQoSProfiles<br/>Или пойти по стейту vdcgroup -> vdc -> org -> resPlatform, взять gatewayQoSProfiles<br/><br/>Регулярку могу написать (Виталя)"},
|
||||
"vdc_group_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UUID Услуги `Группа виртуальных датацентров (groupvDC)` Не изменяется после создания."},
|
||||
"vdc_type": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("vdc"), MarkdownDescription: "Тип родительской услуги Не изменяется после создания."},
|
||||
"vdc_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UUID Услуги `Виртуальный датацентр (vDC)` Не изменяется после создания."},
|
||||
"virtual_services_count": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Кол-во виртуальных сервисов, которые **резервируются** на AVI"},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
}
|
||||
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
@@ -114,31 +119,11 @@ func (r *VcNsxtResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.VdcGroupUid.IsNull() && !plan.VdcGroupUid.IsUnknown() {
|
||||
resolvedVdcGroupUid, err := r.client.ResolveRefSvcParamValue(ctx, 29, plan.VdcGroupUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vdc_group_uid", err.Error())
|
||||
} else if resolvedVdcGroupUid != "" && resolvedVdcGroupUid != plan.VdcGroupUid.ValueString() {
|
||||
plan.VdcGroupUid = types.StringValue(resolvedVdcGroupUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, plan.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vdc_uid", err.Error())
|
||||
} else if resolvedVdcUid != "" && resolvedVdcUid != plan.VdcUid.ValueString() {
|
||||
plan.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -146,19 +131,28 @@ func (r *VcNsxtResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
}
|
||||
}
|
||||
if !plan.VdcGroupUid.IsNull() && !plan.VdcGroupUid.IsUnknown() && !state.VdcGroupUid.IsNull() && !state.VdcGroupUid.IsUnknown() {
|
||||
if plan.VdcGroupUid.ValueString() != state.VdcGroupUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VdcGroupUid.ValueString(), state.VdcGroupUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vdc_group_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VdcType.IsNull() && !plan.VdcType.IsUnknown() && !state.VdcType.IsNull() && !state.VdcType.IsUnknown() {
|
||||
if plan.VdcType.ValueString() != state.VdcType.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VdcType.ValueString(), state.VdcType.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vdc_type", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if plan.VdcUid.ValueString() != state.VdcUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VdcUid.ValueString(), state.VdcUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vdc_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -195,9 +189,12 @@ func (r *VcNsxtResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
341: resources_core.FormatInt64(config.VirtualServicesCount),
|
||||
621: resources_core.FormatString(config.VdcType),
|
||||
622: resources_core.FormatString(config.VdcGroupUid),
|
||||
825: resources_core.FormatString(config.QosProfile),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 22, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 22, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcNsxtResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -206,6 +203,42 @@ func (r *VcNsxtResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalVdcGroupUid := data.VdcGroupUid
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalVdcUid := data.VdcUid
|
||||
if !data.VdcGroupUid.IsNull() && !data.VdcGroupUid.IsUnknown() {
|
||||
resolvedVdcGroupUid, err := r.client.ResolveRefSvcParamValue(ctx, 29, data.VdcGroupUid.ValueString())
|
||||
if err != nil {
|
||||
@@ -228,7 +261,9 @@ func (r *VcNsxtResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 22, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 22, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
8: resources_core.FormatString(data.VdcUid),
|
||||
@@ -236,12 +271,16 @@ func (r *VcNsxtResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
341: resources_core.FormatInt64(data.VirtualServicesCount),
|
||||
621: resources_core.FormatString(data.VdcType),
|
||||
622: resources_core.FormatString(data.VdcGroupUid),
|
||||
825: resources_core.FormatString(data.QosProfile),
|
||||
}
|
||||
|
||||
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, 22, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -261,7 +300,7 @@ func (r *VcNsxtResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceName", Field: "IpSpaceName", Type: "string"},
|
||||
{Code: "needEnableAVI", Field: "NeedEnableAVI", Type: "bool"},
|
||||
{Code: "needExternalAddressSNAT", Field: "NeedExternalAddressSNAT", Type: "bool"},
|
||||
{Code: "qosProfile", Field: "QosProfile", Type: "string"},
|
||||
{Code: "vdcGroupUid", Field: "VdcGroupUid", Type: "string"},
|
||||
{Code: "vdcType", Field: "VdcType", Type: "string"},
|
||||
{Code: "vdcUid", Field: "VdcUid", Type: "string"},
|
||||
@@ -287,6 +326,21 @@ func (r *VcNsxtResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
state.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalVdcGroupUid.IsNull() && !originalVdcGroupUid.IsUnknown() && !state.VdcGroupUid.IsNull() && !state.VdcGroupUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VdcGroupUid.ValueString(), originalVdcGroupUid.ValueString()) {
|
||||
state.VdcGroupUid = originalVdcGroupUid
|
||||
}
|
||||
}
|
||||
if !originalVdcUid.IsNull() && !originalVdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VdcUid.ValueString(), originalVdcUid.ValueString()) {
|
||||
state.VdcUid = originalVdcUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -324,7 +378,7 @@ func (r *VcNsxtResource) Read(ctx context.Context, req resource.ReadRequest, res
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceName", Field: "IpSpaceName", Type: "string"},
|
||||
{Code: "needEnableAVI", Field: "NeedEnableAVI", Type: "bool"},
|
||||
{Code: "needExternalAddressSNAT", Field: "NeedExternalAddressSNAT", Type: "bool"},
|
||||
{Code: "qosProfile", Field: "QosProfile", Type: "string"},
|
||||
{Code: "vdcGroupUid", Field: "VdcGroupUid", Type: "string"},
|
||||
{Code: "vdcType", Field: "VdcType", Type: "string"},
|
||||
{Code: "vdcUid", Field: "VdcUid", Type: "string"},
|
||||
@@ -350,6 +404,21 @@ func (r *VcNsxtResource) Read(ctx context.Context, req resource.ReadRequest, res
|
||||
newState.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.VdcGroupUid.IsNull() && !state.VdcGroupUid.IsUnknown() && !newState.VdcGroupUid.IsNull() && !newState.VdcGroupUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.VdcGroupUid.ValueString(), state.VdcGroupUid.ValueString()) {
|
||||
newState.VdcGroupUid = state.VdcGroupUid
|
||||
}
|
||||
}
|
||||
if !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() && !newState.VdcUid.IsNull() && !newState.VdcUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.VdcUid.ValueString(), state.VdcUid.ValueString()) {
|
||||
newState.VdcUid = state.VdcUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -392,19 +461,19 @@ func (r *VcNsxtResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.NeedExternalAddressSNAT.IsNull() != state.NeedExternalAddressSNAT.IsNull() || plan.NeedExternalAddressSNAT.IsUnknown() != state.NeedExternalAddressSNAT.IsUnknown() {
|
||||
if plan.IpSpaceName.IsNull() != state.IpSpaceName.IsNull() || plan.IpSpaceName.IsUnknown() != state.IpSpaceName.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.NeedExternalAddressSNAT.IsNull() && !plan.NeedExternalAddressSNAT.IsUnknown() && !state.NeedExternalAddressSNAT.IsNull() && !state.NeedExternalAddressSNAT.IsUnknown() {
|
||||
if plan.NeedExternalAddressSNAT.ValueBool() != state.NeedExternalAddressSNAT.ValueBool() {
|
||||
} else if !plan.IpSpaceName.IsNull() && !plan.IpSpaceName.IsUnknown() && !state.IpSpaceName.IsNull() && !state.IpSpaceName.IsUnknown() {
|
||||
if !strings.EqualFold(plan.IpSpaceName.ValueString(), state.IpSpaceName.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IpSpaceName.IsNull() != state.IpSpaceName.IsNull() || plan.IpSpaceName.IsUnknown() != state.IpSpaceName.IsUnknown() {
|
||||
if plan.QosProfile.IsNull() != state.QosProfile.IsNull() || plan.QosProfile.IsUnknown() != state.QosProfile.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceName.IsNull() && !plan.IpSpaceName.IsUnknown() && !state.IpSpaceName.IsNull() && !state.IpSpaceName.IsUnknown() {
|
||||
if plan.IpSpaceName.ValueString() != state.IpSpaceName.ValueString() {
|
||||
} else if !plan.QosProfile.IsNull() && !plan.QosProfile.IsUnknown() && !state.QosProfile.IsNull() && !state.QosProfile.IsUnknown() {
|
||||
if !strings.EqualFold(plan.QosProfile.ValueString(), state.QosProfile.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -427,14 +496,17 @@ func (r *VcNsxtResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
params := map[int]string{
|
||||
368: resources_core.FormatBool(plan.NeedEnableAVI),
|
||||
369: resources_core.FormatInt64(plan.VirtualServicesCount),
|
||||
371: resources_core.FormatBool(plan.NeedExternalAddressSNAT),
|
||||
372: resources_core.FormatString(plan.IpSpaceName),
|
||||
856: resources_core.FormatString(plan.QosProfile),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -453,7 +525,7 @@ func (r *VcNsxtResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceName", Field: "IpSpaceName", Type: "string"},
|
||||
{Code: "needEnableAVI", Field: "NeedEnableAVI", Type: "bool"},
|
||||
{Code: "needExternalAddressSNAT", Field: "NeedExternalAddressSNAT", Type: "bool"},
|
||||
{Code: "qosProfile", Field: "QosProfile", Type: "string"},
|
||||
{Code: "vdcGroupUid", Field: "VdcGroupUid", Type: "string"},
|
||||
{Code: "vdcType", Field: "VdcType", Type: "string"},
|
||||
{Code: "vdcUid", Field: "VdcUid", Type: "string"},
|
||||
@@ -479,6 +551,20 @@ func (r *VcNsxtResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
state.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.VdcGroupUid.IsNull() && !plan.VdcGroupUid.IsUnknown() && !state.VdcGroupUid.IsNull() && !state.VdcGroupUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VdcGroupUid.ValueString(), plan.VdcGroupUid.ValueString()) {
|
||||
state.VdcGroupUid = plan.VdcGroupUid
|
||||
}
|
||||
}
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VdcUid.ValueString(), plan.VdcUid.ValueString()) {
|
||||
state.VdcUid = plan.VdcUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -492,16 +578,15 @@ func (r *VcNsxtResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,7 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,7 @@ type VcVmModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessIpList types.String `tfsdk:"access_ip_list"`
|
||||
IpSpaceName types.String `tfsdk:"ip_space_name"`
|
||||
TemplateName types.String `tfsdk:"template_name"`
|
||||
@@ -60,13 +64,14 @@ func (r *VcVmResource) Metadata(ctx context.Context, req resource.MetadataReques
|
||||
|
||||
func (r *VcVmResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"access_ip_list": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("79.111.119.78/32 185.247.187.1/32"), MarkdownDescription: "Вставить содержимое из 'curl ifconfig.io'<br/>IP адреса в CIDR формате через пробел для настройки аксес листов"},
|
||||
"ip_space_name": schema.StringAttribute{Required: true, MarkdownDescription: "Брать из свойств ресурсной платформы<br/>.vcd.ipSpace<br/><br/>Имя IP-space для выделения внешнего IP (IP выделяется обязательно) Не изменяется после создания."},
|
||||
"template_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("RockyLinux_9-16G"), MarkdownDescription: "ОС Не изменяется после создания."},
|
||||
"vapp_uid": schema.StringAttribute{Required: true, MarkdownDescription: "InstanceUid vApp. vApp должен быть создан Не изменяется после создания."},
|
||||
"vapp_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "InstanceUid vApp. vApp должен быть создан Не изменяется после создания."},
|
||||
"vm_cpu": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("2"), MarkdownDescription: "CPU"},
|
||||
"vm_disksize": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("10"), MarkdownDescription: "Размер диска под сервис (GB)"},
|
||||
"vm_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("vm0001"), MarkdownDescription: "Имя ВМ. Не поддерживает спец-символы Не изменяется после создания."},
|
||||
@@ -115,22 +120,11 @@ func (r *VcVmResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.VappUid.IsNull() && !plan.VappUid.IsUnknown() {
|
||||
resolvedVappUid, err := r.client.ResolveRefSvcParamValue(ctx, 26, plan.VappUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vapp_uid", err.Error())
|
||||
} else if resolvedVappUid != "" && resolvedVappUid != plan.VappUid.ValueString() {
|
||||
plan.VappUid = types.StringValue(resolvedVappUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -138,39 +132,57 @@ func (r *VcVmResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
}
|
||||
}
|
||||
if !plan.IpSpaceName.IsNull() && !plan.IpSpaceName.IsUnknown() && !state.IpSpaceName.IsNull() && !state.IpSpaceName.IsUnknown() {
|
||||
if plan.IpSpaceName.ValueString() != state.IpSpaceName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.IpSpaceName.ValueString(), state.IpSpaceName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить ip_space_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.TemplateName.IsNull() && !plan.TemplateName.IsUnknown() && !state.TemplateName.IsNull() && !state.TemplateName.IsUnknown() {
|
||||
if plan.TemplateName.ValueString() != state.TemplateName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.TemplateName.ValueString(), state.TemplateName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить template_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VappUid.IsNull() && !plan.VappUid.IsUnknown() && !state.VappUid.IsNull() && !state.VappUid.IsUnknown() {
|
||||
if plan.VappUid.ValueString() != state.VappUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VappUid.ValueString(), state.VappUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vapp_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VmName.IsNull() && !plan.VmName.IsUnknown() && !state.VmName.IsNull() && !state.VmName.IsUnknown() {
|
||||
if plan.VmName.ValueString() != state.VmName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VmName.ValueString(), state.VmName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vm_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.IpSpaceName.IsNull() || config.IpSpaceName.IsUnknown() {
|
||||
if config.IpSpaceName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "ip_space_name is required.")
|
||||
return
|
||||
}
|
||||
if config.VappUid.IsNull() || config.VappUid.IsUnknown() {
|
||||
if config.IpSpaceName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.VappUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vapp_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.VappUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -197,8 +209,10 @@ func (r *VcVmResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
177: resources_core.FormatString(config.AccessIpList),
|
||||
406: resources_core.FormatString(config.IpSpaceName),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 23, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 23, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcVmResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -215,6 +229,24 @@ func (r *VcVmResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vapp_uid is required.")
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalVappUid := data.VappUid
|
||||
if !data.VappUid.IsNull() && !data.VappUid.IsUnknown() {
|
||||
resolvedVappUid, err := r.client.ResolveRefSvcParamValue(ctx, 26, data.VappUid.ValueString())
|
||||
if err != nil {
|
||||
@@ -227,7 +259,9 @@ func (r *VcVmResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 23, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 23, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
33: resources_core.FormatString(data.VappUid),
|
||||
@@ -244,6 +278,9 @@ func (r *VcVmResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
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, 23, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -282,6 +319,16 @@ func (r *VcVmResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
state.VappUid = types.StringValue(resolvedVappUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalVappUid.IsNull() && !originalVappUid.IsUnknown() && !state.VappUid.IsNull() && !state.VappUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VappUid.ValueString(), originalVappUid.ValueString()) {
|
||||
state.VappUid = originalVappUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -338,6 +385,16 @@ func (r *VcVmResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
newState.VappUid = types.StringValue(resolvedVappUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.VappUid.IsNull() && !state.VappUid.IsUnknown() && !newState.VappUid.IsNull() && !newState.VappUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.VappUid.ValueString(), state.VappUid.ValueString()) {
|
||||
newState.VappUid = state.VappUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -365,7 +422,7 @@ func (r *VcVmResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
if plan.VmCpu.IsNull() != state.VmCpu.IsNull() || plan.VmCpu.IsUnknown() != state.VmCpu.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.VmCpu.IsNull() && !plan.VmCpu.IsUnknown() && !state.VmCpu.IsNull() && !state.VmCpu.IsUnknown() {
|
||||
if plan.VmCpu.ValueString() != state.VmCpu.ValueString() {
|
||||
if !strings.EqualFold(plan.VmCpu.ValueString(), state.VmCpu.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -374,7 +431,7 @@ func (r *VcVmResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
if plan.VmRam.IsNull() != state.VmRam.IsNull() || plan.VmRam.IsUnknown() != state.VmRam.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.VmRam.IsNull() && !plan.VmRam.IsUnknown() && !state.VmRam.IsNull() && !state.VmRam.IsUnknown() {
|
||||
if plan.VmRam.ValueString() != state.VmRam.ValueString() {
|
||||
if !strings.EqualFold(plan.VmRam.ValueString(), state.VmRam.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -383,7 +440,7 @@ func (r *VcVmResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
if plan.VmDisksize.IsNull() != state.VmDisksize.IsNull() || plan.VmDisksize.IsUnknown() != state.VmDisksize.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.VmDisksize.IsNull() && !plan.VmDisksize.IsUnknown() && !state.VmDisksize.IsNull() && !state.VmDisksize.IsUnknown() {
|
||||
if plan.VmDisksize.ValueString() != state.VmDisksize.ValueString() {
|
||||
if !strings.EqualFold(plan.VmDisksize.ValueString(), state.VmDisksize.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -392,7 +449,7 @@ func (r *VcVmResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
if plan.AccessIpList.IsNull() != state.AccessIpList.IsNull() || plan.AccessIpList.IsUnknown() != state.AccessIpList.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AccessIpList.IsNull() && !plan.AccessIpList.IsUnknown() && !state.AccessIpList.IsNull() && !state.AccessIpList.IsUnknown() {
|
||||
if plan.AccessIpList.ValueString() != state.AccessIpList.ValueString() {
|
||||
if !strings.EqualFold(plan.AccessIpList.ValueString(), state.AccessIpList.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -423,6 +480,9 @@ func (r *VcVmResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -460,6 +520,15 @@ func (r *VcVmResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
state.VappUid = types.StringValue(resolvedVappUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.VappUid.IsNull() && !plan.VappUid.IsUnknown() && !state.VappUid.IsNull() && !state.VappUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VappUid.ValueString(), plan.VappUid.ValueString()) {
|
||||
state.VappUid = plan.VappUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -473,7 +542,6 @@ func (r *VcVmResource) Delete(ctx context.Context, req resource.DeleteRequest, r
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -483,6 +551,9 @@ func (r *VcVmResource) Delete(ctx context.Context, req resource.DeleteRequest, r
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,7 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,7 @@ type VcexternalipModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
DnatCreate types.Bool `tfsdk:"dnat_create"`
|
||||
FromServiceCloudEdgeName types.String `tfsdk:"from_service_cloud_edge_name"`
|
||||
FromServiceCloudEdgeScope types.String `tfsdk:"from_service_cloud_edge_scope"`
|
||||
@@ -66,9 +70,10 @@ func (r *VcexternalipResource) Metadata(ctx context.Context, req resource.Metada
|
||||
|
||||
func (r *VcexternalipResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"dnat_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true), MarkdownDescription: "Необходимо создать DNAT-правило."},
|
||||
"from_service_cloud_edge_name": schema.StringAttribute{Optional: true, MarkdownDescription: "Edge составной услуги (указывать, если resourceRealm связан с CloudDirector) Не изменяется после создания."},
|
||||
"from_service_cloud_edge_scope": schema.StringAttribute{Optional: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
@@ -134,75 +139,114 @@ func (r *VcexternalipResource) ModifyPlan(ctx context.Context, req resource.Modi
|
||||
}
|
||||
}
|
||||
if !plan.FromServiceCloudEdgeName.IsNull() && !plan.FromServiceCloudEdgeName.IsUnknown() && !state.FromServiceCloudEdgeName.IsNull() && !state.FromServiceCloudEdgeName.IsUnknown() {
|
||||
if plan.FromServiceCloudEdgeName.ValueString() != state.FromServiceCloudEdgeName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.FromServiceCloudEdgeName.ValueString(), state.FromServiceCloudEdgeName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить from_service_cloud_edge_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.FromServiceCloudEdgeScope.IsNull() && !plan.FromServiceCloudEdgeScope.IsUnknown() && !state.FromServiceCloudEdgeScope.IsNull() && !state.FromServiceCloudEdgeScope.IsUnknown() {
|
||||
if plan.FromServiceCloudEdgeScope.ValueString() != state.FromServiceCloudEdgeScope.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.FromServiceCloudEdgeScope.ValueString(), state.FromServiceCloudEdgeScope.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить from_service_cloud_edge_scope", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.FromServiceCloudOrgName.IsNull() && !plan.FromServiceCloudOrgName.IsUnknown() && !state.FromServiceCloudOrgName.IsNull() && !state.FromServiceCloudOrgName.IsUnknown() {
|
||||
if plan.FromServiceCloudOrgName.ValueString() != state.FromServiceCloudOrgName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.FromServiceCloudOrgName.ValueString(), state.FromServiceCloudOrgName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить from_service_cloud_org_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.FromServiceCloudVdcName.IsNull() && !plan.FromServiceCloudVdcName.IsUnknown() && !state.FromServiceCloudVdcName.IsNull() && !state.FromServiceCloudVdcName.IsUnknown() {
|
||||
if plan.FromServiceCloudVdcName.ValueString() != state.FromServiceCloudVdcName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.FromServiceCloudVdcName.ValueString(), state.FromServiceCloudVdcName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить from_service_cloud_vdc_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.FromServiceCloudVmwareUrl.IsNull() && !plan.FromServiceCloudVmwareUrl.IsUnknown() && !state.FromServiceCloudVmwareUrl.IsNull() && !state.FromServiceCloudVmwareUrl.IsUnknown() {
|
||||
if plan.FromServiceCloudVmwareUrl.ValueString() != state.FromServiceCloudVmwareUrl.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.FromServiceCloudVmwareUrl.ValueString(), state.FromServiceCloudVmwareUrl.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить from_service_cloud_vmware_url", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.FromServiceNamespace.IsNull() && !plan.FromServiceNamespace.IsUnknown() && !state.FromServiceNamespace.IsNull() && !state.FromServiceNamespace.IsUnknown() {
|
||||
if plan.FromServiceNamespace.ValueString() != state.FromServiceNamespace.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.FromServiceNamespace.ValueString(), state.FromServiceNamespace.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить from_service_namespace", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.FromServiceVdcGroupName.IsNull() && !plan.FromServiceVdcGroupName.IsUnknown() && !state.FromServiceVdcGroupName.IsNull() && !state.FromServiceVdcGroupName.IsUnknown() {
|
||||
if plan.FromServiceVdcGroupName.ValueString() != state.FromServiceVdcGroupName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.FromServiceVdcGroupName.ValueString(), state.FromServiceVdcGroupName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить from_service_vdc_group_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.FromServiceNamespace.IsNull() || config.FromServiceNamespace.IsUnknown() {
|
||||
if config.FromServiceNamespace.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "from_service_namespace is required.")
|
||||
return
|
||||
}
|
||||
if config.InternalAddrAccess.IsNull() || config.InternalAddrAccess.IsUnknown() {
|
||||
if config.FromServiceNamespace.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.InternalAddrAccess.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "internal_addr_access is required.")
|
||||
return
|
||||
}
|
||||
if config.IpSpaceName.IsNull() || config.IpSpaceName.IsUnknown() {
|
||||
if config.InternalAddrAccess.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.IpSpaceName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "ip_space_name is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.IpSpaceName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ServiceUid.IsNull() || config.ServiceUid.IsUnknown() {
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ServiceUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "service_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.ServiceUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -227,8 +271,10 @@ func (r *VcexternalipResource) ModifyPlan(ctx context.Context, req resource.Modi
|
||||
624: resources_core.FormatString(config.FromServiceCloudEdgeScope),
|
||||
626: resources_core.FormatString(config.FromServiceVdcGroupName),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 25, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 25, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcexternalipResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -259,7 +305,9 @@ func (r *VcexternalipResource) Create(ctx context.Context, req resource.CreateRe
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 25, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 25, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
140: resources_core.FormatString(data.ServiceUid),
|
||||
@@ -282,6 +330,9 @@ func (r *VcexternalipResource) Create(ctx context.Context, req resource.CreateRe
|
||||
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, 25, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -399,7 +450,7 @@ func (r *VcexternalipResource) Update(ctx context.Context, req resource.UpdateRe
|
||||
if plan.ServiceUid.IsNull() != state.ServiceUid.IsNull() || plan.ServiceUid.IsUnknown() != state.ServiceUid.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ServiceUid.IsNull() && !plan.ServiceUid.IsUnknown() && !state.ServiceUid.IsNull() && !state.ServiceUid.IsUnknown() {
|
||||
if plan.ServiceUid.ValueString() != state.ServiceUid.ValueString() {
|
||||
if !strings.EqualFold(plan.ServiceUid.ValueString(), state.ServiceUid.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -408,7 +459,7 @@ func (r *VcexternalipResource) Update(ctx context.Context, req resource.UpdateRe
|
||||
if plan.IpSpaceName.IsNull() != state.IpSpaceName.IsNull() || plan.IpSpaceName.IsUnknown() != state.IpSpaceName.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceName.IsNull() && !plan.IpSpaceName.IsUnknown() && !state.IpSpaceName.IsNull() && !state.IpSpaceName.IsUnknown() {
|
||||
if plan.IpSpaceName.ValueString() != state.IpSpaceName.ValueString() {
|
||||
if !strings.EqualFold(plan.IpSpaceName.ValueString(), state.IpSpaceName.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -435,7 +486,7 @@ func (r *VcexternalipResource) Update(ctx context.Context, req resource.UpdateRe
|
||||
if plan.InternalPortAccess.IsNull() != state.InternalPortAccess.IsNull() || plan.InternalPortAccess.IsUnknown() != state.InternalPortAccess.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.InternalPortAccess.IsNull() && !plan.InternalPortAccess.IsUnknown() && !state.InternalPortAccess.IsNull() && !state.InternalPortAccess.IsUnknown() {
|
||||
if plan.InternalPortAccess.ValueString() != state.InternalPortAccess.ValueString() {
|
||||
if !strings.EqualFold(plan.InternalPortAccess.ValueString(), state.InternalPortAccess.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -444,7 +495,7 @@ func (r *VcexternalipResource) Update(ctx context.Context, req resource.UpdateRe
|
||||
if plan.InternalAddrAccess.IsNull() != state.InternalAddrAccess.IsNull() || plan.InternalAddrAccess.IsUnknown() != state.InternalAddrAccess.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.InternalAddrAccess.IsNull() && !plan.InternalAddrAccess.IsUnknown() && !state.InternalAddrAccess.IsNull() && !state.InternalAddrAccess.IsUnknown() {
|
||||
if plan.InternalAddrAccess.ValueString() != state.InternalAddrAccess.ValueString() {
|
||||
if !strings.EqualFold(plan.InternalAddrAccess.ValueString(), state.InternalAddrAccess.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -477,6 +528,9 @@ func (r *VcexternalipResource) Update(ctx context.Context, req resource.UpdateRe
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -525,16 +579,15 @@ func (r *VcexternalipResource) Delete(ctx context.Context, req resource.DeleteRe
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -29,6 +32,7 @@ type VappModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
NsxtUid types.String `tfsdk:"nsxt_uid"`
|
||||
VappName types.String `tfsdk:"vapp_name"`
|
||||
VdcUid types.String `tfsdk:"vdc_uid"`
|
||||
@@ -54,12 +58,13 @@ func (r *VappResource) Metadata(ctx context.Context, req resource.MetadataReques
|
||||
|
||||
func (r *VappResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"nsxt_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UUID Услуги `Сетевой шлюз периметра (Edge)` Не изменяется после создания."},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"nsxt_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UUID Услуги `Сетевой шлюз периметра (Edge)` Не изменяется после создания."},
|
||||
"vapp_name": schema.StringAttribute{Required: true, MarkdownDescription: "Уникальное в рамках организации Не изменяется после создания."},
|
||||
"vdc_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UUID Услуги `Виртуальный датацентр (vDC)` Не изменяется после создания."},
|
||||
"vdc_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UUID Услуги `Виртуальный датацентр (vDC)` Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -104,31 +109,11 @@ func (r *VappResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.NsxtUid.IsNull() && !plan.NsxtUid.IsUnknown() {
|
||||
resolvedNsxtUid, err := r.client.ResolveRefSvcParamValue(ctx, 22, plan.NsxtUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve nsxt_uid", err.Error())
|
||||
} else if resolvedNsxtUid != "" && resolvedNsxtUid != plan.NsxtUid.ValueString() {
|
||||
plan.NsxtUid = types.StringValue(resolvedNsxtUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, plan.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vdc_uid", err.Error())
|
||||
} else if resolvedVdcUid != "" && resolvedVdcUid != plan.VdcUid.ValueString() {
|
||||
plan.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -136,37 +121,55 @@ func (r *VappResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
}
|
||||
}
|
||||
if !plan.NsxtUid.IsNull() && !plan.NsxtUid.IsUnknown() && !state.NsxtUid.IsNull() && !state.NsxtUid.IsUnknown() {
|
||||
if plan.NsxtUid.ValueString() != state.NsxtUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.NsxtUid.ValueString(), state.NsxtUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить nsxt_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VappName.IsNull() && !plan.VappName.IsUnknown() && !state.VappName.IsNull() && !state.VappName.IsUnknown() {
|
||||
if plan.VappName.ValueString() != state.VappName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VappName.ValueString(), state.VappName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vapp_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if plan.VdcUid.ValueString() != state.VdcUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VdcUid.ValueString(), state.VdcUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vdc_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.NsxtUid.IsNull() || config.NsxtUid.IsUnknown() {
|
||||
if config.NsxtUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "nsxt_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.VappName.IsNull() || config.VappName.IsUnknown() {
|
||||
if config.NsxtUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.VappName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vapp_name is required.")
|
||||
return
|
||||
}
|
||||
if config.VdcUid.IsNull() || config.VdcUid.IsUnknown() {
|
||||
if config.VappName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.VdcUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vdc_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.VdcUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -196,8 +199,10 @@ func (r *VappResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
191: resources_core.FormatString(config.VappName),
|
||||
623: resources_core.FormatString(config.VdcUid),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 26, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 26, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VappResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -218,6 +223,42 @@ func (r *VappResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vdc_uid is required.")
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalNsxtUid := data.NsxtUid
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalVdcUid := data.VdcUid
|
||||
if !data.NsxtUid.IsNull() && !data.NsxtUid.IsUnknown() {
|
||||
resolvedNsxtUid, err := r.client.ResolveRefSvcParamValue(ctx, 22, data.NsxtUid.ValueString())
|
||||
if err != nil {
|
||||
@@ -240,7 +281,9 @@ func (r *VappResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 26, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 26, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
190: resources_core.FormatString(data.NsxtUid),
|
||||
@@ -252,6 +295,9 @@ func (r *VappResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
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, 26, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -293,6 +339,21 @@ func (r *VappResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
state.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalNsxtUid.IsNull() && !originalNsxtUid.IsUnknown() && !state.NsxtUid.IsNull() && !state.NsxtUid.IsUnknown() {
|
||||
if strings.EqualFold(state.NsxtUid.ValueString(), originalNsxtUid.ValueString()) {
|
||||
state.NsxtUid = originalNsxtUid
|
||||
}
|
||||
}
|
||||
if !originalVdcUid.IsNull() && !originalVdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VdcUid.ValueString(), originalVdcUid.ValueString()) {
|
||||
state.VdcUid = originalVdcUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -352,6 +413,21 @@ func (r *VappResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
newState.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.NsxtUid.IsNull() && !state.NsxtUid.IsUnknown() && !newState.NsxtUid.IsNull() && !newState.NsxtUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.NsxtUid.ValueString(), state.NsxtUid.ValueString()) {
|
||||
newState.NsxtUid = state.NsxtUid
|
||||
}
|
||||
}
|
||||
if !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() && !newState.VdcUid.IsNull() && !newState.VdcUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.VdcUid.ValueString(), state.VdcUid.ValueString()) {
|
||||
newState.VdcUid = state.VdcUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -396,6 +472,9 @@ func (r *VappResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -436,6 +515,20 @@ func (r *VappResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
state.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.NsxtUid.IsNull() && !plan.NsxtUid.IsUnknown() && !state.NsxtUid.IsNull() && !state.NsxtUid.IsUnknown() {
|
||||
if strings.EqualFold(state.NsxtUid.ValueString(), plan.NsxtUid.ValueString()) {
|
||||
state.NsxtUid = plan.NsxtUid
|
||||
}
|
||||
}
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VdcUid.ValueString(), plan.VdcUid.ValueString()) {
|
||||
state.VdcUid = plan.VdcUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -449,7 +542,6 @@ func (r *VappResource) Delete(ctx context.Context, req resource.DeleteRequest, r
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -459,6 +551,9 @@ func (r *VappResource) Delete(ctx context.Context, req resource.DeleteRequest, r
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,7 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,7 @@ type VcVmV2Model struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
ServerListJson types.String `tfsdk:"server_list_json"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
@@ -53,10 +57,11 @@ func (r *VcVmV2Resource) Metadata(ctx context.Context, req resource.MetadataRequ
|
||||
|
||||
func (r *VcVmV2Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"server_list_json": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{}]"), MarkdownDescription: "Необходимо изменить vappUid и ipSpaceName<br/>Пример конфигурации взять по ссылке ниже<br/><br/>Объект серверов вида [{}] (Map(Object))<br/>Более подробнее смотреть в README.md (https://gitea.mgmt.nubes.ru/cloudServices/vcVmV2)"},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"server_list_json": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[{}]"), MarkdownDescription: "Необходимо изменить vappUid и ipSpaceName<br/>Пример конфигурации взять по ссылке ниже<br/><br/>Объект серверов вида [{}] (Map(Object))<br/>Более подробнее смотреть в README.md (https://gitea.mgmt.nubes.ru/cloudServices/vcVmV2)", PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()}},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -120,8 +125,10 @@ func (r *VcVmV2Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
params := map[int]string{
|
||||
301: resources_core.FormatString(config.ServerListJson),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 27, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 27, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcVmV2Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -132,7 +139,9 @@ func (r *VcVmV2Resource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 27, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 27, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
301: resources_core.FormatString(data.ServerListJson),
|
||||
@@ -142,6 +151,9 @@ func (r *VcVmV2Resource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
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, 27, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -233,7 +245,7 @@ func (r *VcVmV2Resource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.ServerListJson.IsNull() != state.ServerListJson.IsNull() || plan.ServerListJson.IsUnknown() != state.ServerListJson.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ServerListJson.IsNull() && !plan.ServerListJson.IsUnknown() && !state.ServerListJson.IsNull() && !state.ServerListJson.IsUnknown() {
|
||||
if plan.ServerListJson.ValueString() != state.ServerListJson.ValueString() {
|
||||
if !strings.EqualFold(plan.ServerListJson.ValueString(), state.ServerListJson.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -261,6 +273,9 @@ func (r *VcVmV2Resource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -296,7 +311,6 @@ func (r *VcVmV2Resource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -306,6 +320,9 @@ func (r *VcVmV2Resource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: vc_vm_v3
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &VcVmV3ReconcileResource{}
|
||||
|
||||
// VcVmV3ReconcileResource triggers a one-time action.
|
||||
type VcVmV3ReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type VcVmV3ReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcVmV3ID types.String `tfsdk:"vc_vm_v3_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewVcVmV3ReconcileResource() resource.Resource {
|
||||
return &VcVmV3ReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *VcVmV3ReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_vc_vm_v3_reconcile"
|
||||
}
|
||||
|
||||
func (r *VcVmV3ReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_vm_v3_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *VcVmV3ReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan VcVmV3ReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.VcVmV3ID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *VcVmV3ReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state VcVmV3ReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *VcVmV3ReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan VcVmV3ReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.VcVmV3ID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *VcVmV3ReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *VcVmV3ReconcileResource) 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
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,21 +26,23 @@ type VcVmV3RedeployResource struct {
|
||||
}
|
||||
|
||||
type VcVmV3RedeployModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcVmV3ID types.String `tfsdk:"vc_vm_v3_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
VmName types.String `tfsdk:"vm_name"`
|
||||
VmCpu types.Int64 `tfsdk:"vm_cpu"`
|
||||
VmRam types.Int64 `tfsdk:"vm_ram"`
|
||||
VmDisk types.Int64 `tfsdk:"vm_disk"`
|
||||
IpSpaceName types.String `tfsdk:"ip_space_name"`
|
||||
AccessIpList types.String `tfsdk:"access_ip_list"`
|
||||
ImageVm types.String `tfsdk:"image_vm"`
|
||||
UserLogin types.String `tfsdk:"user_login"`
|
||||
UserPublicKey types.String `tfsdk:"user_public_key"`
|
||||
CloudInit types.String `tfsdk:"cloud_init"`
|
||||
AccessPortList types.String `tfsdk:"access_port_list"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcVmV3ID types.String `tfsdk:"vc_vm_v3_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
VmName types.String `tfsdk:"vm_name"`
|
||||
VmCpu types.Int64 `tfsdk:"vm_cpu"`
|
||||
VmRam types.Int64 `tfsdk:"vm_ram"`
|
||||
VmDisk types.Int64 `tfsdk:"vm_disk"`
|
||||
IpSpaceName types.String `tfsdk:"ip_space_name"`
|
||||
AccessIpList types.String `tfsdk:"access_ip_list"`
|
||||
ImageVm types.String `tfsdk:"image_vm"`
|
||||
UserLogin types.String `tfsdk:"user_login"`
|
||||
UserPublicKey types.String `tfsdk:"user_public_key"`
|
||||
CloudInit types.String `tfsdk:"cloud_init"`
|
||||
AccessPortList types.String `tfsdk:"access_port_list"`
|
||||
}
|
||||
|
||||
func NewVcVmV3RedeployResource() resource.Resource {
|
||||
@@ -51,21 +55,23 @@ func (r *VcVmV3RedeployResource) Metadata(ctx context.Context, req resource.Meta
|
||||
|
||||
func (r *VcVmV3RedeployResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_vm_v3_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"vm_name": schema.StringAttribute{Required: true},
|
||||
"vm_cpu": schema.Int64Attribute{Required: true, MarkdownDescription: "шт"},
|
||||
"vm_ram": schema.Int64Attribute{Required: true, MarkdownDescription: "Gb"},
|
||||
"vm_disk": schema.Int64Attribute{Optional: true, MarkdownDescription: "Основной диск у vm зависит от образа (см. документацию)<br/>Если необходимо дополнительное место для vm, введите колличество Gb в эту форму"},
|
||||
"ip_space_name": schema.StringAttribute{Optional: true, MarkdownDescription: "no-needed, без внешнего ip<br/>internet-antiddos-v1, с функцией защиты от ddos атак<br/>internet-no-antiddos-v1, с функцией защиты от ddos атак<br/>internet-ipv4-v1 тестовое простанство dev"},
|
||||
"access_ip_list": schema.StringAttribute{Optional: true, MarkdownDescription: "Формат массива<br/>Каким адресам будет предоставлен доступ до вм<br/><br/>Требует выделенного внешнего IP<br/>Если ничего не передано, то выставлено ["0.0.0.0/0"]"},
|
||||
"image_vm": schema.StringAttribute{Required: true},
|
||||
"user_login": schema.StringAttribute{Required: true, MarkdownDescription: "Учётка SSH"},
|
||||
"user_public_key": schema.StringAttribute{Required: true, MarkdownDescription: "Публичная часть SSH-Ключа для пользователя"},
|
||||
"cloud_init": schema.StringAttribute{Optional: true, MarkdownDescription: "Можно передать создание дополнительных пользователей.<br/>На текущий момент не реализовано выполнения в cloud-init, установки пакетов, выполнения команд и записи файлов."},
|
||||
"access_port_list": schema.StringAttribute{Optional: true, MarkdownDescription: "По каким портам открыть доступ к ВМ.<br/>Порты 22, 10050 проставляются автоматически<br/>Пока доступно:<br/>22 - SSH,<br/>80 - HTTP,<br/>443 - HTTPS,<br/>10050 - ZABBIX"},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_vm_v3_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
"vm_name": schema.StringAttribute{Required: true},
|
||||
"vm_cpu": schema.Int64Attribute{Required: true, MarkdownDescription: "шт"},
|
||||
"vm_ram": schema.Int64Attribute{Required: true, MarkdownDescription: "Gb"},
|
||||
"vm_disk": schema.Int64Attribute{Optional: true, MarkdownDescription: "Основной диск у vm зависит от образа (см. документацию)<br/>Если необходимо дополнительное место для vm, введите колличество Gb в эту форму"},
|
||||
"ip_space_name": schema.StringAttribute{Optional: true, MarkdownDescription: "no-needed, без внешнего ip<br/>internet-antiddos-v1, с функцией защиты от ddos атак<br/>internet-no-antiddos-v1, с функцией защиты от ddos атак<br/>internet-ipv4-v1 тестовое простанство dev"},
|
||||
"access_ip_list": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Формат массива<br/>Каким адресам будет предоставлен доступ до вм<br/><br/>Требует выделенного внешнего IP<br/>Если ничего не передано, то выставлено ["0.0.0.0/0"]", PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()}},
|
||||
"image_vm": schema.StringAttribute{Required: true},
|
||||
"user_login": schema.StringAttribute{Required: true, MarkdownDescription: "Учётка SSH"},
|
||||
"user_public_key": schema.StringAttribute{Required: true, MarkdownDescription: "Публичная часть SSH-Ключа для пользователя"},
|
||||
"cloud_init": schema.StringAttribute{Optional: true, MarkdownDescription: "Можно передать создание дополнительных пользователей.<br/>На текущий момент не реализовано выполнения в cloud-init, установки пакетов, выполнения команд и записи файлов."},
|
||||
"access_port_list": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "По каким портам открыть доступ к ВМ.<br/>Порты 22, 10050 проставляются автоматически<br/>Пока доступно:<br/>22 - SSH,<br/>80 - HTTP,<br/>443 - HTTPS,<br/>10050 - ZABBIX", PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()}},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -126,6 +132,9 @@ func (r *VcVmV3RedeployResource) Create(ctx context.Context, req resource.Create
|
||||
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, "redeploy", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,6 +34,7 @@ type VcVmV3Model struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessIpList types.String `tfsdk:"access_ip_list"`
|
||||
AccessPortList types.String `tfsdk:"access_port_list"`
|
||||
CloudInit types.String `tfsdk:"cloud_init"`
|
||||
@@ -65,17 +69,18 @@ func (r *VcVmV3Resource) Metadata(ctx context.Context, req resource.MetadataRequ
|
||||
|
||||
func (r *VcVmV3Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"access_ip_list": schema.StringAttribute{Optional: true, MarkdownDescription: "Формат массива<br/>Каким адресам будет предоставлен доступ до вм<br/><br/>Требует выделенного внешнего IP<br/>Если ничего не передано, то выставлено ["0.0.0.0/0"]"},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"access_ip_list": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Формат массива<br/>Каким адресам будет предоставлен доступ до вм<br/><br/>Требует выделенного внешнего IP<br/>Если ничего не передано, то выставлено ["0.0.0.0/0"]", PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()}},
|
||||
"access_port_list": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[\"22\"]"), MarkdownDescription: "По каким портам открыть доступ к ВМ.<br/>Порты 22, 10050 проставляются автоматически<br/>Пока доступно: <br/>22 - SSH, <br/>80 - HTTP,<br/>443 - HTTPS,<br/>10050 - ZABBIX"},
|
||||
"cloud_init": schema.StringAttribute{Optional: true, MarkdownDescription: "Можно передать создание дополнительных пользователей. <br/>На текущий момент не реализовано выполнения в cloud-init, установки пакетов, выполнения команд и записи файлов. Не изменяется после создания."},
|
||||
"image_vm": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"ip_space_name": schema.StringAttribute{Required: true},
|
||||
"user_login": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("myuser"), MarkdownDescription: "Учётка SSH Не изменяется после создания."},
|
||||
"user_public_key": schema.StringAttribute{Required: true, MarkdownDescription: "Публичная часть SSH-Ключа для пользователя Не изменяется после создания."},
|
||||
"vapp_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UI display_name for service_id=26 (mapped to UUID in core) Не изменяется после создания."},
|
||||
"vapp_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UI display_name for service_id=26 (mapped to UUID in core) Не изменяется после создания."},
|
||||
"vm_cpu": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "шт"},
|
||||
"vm_disk": schema.Int64Attribute{Optional: true, MarkdownDescription: "Основной диск у vm зависит от образа (см. документацию)<br/>Если необходимо дополнительное место для vm, введите колличество Gb в эту форму"},
|
||||
"vm_name": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Не изменяется после создания."},
|
||||
@@ -124,22 +129,11 @@ func (r *VcVmV3Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.VappUid.IsNull() && !plan.VappUid.IsUnknown() {
|
||||
resolvedVappUid, err := r.client.ResolveRefSvcParamValue(ctx, 26, plan.VappUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vapp_uid", err.Error())
|
||||
} else if resolvedVappUid != "" && resolvedVappUid != plan.VappUid.ValueString() {
|
||||
plan.VappUid = types.StringValue(resolvedVappUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -147,59 +141,89 @@ func (r *VcVmV3Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
}
|
||||
}
|
||||
if !plan.CloudInit.IsNull() && !plan.CloudInit.IsUnknown() && !state.CloudInit.IsNull() && !state.CloudInit.IsUnknown() {
|
||||
if plan.CloudInit.ValueString() != state.CloudInit.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.CloudInit.ValueString(), state.CloudInit.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить cloud_init", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ImageVm.IsNull() && !plan.ImageVm.IsUnknown() && !state.ImageVm.IsNull() && !state.ImageVm.IsUnknown() {
|
||||
if plan.ImageVm.ValueString() != state.ImageVm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ImageVm.ValueString(), state.ImageVm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить image_vm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.UserLogin.IsNull() && !plan.UserLogin.IsUnknown() && !state.UserLogin.IsNull() && !state.UserLogin.IsUnknown() {
|
||||
if plan.UserLogin.ValueString() != state.UserLogin.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.UserLogin.ValueString(), state.UserLogin.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить user_login", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.UserPublicKey.IsNull() && !plan.UserPublicKey.IsUnknown() && !state.UserPublicKey.IsNull() && !state.UserPublicKey.IsUnknown() {
|
||||
if plan.UserPublicKey.ValueString() != state.UserPublicKey.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.UserPublicKey.ValueString(), state.UserPublicKey.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить user_public_key", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VappUid.IsNull() && !plan.VappUid.IsUnknown() && !state.VappUid.IsNull() && !state.VappUid.IsUnknown() {
|
||||
if plan.VappUid.ValueString() != state.VappUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VappUid.ValueString(), state.VappUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vapp_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.VmName.IsNull() && !plan.VmName.IsUnknown() && !state.VmName.IsNull() && !state.VmName.IsUnknown() {
|
||||
if plan.VmName.ValueString() != state.VmName.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VmName.ValueString(), state.VmName.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vm_name", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ImageVm.IsNull() || config.ImageVm.IsUnknown() {
|
||||
if config.ImageVm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "image_vm is required.")
|
||||
return
|
||||
}
|
||||
if config.IpSpaceName.IsNull() || config.IpSpaceName.IsUnknown() {
|
||||
if config.ImageVm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.IpSpaceName.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "ip_space_name is required.")
|
||||
return
|
||||
}
|
||||
if config.UserPublicKey.IsNull() || config.UserPublicKey.IsUnknown() {
|
||||
if config.IpSpaceName.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.UserPublicKey.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "user_public_key is required.")
|
||||
return
|
||||
}
|
||||
if config.VappUid.IsNull() || config.VappUid.IsUnknown() {
|
||||
if config.UserPublicKey.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.VappUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vapp_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.VappUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -230,8 +254,10 @@ func (r *VcVmV3Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
417: resources_core.FormatString(config.UserPublicKey),
|
||||
448: resources_core.FormatString(config.AccessPortList),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 28, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 28, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcVmV3Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -256,6 +282,24 @@ func (r *VcVmV3Resource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vapp_uid is required.")
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalVappUid := data.VappUid
|
||||
if !data.VappUid.IsNull() && !data.VappUid.IsUnknown() {
|
||||
resolvedVappUid, err := r.client.ResolveRefSvcParamValue(ctx, 26, data.VappUid.ValueString())
|
||||
if err != nil {
|
||||
@@ -268,7 +312,9 @@ func (r *VcVmV3Resource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 28, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 28, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
407: resources_core.FormatString(data.VappUid),
|
||||
@@ -289,6 +335,9 @@ func (r *VcVmV3Resource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
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, 28, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -331,6 +380,16 @@ func (r *VcVmV3Resource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
state.VappUid = types.StringValue(resolvedVappUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalVappUid.IsNull() && !originalVappUid.IsUnknown() && !state.VappUid.IsNull() && !state.VappUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VappUid.ValueString(), originalVappUid.ValueString()) {
|
||||
state.VappUid = originalVappUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -391,6 +450,16 @@ func (r *VcVmV3Resource) Read(ctx context.Context, req resource.ReadRequest, res
|
||||
newState.VappUid = types.StringValue(resolvedVappUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.VappUid.IsNull() && !state.VappUid.IsUnknown() && !newState.VappUid.IsNull() && !newState.VappUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.VappUid.ValueString(), state.VappUid.ValueString()) {
|
||||
newState.VappUid = state.VappUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -445,7 +514,7 @@ func (r *VcVmV3Resource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.IpSpaceName.IsNull() != state.IpSpaceName.IsNull() || plan.IpSpaceName.IsUnknown() != state.IpSpaceName.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceName.IsNull() && !plan.IpSpaceName.IsUnknown() && !state.IpSpaceName.IsNull() && !state.IpSpaceName.IsUnknown() {
|
||||
if plan.IpSpaceName.ValueString() != state.IpSpaceName.ValueString() {
|
||||
if !strings.EqualFold(plan.IpSpaceName.ValueString(), state.IpSpaceName.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -454,7 +523,7 @@ func (r *VcVmV3Resource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.AccessIpList.IsNull() != state.AccessIpList.IsNull() || plan.AccessIpList.IsUnknown() != state.AccessIpList.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AccessIpList.IsNull() && !plan.AccessIpList.IsUnknown() && !state.AccessIpList.IsNull() && !state.AccessIpList.IsUnknown() {
|
||||
if plan.AccessIpList.ValueString() != state.AccessIpList.ValueString() {
|
||||
if !strings.EqualFold(plan.AccessIpList.ValueString(), state.AccessIpList.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -463,7 +532,7 @@ func (r *VcVmV3Resource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.AccessPortList.IsNull() != state.AccessPortList.IsNull() || plan.AccessPortList.IsUnknown() != state.AccessPortList.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AccessPortList.IsNull() && !plan.AccessPortList.IsUnknown() && !state.AccessPortList.IsNull() && !state.AccessPortList.IsUnknown() {
|
||||
if plan.AccessPortList.ValueString() != state.AccessPortList.ValueString() {
|
||||
if !strings.EqualFold(plan.AccessPortList.ValueString(), state.AccessPortList.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -496,6 +565,9 @@ func (r *VcVmV3Resource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -537,6 +609,15 @@ func (r *VcVmV3Resource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
state.VappUid = types.StringValue(resolvedVappUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.VappUid.IsNull() && !plan.VappUid.IsUnknown() && !state.VappUid.IsNull() && !state.VappUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VappUid.ValueString(), plan.VappUid.ValueString()) {
|
||||
state.VappUid = plan.VappUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -550,7 +631,6 @@ func (r *VcVmV3Resource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -560,6 +640,9 @@ func (r *VcVmV3Resource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,12 @@ type VcVdcGroupReconcileResource struct {
|
||||
}
|
||||
|
||||
type VcVdcGroupReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcVdcGroupID types.String `tfsdk:"vc_vdc_group_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcVdcGroupID types.String `tfsdk:"vc_vdc_group_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewVcVdcGroupReconcileResource() resource.Resource {
|
||||
@@ -40,10 +43,12 @@ func (r *VcVdcGroupReconcileResource) Metadata(ctx context.Context, req resource
|
||||
|
||||
func (r *VcVdcGroupReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_vdc_group_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"vc_vdc_group_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -68,6 +73,9 @@ func (r *VcVdcGroupReconcileResource) Create(ctx context.Context, req resource.C
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -29,6 +32,7 @@ type VcVdcGroupModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
VdcUid types.String `tfsdk:"vdc_uid"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
@@ -52,10 +56,11 @@ func (r *VcVdcGroupResource) Metadata(ctx context.Context, req resource.Metadata
|
||||
|
||||
func (r *VcVdcGroupResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"vdc_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UI display_name for service_id=21 (mapped to UUID in core) Не изменяется после создания."},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"vdc_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UI display_name for service_id=21 (mapped to UUID in core) Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -100,22 +105,11 @@ func (r *VcVdcGroupResource) ModifyPlan(ctx context.Context, req resource.Modify
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, plan.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve vdc_uid", err.Error())
|
||||
} else if resolvedVdcUid != "" && resolvedVdcUid != plan.VdcUid.ValueString() {
|
||||
plan.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -123,17 +117,23 @@ func (r *VcVdcGroupResource) ModifyPlan(ctx context.Context, req resource.Modify
|
||||
}
|
||||
}
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if plan.VdcUid.ValueString() != state.VdcUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.VdcUid.ValueString(), state.VdcUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить vdc_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.VdcUid.IsNull() || config.VdcUid.IsUnknown() {
|
||||
if config.VdcUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vdc_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.VdcUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -153,8 +153,10 @@ func (r *VcVdcGroupResource) ModifyPlan(ctx context.Context, req resource.Modify
|
||||
params := map[int]string{
|
||||
618: resources_core.FormatString(config.VdcUid),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 29, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 29, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *VcVdcGroupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -167,6 +169,24 @@ func (r *VcVdcGroupResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
resp.Diagnostics.AddError("Missing required attribute", "vdc_uid is required.")
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalVdcUid := data.VdcUid
|
||||
if !data.VdcUid.IsNull() && !data.VdcUid.IsUnknown() {
|
||||
resolvedVdcUid, err := r.client.ResolveRefSvcParamValue(ctx, 21, data.VdcUid.ValueString())
|
||||
if err != nil {
|
||||
@@ -179,7 +199,9 @@ func (r *VcVdcGroupResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 29, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 29, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
618: resources_core.FormatString(data.VdcUid),
|
||||
@@ -189,6 +211,9 @@ func (r *VcVdcGroupResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
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, 29, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -220,6 +245,16 @@ func (r *VcVdcGroupResource) Create(ctx context.Context, req resource.CreateRequ
|
||||
state.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalVdcUid.IsNull() && !originalVdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VdcUid.ValueString(), originalVdcUid.ValueString()) {
|
||||
state.VdcUid = originalVdcUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -269,6 +304,16 @@ func (r *VcVdcGroupResource) Read(ctx context.Context, req resource.ReadRequest,
|
||||
newState.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() && !newState.VdcUid.IsNull() && !newState.VdcUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.VdcUid.ValueString(), state.VdcUid.ValueString()) {
|
||||
newState.VdcUid = state.VdcUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -313,6 +358,9 @@ func (r *VcVdcGroupResource) Update(ctx context.Context, req resource.UpdateRequ
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -343,6 +391,15 @@ func (r *VcVdcGroupResource) Update(ctx context.Context, req resource.UpdateRequ
|
||||
state.VdcUid = types.StringValue(resolvedVdcUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.VdcUid.IsNull() && !plan.VdcUid.IsUnknown() && !state.VdcUid.IsNull() && !state.VdcUid.IsUnknown() {
|
||||
if strings.EqualFold(state.VdcUid.ValueString(), plan.VdcUid.ValueString()) {
|
||||
state.VdcUid = plan.VdcUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -356,16 +413,15 @@ func (r *VcVdcGroupResource) Delete(ctx context.Context, req resource.DeleteRequ
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: vc_vdc_group
|
||||
// Subresource: vdc
|
||||
|
||||
var _ resource.Resource = &VcVdcGroupVdcResource{}
|
||||
|
||||
// VcVdcGroupVdcResource manages a subresource via operations.
|
||||
type VcVdcGroupVdcResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type VcVdcGroupVdcModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
VcVdcGroupID types.String `tfsdk:"vc_vdc_group_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewVcVdcGroupVdcResource() resource.Resource {
|
||||
return &VcVdcGroupVdcResource{}
|
||||
}
|
||||
|
||||
func (r *VcVdcGroupVdcResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_vc_vdc_group_vdc"
|
||||
}
|
||||
|
||||
func (r *VcVdcGroupVdcResource) 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()}},
|
||||
"vc_vdc_group_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(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *VcVdcGroupVdcResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan VcVdcGroupVdcModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.VcVdcGroupID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{}
|
||||
identityCodes := []string{}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("vdc", 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, "vdc", idParams))
|
||||
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{})
|
||||
|
||||
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, "", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "", 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, "vdc", idParams))
|
||||
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 known && found {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "vdc", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект подтверждён в state_out, выполняется усыновление")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "vdc", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *VcVdcGroupVdcResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state VcVdcGroupVdcModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *VcVdcGroupVdcResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan VcVdcGroupVdcModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
var state VcVdcGroupVdcModel
|
||||
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.VcVdcGroupID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{}
|
||||
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "vdc", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *VcVdcGroupVdcResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state VcVdcGroupVdcModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(state.VcVdcGroupID.ValueString())
|
||||
if instanceUID == "" {
|
||||
return
|
||||
}
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{}
|
||||
identityCodes := []string{}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("vdc", 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 skipMissingOnDelete {
|
||||
resp.Diagnostics.AddWarning("Подресурс не найден", "Объект отсутствует и будет пропущен: "+targetValue)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Подресурс не найден", "Объект отсутствует: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
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, "", 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 *VcVdcGroupVdcResource) 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
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: template
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &TemplateReconcileResource{}
|
||||
|
||||
// TemplateReconcileResource triggers a one-time action.
|
||||
type TemplateReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type TemplateReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
TemplateID types.String `tfsdk:"template_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewTemplateReconcileResource() resource.Resource {
|
||||
return &TemplateReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *TemplateReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_template_reconcile"
|
||||
}
|
||||
|
||||
func (r *TemplateReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"template_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *TemplateReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan TemplateReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.TemplateID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *TemplateReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state TemplateReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *TemplateReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan TemplateReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.TemplateID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *TemplateReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *TemplateReconcileResource) 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
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,8 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,12 +32,10 @@ type TemplateModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
@@ -59,15 +58,13 @@ func (r *TemplateResource) Metadata(ctx context.Context, req resource.MetadataRe
|
||||
|
||||
func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(100)},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Квота диска (Указывается в GIGAbytes)"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(200)},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"access_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -118,22 +115,36 @@ func (r *TemplateResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -145,15 +156,14 @@ func (r *TemplateResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
params := map[int]string{
|
||||
202: resources_core.FormatInt64(config.ResourceCPU),
|
||||
203: resources_core.FormatInt64(config.ResourceMemory),
|
||||
204: resources_core.FormatInt64(config.ResourceDisk),
|
||||
205: resources_core.FormatInt64(config.ResourceInstances),
|
||||
206: resources_core.FormatString(config.ResourceRealm),
|
||||
207: resources_core.FormatString(config.Domain),
|
||||
813: resources_core.FormatString(config.ClusterConfiguration),
|
||||
814: resources_core.FormatString(config.StartupConfiguration),
|
||||
815: resources_core.FormatString(config.AccessConfiguration),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 2, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 2, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *TemplateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -162,27 +172,37 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 2, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 2, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
202: resources_core.FormatInt64(data.ResourceCPU),
|
||||
203: resources_core.FormatInt64(data.ResourceMemory),
|
||||
204: resources_core.FormatInt64(data.ResourceDisk),
|
||||
205: resources_core.FormatInt64(data.ResourceInstances),
|
||||
206: resources_core.FormatString(data.ResourceRealm),
|
||||
207: resources_core.FormatString(data.Domain),
|
||||
813: resources_core.FormatString(data.ClusterConfiguration),
|
||||
814: resources_core.FormatString(data.StartupConfiguration),
|
||||
815: resources_core.FormatString(data.AccessConfiguration),
|
||||
}
|
||||
|
||||
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, 2, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -200,12 +220,9 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -246,12 +263,9 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -281,37 +295,19 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPU.IsNull() != state.ResourceCPU.IsNull() || plan.ResourceCPU.IsUnknown() != state.ResourceCPU.IsUnknown() {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemory.IsNull() != state.ResourceMemory.IsNull() || plan.ResourceMemory.IsUnknown() != state.ResourceMemory.IsUnknown() {
|
||||
if plan.AccessConfiguration.IsNull() != state.AccessConfiguration.IsNull() || plan.AccessConfiguration.IsUnknown() != state.AccessConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceDisk.IsNull() != state.ResourceDisk.IsNull() || plan.ResourceDisk.IsUnknown() != state.ResourceDisk.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceDisk.IsNull() && !plan.ResourceDisk.IsUnknown() && !state.ResourceDisk.IsNull() && !state.ResourceDisk.IsUnknown() {
|
||||
if plan.ResourceDisk.ValueInt64() != state.ResourceDisk.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstances.IsNull() != state.ResourceInstances.IsNull() || plan.ResourceInstances.IsUnknown() != state.ResourceInstances.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
} else if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -332,16 +328,17 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
}
|
||||
|
||||
params := map[int]string{
|
||||
208: resources_core.FormatInt64(plan.ResourceCPU),
|
||||
209: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
210: resources_core.FormatInt64(plan.ResourceDisk),
|
||||
211: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
816: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
817: resources_core.FormatString(plan.AccessConfiguration),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -358,12 +355,9 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -382,7 +376,6 @@ func (r *TemplateResource) Delete(ctx context.Context, req resource.DeleteReques
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -392,6 +385,9 @@ func (r *TemplateResource) Delete(ctx context.Context, req resource.DeleteReques
|
||||
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
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: nextcloud
|
||||
// Subresource: backup
|
||||
|
||||
var _ resource.Resource = &NextcloudBackupResource{}
|
||||
|
||||
// NextcloudBackupResource manages a subresource via operations.
|
||||
type NextcloudBackupResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type NextcloudBackupModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
NextcloudID types.String `tfsdk:"nextcloud_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
}
|
||||
|
||||
func NewNextcloudBackupResource() resource.Resource {
|
||||
return &NextcloudBackupResource{}
|
||||
}
|
||||
|
||||
func (r *NextcloudBackupResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_nextcloud_backup"
|
||||
}
|
||||
|
||||
func (r *NextcloudBackupResource) 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()}},
|
||||
"nextcloud_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(false)},
|
||||
"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."},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *NextcloudBackupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan NextcloudBackupModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.StartupConfiguration.IsNull() || plan.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.NextcloudID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"startupConfiguration",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("backup", 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, "backup", idParams))
|
||||
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{
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
})
|
||||
|
||||
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, "create_backup", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_backup", 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, "backup", idParams))
|
||||
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 known && found {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект подтверждён в state_out, выполняется усыновление")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudBackupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state NextcloudBackupModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudBackupResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan NextcloudBackupModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
var state NextcloudBackupModel
|
||||
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.NextcloudID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
}
|
||||
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudBackupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state NextcloudBackupModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(state.NextcloudID.ValueString())
|
||||
if instanceUID == "" {
|
||||
return
|
||||
}
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{
|
||||
"startupConfiguration": resources_core.FormatString(state.StartupConfiguration),
|
||||
}
|
||||
identityCodes := []string{
|
||||
"startupConfiguration",
|
||||
}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("backup", 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 skipMissingOnDelete {
|
||||
resp.Diagnostics.AddWarning("Подресурс не найден", "Объект отсутствует и будет пропущен: "+targetValue)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Подресурс не найден", "Объект отсутствует: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
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, "", 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 *NextcloudBackupResource) 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
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: nextcloud
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &NextcloudReconcileResource{}
|
||||
|
||||
// NextcloudReconcileResource triggers a one-time action.
|
||||
type NextcloudReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type NextcloudReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
NextcloudID types.String `tfsdk:"nextcloud_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewNextcloudReconcileResource() resource.Resource {
|
||||
return &NextcloudReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *NextcloudReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_nextcloud_reconcile"
|
||||
}
|
||||
|
||||
func (r *NextcloudReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"nextcloud_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *NextcloudReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan NextcloudReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.NextcloudID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state NextcloudReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan NextcloudReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.NextcloudID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *NextcloudReconcileResource) 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
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: nextcloud
|
||||
// Action: recovery
|
||||
|
||||
var _ resource.Resource = &NextcloudRecoveryResource{}
|
||||
|
||||
// NextcloudRecoveryResource triggers a one-time action.
|
||||
type NextcloudRecoveryResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type NextcloudRecoveryModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
NextcloudID types.String `tfsdk:"nextcloud_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
AppConfiguration types.String `tfsdk:"app_configuration"`
|
||||
}
|
||||
|
||||
func NewNextcloudRecoveryResource() resource.Resource {
|
||||
return &NextcloudRecoveryResource{}
|
||||
}
|
||||
|
||||
func (r *NextcloudRecoveryResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_nextcloud_recovery"
|
||||
}
|
||||
|
||||
func (r *NextcloudRecoveryResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"nextcloud_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
"startup_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"access_configuration": schema.StringAttribute{Required: true},
|
||||
"app_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Версия Nextcloud будет взята из бэкапа"},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *NextcloudRecoveryResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan NextcloudRecoveryModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.StartupConfiguration.IsNull() || plan.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if plan.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if plan.AccessConfiguration.IsNull() || plan.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if plan.AppConfiguration.IsNull() || plan.AppConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "app_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.NextcloudID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
"clusterConfiguration": resources_core.FormatString(plan.ClusterConfiguration),
|
||||
"accessConfiguration": resources_core.FormatString(plan.AccessConfiguration),
|
||||
"appConfiguration": resources_core.FormatString(plan.AppConfiguration),
|
||||
}
|
||||
|
||||
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, "recovery", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "recovery", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudRecoveryResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state NextcloudRecoveryModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudRecoveryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan NextcloudRecoveryModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.NextcloudID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
"clusterConfiguration": resources_core.FormatString(plan.ClusterConfiguration),
|
||||
"accessConfiguration": resources_core.FormatString(plan.AccessConfiguration),
|
||||
"appConfiguration": resources_core.FormatString(plan.AppConfiguration),
|
||||
}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "recovery", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "recovery", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudRecoveryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *NextcloudRecoveryResource) 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
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,8 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,15 +32,11 @@ type NextcloudModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
AppVersion types.String `tfsdk:"app_version"`
|
||||
DocumentServer types.String `tfsdk:"document_server"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
PsqlUid types.String `tfsdk:"psql_uid"`
|
||||
RedisUid types.String `tfsdk:"redis_uid"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
AppConfiguration types.String `tfsdk:"app_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
@@ -62,18 +59,14 @@ func (r *NextcloudResource) Metadata(ctx context.Context, req resource.MetadataR
|
||||
|
||||
func (r *NextcloudResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"app_version": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("32.0.1")},
|
||||
"document_server": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("collabora"), MarkdownDescription: "Выбор сервера документов (onlyoffice)/(collabora). onlyoffice пока не доступен<br/>Размер пода collabora:<br/>1.8 ядра. 2Gi ОЗУ Не изменяется после создания."},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"psql_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UI display_name for service_id=90 (mapped to UUID in core) Не изменяется после создания."},
|
||||
"redis_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UI display_name for service_id=91 (mapped to UUID in core) Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1000), MarkdownDescription: "Квота ядра пода (Указывается в mili). 1 ядро = 1000 Mili"},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(2), MarkdownDescription: "Квота диска (Указывается в GIGAbytes)<br/>Диск не может быть меньше 2G (!)"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов<br/>Не может пока быть больше одного, так как нужны SC под RWMany"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1024), MarkdownDescription: "Квота памяти пода (Указывается в Megabytes)"},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"access_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"app_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -118,69 +111,58 @@ func (r *NextcloudResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.PsqlUid.IsNull() && !plan.PsqlUid.IsUnknown() {
|
||||
resolvedPsqlUid, err := r.client.ResolveRefSvcParamValue(ctx, 90, plan.PsqlUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve psql_uid", err.Error())
|
||||
} else if resolvedPsqlUid != "" && resolvedPsqlUid != plan.PsqlUid.ValueString() {
|
||||
plan.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if !plan.RedisUid.IsNull() && !plan.RedisUid.IsUnknown() {
|
||||
resolvedRedisUid, err := r.client.ResolveRefSvcParamValue(ctx, 91, plan.RedisUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve redis_uid", err.Error())
|
||||
} else if resolvedRedisUid != "" && resolvedRedisUid != plan.RedisUid.ValueString() {
|
||||
plan.RedisUid = types.StringValue(resolvedRedisUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
if !plan.DocumentServer.IsNull() && !plan.DocumentServer.IsUnknown() && !state.DocumentServer.IsNull() && !state.DocumentServer.IsUnknown() {
|
||||
if plan.DocumentServer.ValueString() != state.DocumentServer.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить document_server", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить access_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.PsqlUid.IsNull() && !plan.PsqlUid.IsUnknown() && !state.PsqlUid.IsNull() && !state.PsqlUid.IsUnknown() {
|
||||
if plan.PsqlUid.ValueString() != state.PsqlUid.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить psql_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.RedisUid.IsNull() && !plan.RedisUid.IsUnknown() && !state.RedisUid.IsNull() && !state.RedisUid.IsUnknown() {
|
||||
if plan.RedisUid.ValueString() != state.RedisUid.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить redis_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.PsqlUid.IsNull() || config.PsqlUid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "psql_uid is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.RedisUid.IsNull() || config.RedisUid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "redis_uid is required.")
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.AppConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "app_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AppConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -191,35 +173,16 @@ func (r *NextcloudResource) ModifyPlan(ctx context.Context, req resource.ModifyP
|
||||
if !config.AdoptExistingOnCreate.IsNull() && !config.AdoptExistingOnCreate.IsUnknown() {
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
if !config.PsqlUid.IsNull() && !config.PsqlUid.IsUnknown() {
|
||||
resolvedPsqlUid, err := r.client.ResolveRefSvcParamValue(ctx, 90, config.PsqlUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve psql_uid", err.Error())
|
||||
} else if resolvedPsqlUid != "" && resolvedPsqlUid != config.PsqlUid.ValueString() {
|
||||
config.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
}
|
||||
}
|
||||
if !config.RedisUid.IsNull() && !config.RedisUid.IsUnknown() {
|
||||
resolvedRedisUid, err := r.client.ResolveRefSvcParamValue(ctx, 91, config.RedisUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve redis_uid", err.Error())
|
||||
} else if resolvedRedisUid != "" && resolvedRedisUid != config.RedisUid.ValueString() {
|
||||
config.RedisUid = types.StringValue(resolvedRedisUid)
|
||||
}
|
||||
}
|
||||
params := map[int]string{
|
||||
21: resources_core.FormatString(config.Domain),
|
||||
99: resources_core.FormatInt64(config.ResourceCPU),
|
||||
100: resources_core.FormatInt64(config.ResourceMemory),
|
||||
101: resources_core.FormatInt64(config.ResourceDisk),
|
||||
192: resources_core.FormatString(config.DocumentServer),
|
||||
470: resources_core.FormatInt64(config.ResourceInstances),
|
||||
564: resources_core.FormatString(config.PsqlUid),
|
||||
569: resources_core.FormatString(config.AppVersion),
|
||||
570: resources_core.FormatString(config.RedisUid),
|
||||
1055: resources_core.FormatString(config.StartupConfiguration),
|
||||
1056: resources_core.FormatString(config.ClusterConfiguration),
|
||||
1057: resources_core.FormatString(config.AccessConfiguration),
|
||||
1058: resources_core.FormatString(config.AppConfiguration),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 50, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 50, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *NextcloudResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -228,54 +191,42 @@ func (r *NextcloudResource) Create(ctx context.Context, req resource.CreateReque
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.PsqlUid.IsNull() || data.PsqlUid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "psql_uid is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.RedisUid.IsNull() || data.RedisUid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "redis_uid is required.")
|
||||
if data.AppConfiguration.IsNull() || data.AppConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "app_configuration is required.")
|
||||
return
|
||||
}
|
||||
if !data.PsqlUid.IsNull() && !data.PsqlUid.IsUnknown() {
|
||||
resolvedPsqlUid, err := r.client.ResolveRefSvcParamValue(ctx, 90, data.PsqlUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
if resolvedPsqlUid != "" && resolvedPsqlUid != data.PsqlUid.ValueString() {
|
||||
data.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
}
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if !data.RedisUid.IsNull() && !data.RedisUid.IsUnknown() {
|
||||
resolvedRedisUid, err := r.client.ResolveRefSvcParamValue(ctx, 91, data.RedisUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
if resolvedRedisUid != "" && resolvedRedisUid != data.RedisUid.ValueString() {
|
||||
data.RedisUid = types.StringValue(resolvedRedisUid)
|
||||
}
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 50, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 50, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
21: resources_core.FormatString(data.Domain),
|
||||
99: resources_core.FormatInt64(data.ResourceCPU),
|
||||
100: resources_core.FormatInt64(data.ResourceMemory),
|
||||
101: resources_core.FormatInt64(data.ResourceDisk),
|
||||
192: resources_core.FormatString(data.DocumentServer),
|
||||
470: resources_core.FormatInt64(data.ResourceInstances),
|
||||
564: resources_core.FormatString(data.PsqlUid),
|
||||
569: resources_core.FormatString(data.AppVersion),
|
||||
570: resources_core.FormatString(data.RedisUid),
|
||||
1055: resources_core.FormatString(data.StartupConfiguration),
|
||||
1056: resources_core.FormatString(data.ClusterConfiguration),
|
||||
1057: resources_core.FormatString(data.AccessConfiguration),
|
||||
1058: resources_core.FormatString(data.AppConfiguration),
|
||||
}
|
||||
|
||||
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, 50, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -293,36 +244,15 @@ func (r *NextcloudResource) Create(ctx context.Context, req resource.CreateReque
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "documentServer", Field: "DocumentServer", Type: "string"},
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "psqlUid", Field: "PsqlUid", Type: "string"},
|
||||
{Code: "redisUid", Field: "RedisUid", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if !state.PsqlUid.IsNull() && !state.PsqlUid.IsUnknown() {
|
||||
resolvedPsqlUid, err := r.client.ResolveRefSvcParamValue(ctx, 90, state.PsqlUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve psql_uid", err.Error())
|
||||
} else if resolvedPsqlUid != "" && resolvedPsqlUid != state.PsqlUid.ValueString() {
|
||||
state.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
}
|
||||
}
|
||||
if !state.RedisUid.IsNull() && !state.RedisUid.IsUnknown() {
|
||||
resolvedRedisUid, err := r.client.ResolveRefSvcParamValue(ctx, 91, state.RedisUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve redis_uid", err.Error())
|
||||
} else if resolvedRedisUid != "" && resolvedRedisUid != state.RedisUid.ValueString() {
|
||||
state.RedisUid = types.StringValue(resolvedRedisUid)
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -358,36 +288,15 @@ func (r *NextcloudResource) Read(ctx context.Context, req resource.ReadRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "documentServer", Field: "DocumentServer", Type: "string"},
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "psqlUid", Field: "PsqlUid", Type: "string"},
|
||||
{Code: "redisUid", Field: "RedisUid", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if !newState.PsqlUid.IsNull() && !newState.PsqlUid.IsUnknown() {
|
||||
resolvedPsqlUid, err := r.client.ResolveRefSvcParamValue(ctx, 90, newState.PsqlUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve psql_uid", err.Error())
|
||||
} else if resolvedPsqlUid != "" && resolvedPsqlUid != newState.PsqlUid.ValueString() {
|
||||
newState.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
}
|
||||
}
|
||||
if !newState.RedisUid.IsNull() && !newState.RedisUid.IsUnknown() {
|
||||
resolvedRedisUid, err := r.client.ResolveRefSvcParamValue(ctx, 91, newState.RedisUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve redis_uid", err.Error())
|
||||
} else if resolvedRedisUid != "" && resolvedRedisUid != newState.RedisUid.ValueString() {
|
||||
newState.RedisUid = types.StringValue(resolvedRedisUid)
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -412,46 +321,19 @@ func (r *NextcloudResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPU.IsNull() != state.ResourceCPU.IsNull() || plan.ResourceCPU.IsUnknown() != state.ResourceCPU.IsUnknown() {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemory.IsNull() != state.ResourceMemory.IsNull() || plan.ResourceMemory.IsUnknown() != state.ResourceMemory.IsUnknown() {
|
||||
if plan.AppConfiguration.IsNull() != state.AppConfiguration.IsNull() || plan.AppConfiguration.IsUnknown() != state.AppConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceDisk.IsNull() != state.ResourceDisk.IsNull() || plan.ResourceDisk.IsUnknown() != state.ResourceDisk.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceDisk.IsNull() && !plan.ResourceDisk.IsUnknown() && !state.ResourceDisk.IsNull() && !state.ResourceDisk.IsUnknown() {
|
||||
if plan.ResourceDisk.ValueInt64() != state.ResourceDisk.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstances.IsNull() != state.ResourceInstances.IsNull() || plan.ResourceInstances.IsUnknown() != state.ResourceInstances.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AppVersion.IsNull() != state.AppVersion.IsNull() || plan.AppVersion.IsUnknown() != state.AppVersion.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AppVersion.IsNull() && !plan.AppVersion.IsUnknown() && !state.AppVersion.IsNull() && !state.AppVersion.IsUnknown() {
|
||||
if plan.AppVersion.ValueString() != state.AppVersion.ValueString() {
|
||||
} else if !plan.AppConfiguration.IsNull() && !plan.AppConfiguration.IsUnknown() && !state.AppConfiguration.IsNull() && !state.AppConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AppConfiguration.ValueString(), state.AppConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -472,17 +354,17 @@ func (r *NextcloudResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
}
|
||||
|
||||
params := map[int]string{
|
||||
178: resources_core.FormatInt64(plan.ResourceCPU),
|
||||
179: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
180: resources_core.FormatInt64(plan.ResourceDisk),
|
||||
547: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
571: resources_core.FormatString(plan.AppVersion),
|
||||
1059: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
1060: resources_core.FormatString(plan.AppConfiguration),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -499,36 +381,15 @@ func (r *NextcloudResource) Update(ctx context.Context, req resource.UpdateReque
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "documentServer", Field: "DocumentServer", Type: "string"},
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "psqlUid", Field: "PsqlUid", Type: "string"},
|
||||
{Code: "redisUid", Field: "RedisUid", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if !state.PsqlUid.IsNull() && !state.PsqlUid.IsUnknown() {
|
||||
resolvedPsqlUid, err := r.client.ResolveRefSvcParamValue(ctx, 90, state.PsqlUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve psql_uid", err.Error())
|
||||
} else if resolvedPsqlUid != "" && resolvedPsqlUid != state.PsqlUid.ValueString() {
|
||||
state.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
}
|
||||
}
|
||||
if !state.RedisUid.IsNull() && !state.RedisUid.IsUnknown() {
|
||||
resolvedRedisUid, err := r.client.ResolveRefSvcParamValue(ctx, 91, state.RedisUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve redis_uid", err.Error())
|
||||
} else if resolvedRedisUid != "" && resolvedRedisUid != state.RedisUid.ValueString() {
|
||||
state.RedisUid = types.StringValue(resolvedRedisUid)
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -542,7 +403,6 @@ func (r *NextcloudResource) Delete(ctx context.Context, req resource.DeleteReque
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -552,6 +412,9 @@ func (r *NextcloudResource) Delete(ctx context.Context, req resource.DeleteReque
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,12 @@ type NextcloudRestartResource struct {
|
||||
}
|
||||
|
||||
type NextcloudRestartModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
NextcloudID types.String `tfsdk:"nextcloud_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
NextcloudID types.String `tfsdk:"nextcloud_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewNextcloudRestartResource() resource.Resource {
|
||||
@@ -40,10 +43,12 @@ func (r *NextcloudRestartResource) Metadata(ctx context.Context, req resource.Me
|
||||
|
||||
func (r *NextcloudRestartResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"nextcloud_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"nextcloud_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -68,6 +73,9 @@ func (r *NextcloudRestartResource) Create(ctx context.Context, req resource.Crea
|
||||
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, "restart", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,6 +34,7 @@ type SupersetModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
Emails types.String `tfsdk:"emails"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
@@ -60,9 +64,10 @@ func (r *SupersetResource) Metadata(ctx context.Context, req resource.MetadataRe
|
||||
|
||||
func (r *SupersetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"emails": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("testuser1"), MarkdownDescription: "Учётная запись, которой будут выданы админские права. В формате email [ну не совсем. Для доменных учетных записей - без доменного суффикса]. Обязана находиться в Keycloak. Можно передавать несколько через пробел Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Квота ядра пода (Указывается в mili). 1000 Mili = 1 ядро Не изменяется после создания."},
|
||||
@@ -121,13 +126,19 @@ func (r *SupersetResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Domain.ValueString(), state.Domain.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Emails.IsNull() && !plan.Emails.IsUnknown() && !state.Emails.IsNull() && !state.Emails.IsUnknown() {
|
||||
if plan.Emails.ValueString() != state.Emails.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Emails.ValueString(), state.Emails.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить emails", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -157,17 +168,23 @@ func (r *SupersetResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -185,8 +202,13 @@ func (r *SupersetResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
109: resources_core.FormatString(config.ResourceRealm),
|
||||
227: resources_core.FormatInt64(config.ResourceInstances),
|
||||
}
|
||||
desiredDomain := ""
|
||||
if !config.Domain.IsNull() && !config.Domain.IsUnknown() {
|
||||
desiredDomain = config.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 81, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 81, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *SupersetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -201,7 +223,12 @@ func (r *SupersetResource) Create(ctx context.Context, req resource.CreateReques
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 81, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||||
desiredDomain = data.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 81, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
14: resources_core.FormatString(data.Domain),
|
||||
@@ -217,6 +244,9 @@ func (r *SupersetResource) Create(ctx context.Context, req resource.CreateReques
|
||||
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, 81, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -337,6 +367,9 @@ func (r *SupersetResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -378,16 +411,15 @@ func (r *SupersetResource) Delete(ctx context.Context, req resource.DeleteReques
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,8 +34,8 @@ type HarborModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
Emails types.String `tfsdk:"emails"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
@@ -60,16 +63,16 @@ func (r *HarborResource) Metadata(ctx context.Context, req resource.MetadataRequ
|
||||
|
||||
func (r *HarborResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"emails": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("testuser1"), MarkdownDescription: "Учётная запись, которой будут выданы админские права. Обязана находиться в Keycloak. Можно передавать несколько через пробел Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(100), MarkdownDescription: "Квота ядра пода (Указывается в mili). 1000Mili = 1 Ядро Не изменяется после создания."},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов Не изменяется после создания."},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(200), MarkdownDescription: "Квота памяти пода (Указывается в Megabytes) Не изменяется после создания."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"s3_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UUID экземпляра услуги S3 Не изменяется после создания."},
|
||||
"s3_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UUID экземпляра услуги S3 Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -114,22 +117,11 @@ func (r *HarborResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.S3Uid.IsNull() && !plan.S3Uid.IsUnknown() {
|
||||
resolvedS3Uid, err := r.client.ResolveRefSvcParamValue(ctx, 12, plan.S3Uid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve s3_uid", err.Error())
|
||||
} else if resolvedS3Uid != "" && resolvedS3Uid != plan.S3Uid.ValueString() {
|
||||
plan.S3Uid = types.StringValue(resolvedS3Uid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -137,17 +129,14 @@ func (r *HarborResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Domain.ValueString(), state.Domain.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Emails.IsNull() && !plan.Emails.IsUnknown() && !state.Emails.IsNull() && !state.Emails.IsUnknown() {
|
||||
if plan.Emails.ValueString() != state.Emails.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить emails", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_c_p_u", "Параметр задается при создании и не может быть изменен.")
|
||||
@@ -167,27 +156,39 @@ func (r *HarborResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.S3Uid.IsNull() && !plan.S3Uid.IsUnknown() && !state.S3Uid.IsNull() && !state.S3Uid.IsUnknown() {
|
||||
if plan.S3Uid.ValueString() != state.S3Uid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.S3Uid.ValueString(), state.S3Uid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить s3_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.S3Uid.IsNull() || config.S3Uid.IsUnknown() {
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.S3Uid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "s3_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.S3Uid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -206,15 +207,19 @@ func (r *HarborResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
}
|
||||
params := map[int]string{
|
||||
16: resources_core.FormatString(config.Domain),
|
||||
17: resources_core.FormatString(config.Emails),
|
||||
110: resources_core.FormatString(config.ResourceRealm),
|
||||
228: resources_core.FormatInt64(config.ResourceCPU),
|
||||
229: resources_core.FormatInt64(config.ResourceMemory),
|
||||
230: resources_core.FormatInt64(config.ResourceInstances),
|
||||
231: resources_core.FormatString(config.S3Uid),
|
||||
}
|
||||
desiredDomain := ""
|
||||
if !config.Domain.IsNull() && !config.Domain.IsUnknown() {
|
||||
desiredDomain = config.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 82, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 82, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *HarborResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -233,11 +238,15 @@ func (r *HarborResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 82, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||||
desiredDomain = data.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 82, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
16: resources_core.FormatString(data.Domain),
|
||||
17: resources_core.FormatString(data.Emails),
|
||||
110: resources_core.FormatString(data.ResourceRealm),
|
||||
228: resources_core.FormatInt64(data.ResourceCPU),
|
||||
229: resources_core.FormatInt64(data.ResourceMemory),
|
||||
@@ -249,6 +258,9 @@ func (r *HarborResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
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, 82, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -267,7 +279,6 @@ func (r *HarborResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "emails", Field: "Emails", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
@@ -277,6 +288,11 @@ func (r *HarborResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -313,7 +329,6 @@ func (r *HarborResource) Read(ctx context.Context, req resource.ReadRequest, res
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "emails", Field: "Emails", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
@@ -323,6 +338,11 @@ func (r *HarborResource) Read(ctx context.Context, req resource.ReadRequest, res
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -367,6 +387,9 @@ func (r *HarborResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -384,7 +407,6 @@ func (r *HarborResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "emails", Field: "Emails", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
@@ -394,6 +416,10 @@ func (r *HarborResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -407,16 +433,15 @@ func (r *HarborResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,6 +34,7 @@ type K8sZitiControllerModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
@@ -59,9 +63,10 @@ func (r *K8sZitiControllerResource) Metadata(ctx context.Context, req resource.M
|
||||
|
||||
func (r *K8sZitiControllerResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(300), MarkdownDescription: "Квота ядра пода (milicores) Не изменяется после создания."},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Квота диска (Указывается в GIGAbytes) Не изменяется после создания."},
|
||||
@@ -119,7 +124,10 @@ func (r *K8sZitiControllerResource) ModifyPlan(ctx context.Context, req resource
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Domain.ValueString(), state.Domain.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -149,17 +157,23 @@ func (r *K8sZitiControllerResource) ModifyPlan(ctx context.Context, req resource
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -176,8 +190,13 @@ func (r *K8sZitiControllerResource) ModifyPlan(ctx context.Context, req resource
|
||||
298: resources_core.FormatString(config.ResourceRealm),
|
||||
299: resources_core.FormatString(config.Domain),
|
||||
}
|
||||
desiredDomain := ""
|
||||
if !config.Domain.IsNull() && !config.Domain.IsUnknown() {
|
||||
desiredDomain = config.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 88, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 88, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *K8sZitiControllerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -192,7 +211,12 @@ func (r *K8sZitiControllerResource) Create(ctx context.Context, req resource.Cre
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 88, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||||
desiredDomain = data.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 88, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
294: resources_core.FormatInt64(data.ResourceCPU),
|
||||
@@ -207,6 +231,9 @@ func (r *K8sZitiControllerResource) Create(ctx context.Context, req resource.Cre
|
||||
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, 88, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -325,6 +352,9 @@ func (r *K8sZitiControllerResource) Update(ctx context.Context, req resource.Upd
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -365,16 +395,15 @@ func (r *K8sZitiControllerResource) Delete(ctx context.Context, req resource.Del
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: flask
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &FlaskReconcileResource{}
|
||||
|
||||
// FlaskReconcileResource triggers a one-time action.
|
||||
type FlaskReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type FlaskReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
FlaskID types.String `tfsdk:"flask_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewFlaskReconcileResource() resource.Resource {
|
||||
return &FlaskReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *FlaskReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_flask_reconcile"
|
||||
}
|
||||
|
||||
func (r *FlaskReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"flask_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *FlaskReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan FlaskReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.FlaskID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *FlaskReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state FlaskReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *FlaskReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan FlaskReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.FlaskID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *FlaskReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *FlaskReconcileResource) 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
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: flask
|
||||
// Action: redeploy
|
||||
|
||||
var _ resource.Resource = &FlaskRedeployResource{}
|
||||
|
||||
// FlaskRedeployResource triggers a one-time action.
|
||||
type FlaskRedeployResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type FlaskRedeployModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
FlaskID types.String `tfsdk:"flask_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
}
|
||||
|
||||
func NewFlaskRedeployResource() resource.Resource {
|
||||
return &FlaskRedeployResource{}
|
||||
}
|
||||
|
||||
func (r *FlaskRedeployResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_flask_redeploy"
|
||||
}
|
||||
|
||||
func (r *FlaskRedeployResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"flask_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
"startup_configuration": schema.StringAttribute{Required: true},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *FlaskRedeployResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan FlaskRedeployModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.StartupConfiguration.IsNull() || plan.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.FlaskID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
}
|
||||
|
||||
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, "redeploy", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "redeploy", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *FlaskRedeployResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state FlaskRedeployModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *FlaskRedeployResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan FlaskRedeployModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.FlaskID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "redeploy", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "redeploy", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *FlaskRedeployResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *FlaskRedeployResource) 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
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,8 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,14 +33,12 @@ type FlaskModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
GitPath types.String `tfsdk:"git_path"`
|
||||
HealthPath types.String `tfsdk:"health_path"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
AppConfiguration types.String `tfsdk:"app_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
JsonEnv types.String `tfsdk:"json_env"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
@@ -61,17 +61,15 @@ func (r *FlaskResource) Metadata(ctx context.Context, req resource.MetadataReque
|
||||
|
||||
func (r *FlaskResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"git_path": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("https://github.com/Foxyhhd/Baldurs-Gate-test.git"), MarkdownDescription: "Путь до Git, который будет webroot для сайта"},
|
||||
"health_path": schema.StringAttribute{Optional: true, MarkdownDescription: "Путь до healtcheck для запуска пода в Kubernetes, который отдаст 200 код Не изменяется после создания."},
|
||||
"json_env": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("{ "param": "value" }"), MarkdownDescription: "Массив переменных, будут переданы в env приложения"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(300), MarkdownDescription: "Квота ядра пода (milicores)"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(256), MarkdownDescription: "Квота памяти пода (MBytes)"},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"access_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"app_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"json_env": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("{}")},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -122,28 +120,52 @@ func (r *FlaskResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить access_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.HealthPath.IsNull() && !plan.HealthPath.IsUnknown() && !state.HealthPath.IsNull() && !state.HealthPath.IsUnknown() {
|
||||
if plan.HealthPath.ValueString() != state.HealthPath.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить health_path", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.AppConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "app_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AppConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -155,17 +177,16 @@ func (r *FlaskResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
params := map[int]string{
|
||||
248: resources_core.FormatString(config.Domain),
|
||||
249: resources_core.FormatString(config.ResourceRealm),
|
||||
250: resources_core.FormatInt64(config.ResourceCPU),
|
||||
251: resources_core.FormatInt64(config.ResourceMemory),
|
||||
252: resources_core.FormatInt64(config.ResourceInstances),
|
||||
253: resources_core.FormatString(config.GitPath),
|
||||
254: resources_core.FormatString(config.JsonEnv),
|
||||
255: resources_core.FormatString(config.HealthPath),
|
||||
1037: resources_core.FormatString(config.StartupConfiguration),
|
||||
1038: resources_core.FormatString(config.ClusterConfiguration),
|
||||
1039: resources_core.FormatString(config.AccessConfiguration),
|
||||
1040: resources_core.FormatString(config.AppConfiguration),
|
||||
1041: resources_core.FormatString(config.JsonEnv),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 89, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 89, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *FlaskResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -174,29 +195,43 @@ func (r *FlaskResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.AppConfiguration.IsNull() || data.AppConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "app_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 89, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 89, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
248: resources_core.FormatString(data.Domain),
|
||||
249: resources_core.FormatString(data.ResourceRealm),
|
||||
250: resources_core.FormatInt64(data.ResourceCPU),
|
||||
251: resources_core.FormatInt64(data.ResourceMemory),
|
||||
252: resources_core.FormatInt64(data.ResourceInstances),
|
||||
253: resources_core.FormatString(data.GitPath),
|
||||
254: resources_core.FormatString(data.JsonEnv),
|
||||
255: resources_core.FormatString(data.HealthPath),
|
||||
1037: resources_core.FormatString(data.StartupConfiguration),
|
||||
1038: resources_core.FormatString(data.ClusterConfiguration),
|
||||
1039: resources_core.FormatString(data.AccessConfiguration),
|
||||
1040: resources_core.FormatString(data.AppConfiguration),
|
||||
1041: resources_core.FormatString(data.JsonEnv),
|
||||
}
|
||||
|
||||
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, 89, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -214,14 +249,11 @@ func (r *FlaskResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "gitPath", Field: "GitPath", Type: "string"},
|
||||
{Code: "healthPath", Field: "HealthPath", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -262,14 +294,11 @@ func (r *FlaskResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "gitPath", Field: "GitPath", Type: "string"},
|
||||
{Code: "healthPath", Field: "HealthPath", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -299,37 +328,19 @@ func (r *FlaskResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPU.IsNull() != state.ResourceCPU.IsNull() || plan.ResourceCPU.IsUnknown() != state.ResourceCPU.IsUnknown() {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemory.IsNull() != state.ResourceMemory.IsNull() || plan.ResourceMemory.IsUnknown() != state.ResourceMemory.IsUnknown() {
|
||||
if plan.AppConfiguration.IsNull() != state.AppConfiguration.IsNull() || plan.AppConfiguration.IsUnknown() != state.AppConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstances.IsNull() != state.ResourceInstances.IsNull() || plan.ResourceInstances.IsUnknown() != state.ResourceInstances.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.GitPath.IsNull() != state.GitPath.IsNull() || plan.GitPath.IsUnknown() != state.GitPath.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.GitPath.IsNull() && !plan.GitPath.IsUnknown() && !state.GitPath.IsNull() && !state.GitPath.IsUnknown() {
|
||||
if plan.GitPath.ValueString() != state.GitPath.ValueString() {
|
||||
} else if !plan.AppConfiguration.IsNull() && !plan.AppConfiguration.IsUnknown() && !state.AppConfiguration.IsNull() && !state.AppConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AppConfiguration.ValueString(), state.AppConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -338,7 +349,7 @@ func (r *FlaskResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.JsonEnv.IsNull() != state.JsonEnv.IsNull() || plan.JsonEnv.IsUnknown() != state.JsonEnv.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.JsonEnv.IsNull() && !plan.JsonEnv.IsUnknown() && !state.JsonEnv.IsNull() && !state.JsonEnv.IsUnknown() {
|
||||
if plan.JsonEnv.ValueString() != state.JsonEnv.ValueString() {
|
||||
if !strings.EqualFold(plan.JsonEnv.ValueString(), state.JsonEnv.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -359,17 +370,18 @@ func (r *FlaskResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
}
|
||||
|
||||
params := map[int]string{
|
||||
451: resources_core.FormatInt64(plan.ResourceCPU),
|
||||
452: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
453: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
454: resources_core.FormatString(plan.GitPath),
|
||||
455: resources_core.FormatString(plan.JsonEnv),
|
||||
1042: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
1043: resources_core.FormatString(plan.AppConfiguration),
|
||||
1044: resources_core.FormatString(plan.JsonEnv),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -386,14 +398,11 @@ func (r *FlaskResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "gitPath", Field: "GitPath", Type: "string"},
|
||||
{Code: "healthPath", Field: "HealthPath", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -412,7 +421,6 @@ func (r *FlaskResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -422,6 +430,9 @@ func (r *FlaskResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: postgres
|
||||
// Subresource: backup
|
||||
|
||||
var _ resource.Resource = &PostgresBackupResource{}
|
||||
|
||||
// PostgresBackupResource manages a subresource via operations.
|
||||
type PostgresBackupResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type PostgresBackupModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
PostgresID types.String `tfsdk:"postgres_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewPostgresBackupResource() resource.Resource {
|
||||
return &PostgresBackupResource{}
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_postgres_backup"
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) 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()}},
|
||||
"postgres_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(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan PostgresBackupModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.PostgresID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
adoptExistingOnCreate := !plan.AdoptExistingOnCreate.IsNull() && !plan.AdoptExistingOnCreate.IsUnknown() && plan.AdoptExistingOnCreate.ValueBool()
|
||||
|
||||
idParams := map[string]string{}
|
||||
identityCodes := []string{}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("backup", 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, "backup", idParams))
|
||||
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{})
|
||||
|
||||
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, "create_backup", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_backup", 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, "backup", idParams))
|
||||
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 known && found {
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.AddWarning("Подресурс уже существует", "Операция вернула duplicate/exist, объект подтверждён в state_out, выполняется усыновление")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state PostgresBackupModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan PostgresBackupModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
var state PostgresBackupModel
|
||||
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.PostgresID.ValueString())
|
||||
if instanceUID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра")
|
||||
return
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
idParams := map[string]string{}
|
||||
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildSubresourceID(instanceUID, "backup", idParams))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *PostgresBackupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state PostgresBackupModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if "" == "" {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(state.PostgresID.ValueString())
|
||||
if instanceUID == "" {
|
||||
return
|
||||
}
|
||||
skipMissingOnDelete := !state.SkipMissingOnDelete.IsNull() && !state.SkipMissingOnDelete.IsUnknown() && state.SkipMissingOnDelete.ValueBool()
|
||||
|
||||
idParams := map[string]string{}
|
||||
identityCodes := []string{}
|
||||
listKey, idKey := resources_core.ResolveSubresourceStateKeys("backup", 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 skipMissingOnDelete {
|
||||
resp.Diagnostics.AddWarning("Подресурс не найден", "Объект отсутствует и будет пропущен: "+targetValue)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Подресурс не найден", "Объект отсутствует: "+targetValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := resources_core.CompactParams(map[string]string{})
|
||||
|
||||
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, "", 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 *PostgresBackupResource) 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
|
||||
}
|
||||
@@ -32,6 +32,7 @@ type PostgresDatabaseModel struct {
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
DbName types.String `tfsdk:"db_name"`
|
||||
DbOwner types.String `tfsdk:"db_owner"`
|
||||
}
|
||||
@@ -46,7 +47,7 @@ func (r *PostgresDatabaseResource) Metadata(ctx context.Context, req resource.Me
|
||||
|
||||
func (r *PostgresDatabaseResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"postgres_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
@@ -54,6 +55,7 @@ func (r *PostgresDatabaseResource) Schema(ctx context.Context, req resource.Sche
|
||||
"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(false)},
|
||||
"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."},
|
||||
"db_name": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"db_owner": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
@@ -131,8 +133,28 @@ func (r *PostgresDatabaseResource) Create(ctx context.Context, req resource.Crea
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_database", params, operationTimeout); err != nil {
|
||||
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(err) {
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_database", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_database", 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, "database", idParams))
|
||||
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 {
|
||||
@@ -149,7 +171,7 @@ func (r *PostgresDatabaseResource) Create(ctx context.Context, req resource.Crea
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
|
||||
return
|
||||
}
|
||||
if targetValue != "" {
|
||||
@@ -214,6 +236,9 @@ func (r *PostgresDatabaseResource) Update(ctx context.Context, req resource.Upda
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -286,6 +311,9 @@ func (r *PostgresDatabaseResource) Delete(ctx context.Context, req resource.Dele
|
||||
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, "delete_database", params, operationTimeout); err != nil {
|
||||
if resources_core.IsSubresourceMissingError(err) {
|
||||
if targetValue != "" {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: postgres
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &PostgresReconcileResource{}
|
||||
|
||||
// PostgresReconcileResource triggers a one-time action.
|
||||
type PostgresReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type PostgresReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
PostgresID types.String `tfsdk:"postgres_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewPostgresReconcileResource() resource.Resource {
|
||||
return &PostgresReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_postgres_reconcile"
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"postgres_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan PostgresReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.PostgresID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state PostgresReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan PostgresReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.PostgresID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *PostgresReconcileResource) 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
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -25,15 +25,19 @@ type PostgresRecoveryResource struct {
|
||||
}
|
||||
|
||||
type PostgresRecoveryModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
PostgresID types.String `tfsdk:"postgres_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
PostgresID types.String `tfsdk:"postgres_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
PostgresConfiguration types.String `tfsdk:"postgres_configuration"`
|
||||
PostgresConf types.String `tfsdk:"postgres_conf"`
|
||||
BackupConfiguration types.String `tfsdk:"backup_configuration"`
|
||||
AutoscaleConfiguration types.String `tfsdk:"autoscale_configuration"`
|
||||
}
|
||||
|
||||
func NewPostgresRecoveryResource() resource.Resource {
|
||||
@@ -46,15 +50,19 @@ func (r *PostgresRecoveryResource) Metadata(ctx context.Context, req resource.Me
|
||||
|
||||
func (r *PostgresRecoveryResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"postgres_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(512), MarkdownDescription: "Квота памяти пода (Указывается в Megabytes)<br/>"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Квота ядра пода (Указывается в mili)<br/>"},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Квота диска (Указывается в GIGAbytes)"},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания"},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"postgres_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
"startup_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"access_configuration": schema.StringAttribute{Required: true},
|
||||
"postgres_configuration": schema.StringAttribute{Required: true},
|
||||
"postgres_conf": schema.StringAttribute{Optional: true},
|
||||
"backup_configuration": schema.StringAttribute{Required: true},
|
||||
"autoscale_configuration": schema.StringAttribute{Required: true},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -65,8 +73,28 @@ func (r *PostgresRecoveryResource) Create(ctx context.Context, req resource.Crea
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.ResourceRealm.IsNull() || plan.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if plan.StartupConfiguration.IsNull() || plan.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if plan.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if plan.AccessConfiguration.IsNull() || plan.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if plan.PostgresConfiguration.IsNull() || plan.PostgresConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "postgres_configuration is required.")
|
||||
return
|
||||
}
|
||||
if plan.BackupConfiguration.IsNull() || plan.BackupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "backup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if plan.AutoscaleConfiguration.IsNull() || plan.AutoscaleConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "autoscale_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -78,17 +106,22 @@ func (r *PostgresRecoveryResource) Create(ctx context.Context, req resource.Crea
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"resourceInstances": resources_core.FormatInt64(plan.ResourceInstances),
|
||||
"resourceMemory": resources_core.FormatInt64(plan.ResourceMemory),
|
||||
"resourceCPU": resources_core.FormatInt64(plan.ResourceCPU),
|
||||
"resourceDisk": resources_core.FormatInt64(plan.ResourceDisk),
|
||||
"resourceRealm": resources_core.FormatString(plan.ResourceRealm),
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
"clusterConfiguration": resources_core.FormatString(plan.ClusterConfiguration),
|
||||
"accessConfiguration": resources_core.FormatString(plan.AccessConfiguration),
|
||||
"postgresConfiguration": resources_core.FormatString(plan.PostgresConfiguration),
|
||||
"postgresConf": resources_core.FormatString(plan.PostgresConf),
|
||||
"backupConfiguration": resources_core.FormatString(plan.BackupConfiguration),
|
||||
"autoscaleConfiguration": resources_core.FormatString(plan.AutoscaleConfiguration),
|
||||
}
|
||||
|
||||
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, "recovery", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -122,11 +155,13 @@ func (r *PostgresRecoveryResource) Update(ctx context.Context, req resource.Upda
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"resourceInstances": resources_core.FormatInt64(plan.ResourceInstances),
|
||||
"resourceMemory": resources_core.FormatInt64(plan.ResourceMemory),
|
||||
"resourceCPU": resources_core.FormatInt64(plan.ResourceCPU),
|
||||
"resourceDisk": resources_core.FormatInt64(plan.ResourceDisk),
|
||||
"resourceRealm": resources_core.FormatString(plan.ResourceRealm),
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
"clusterConfiguration": resources_core.FormatString(plan.ClusterConfiguration),
|
||||
"accessConfiguration": resources_core.FormatString(plan.AccessConfiguration),
|
||||
"postgresConfiguration": resources_core.FormatString(plan.PostgresConfiguration),
|
||||
"postgresConf": resources_core.FormatString(plan.PostgresConf),
|
||||
"backupConfiguration": resources_core.FormatString(plan.BackupConfiguration),
|
||||
"autoscaleConfiguration": resources_core.FormatString(plan.AutoscaleConfiguration),
|
||||
}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "recovery", params); err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,8 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -28,40 +29,27 @@ type PostgresResource struct {
|
||||
}
|
||||
|
||||
type PostgresModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
AllowNoSSL types.Bool `tfsdk:"allow_no_s_s_l"`
|
||||
AppVersion types.String `tfsdk:"app_version"`
|
||||
AutoScale types.Bool `tfsdk:"auto_scale"`
|
||||
AutoScalePercentage types.Int64 `tfsdk:"auto_scale_percentage"`
|
||||
AutoScaleQuotaGb types.String `tfsdk:"auto_scale_quota_gb"`
|
||||
AutoScaleTechWindow types.Int64 `tfsdk:"auto_scale_tech_window"`
|
||||
EnablePgPoolerMaster types.Bool `tfsdk:"enable_pg_pooler_master"`
|
||||
EnablePgPoolerSlave types.Bool `tfsdk:"enable_pg_pooler_slave"`
|
||||
ExtBACKUPNUMTORETAIN types.Int64 `tfsdk:"ext__b_a_c_k_u_p__n_u_m__t_o__r_e_t_a_i_n"`
|
||||
ExtBACKUPSCHEDULE types.String `tfsdk:"ext__b_a_c_k_u_p__s_c_h_e_d_u_l_e"`
|
||||
IpSpaceNameMaster types.String `tfsdk:"ip_space_name_master"`
|
||||
IpSpaceNameSlave types.String `tfsdk:"ip_space_name_slave"`
|
||||
JsonParameters types.String `tfsdk:"json_parameters"`
|
||||
NeedExternalAddressMaster types.Bool `tfsdk:"need_external_address_master"`
|
||||
NeedExternalAddressSlave types.Bool `tfsdk:"need_external_address_slave"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.String `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
S3Uid types.String `tfsdk:"s3_uid"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
AutoscaleConfiguration types.String `tfsdk:"autoscale_configuration"`
|
||||
BackupConfiguration types.String `tfsdk:"backup_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
PostgresConf types.String `tfsdk:"postgres_conf"`
|
||||
PostgresConfiguration types.String `tfsdk:"postgres_configuration"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
}
|
||||
|
||||
func NewPostgresResource() resource.Resource {
|
||||
@@ -74,40 +62,27 @@ func (r *PostgresResource) Metadata(ctx context.Context, req resource.MetadataRe
|
||||
|
||||
func (r *PostgresResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"allow_no_s_s_l": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "По умолчанию подключение обязательное (sslmode=require). Выставление в true отключит этот параметр"},
|
||||
"app_version": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("17"), MarkdownDescription: "Версия PostgreSQL"},
|
||||
"auto_scale": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Включает автоскейлинг PV. При включении параметра необходимо настроить autoScalePercentage и autoScaleTechWindow. Алерт при срабатывании вызывает modify у текущей услуги Не изменяется после создания."},
|
||||
"auto_scale_percentage": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(10), MarkdownDescription: "Требует включения autoScale. Позволяет указать в процентах расширение от текущего макс объема Не изменяется после создания."},
|
||||
"auto_scale_quota_gb": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("1"), MarkdownDescription: "Квота для autoScale Не изменяется после создания."},
|
||||
"auto_scale_tech_window": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(0), MarkdownDescription: "Требует включения autoScale. Окно обновления (пока не реализовано). Указывается час в виде Integer (0 / 5 / 12 / 23) Не изменяется после создания."},
|
||||
"enable_pg_pooler_master": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Включает pgPooler (на запись). Будет создан новый под"},
|
||||
"enable_pg_pooler_slave": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Включает pgPooler (на чтение). Будет создан новый под"},
|
||||
"ext__b_a_c_k_u_p__n_u_m__t_o__r_e_t_a_i_n": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(7), MarkdownDescription: "Не обязательный. Количество сохраняемых бекапов"},
|
||||
"ext__b_a_c_k_u_p__s_c_h_e_d_u_l_e": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("0 0 * * *"), MarkdownDescription: "Не обязательный. Время запуска бекапа"},
|
||||
"ip_space_name_master": schema.StringAttribute{Optional: true, MarkdownDescription: "Имя ipSpace для публикации VIP master. Не может быть пустым, если включен needExternalAddressMaster. Пример: `internet-no-antiddos-v1`"},
|
||||
"ip_space_name_slave": schema.StringAttribute{Optional: true, MarkdownDescription: "Имя ipSpace для публикации VIP slave. Не может быть пустым, если включен needExternalAddressSlave"},
|
||||
"json_parameters": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("{ \"log_connections\": \"off\", \"log_disconnections\": \"off\" }"), MarkdownDescription: "Массив переменных, более подробнее про настройку можно посмотреть https://www.postgresql.org/docs/current/runtime-config.html"},
|
||||
"need_external_address_master": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Выделяет внешний (белый) IP для доступа к master"},
|
||||
"need_external_address_slave": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Выделяет внешний (белый) IP для доступа к slave. Будет работать, если slave в принципе есть"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Квота ядра пода (milicores)"},
|
||||
"resource_disk": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("10"), MarkdownDescription: "Квота диска (Указывается в GIGAbytes)"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(512), MarkdownDescription: "Квота памяти пода (MBytes)"},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"s3_uid": schema.StringAttribute{Required: true, MarkdownDescription: "Экземпляр (учетная запись) S3 для резервного копирования. Резервное копирование обязательно Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
"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."},
|
||||
"access_configuration": schema.StringAttribute{Required: true},
|
||||
"autoscale_configuration": schema.StringAttribute{Required: true},
|
||||
"backup_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"postgres_conf": schema.StringAttribute{Optional: true},
|
||||
"postgres_configuration": schema.StringAttribute{Required: true},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
}
|
||||
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
@@ -142,72 +117,63 @@ func (r *PostgresResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.S3Uid.IsNull() && !plan.S3Uid.IsUnknown() {
|
||||
resolvedS3Uid, err := r.client.ResolveRefSvcParamValue(ctx, 12, plan.S3Uid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve s3_uid", err.Error())
|
||||
} else if resolvedS3Uid != "" && resolvedS3Uid != plan.S3Uid.ValueString() {
|
||||
plan.S3Uid = types.StringValue(resolvedS3Uid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
if !plan.AutoScale.IsNull() && !plan.AutoScale.IsUnknown() && !state.AutoScale.IsNull() && !state.AutoScale.IsUnknown() {
|
||||
if plan.AutoScale.ValueBool() != state.AutoScale.ValueBool() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить auto_scale", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.AutoScalePercentage.IsNull() && !plan.AutoScalePercentage.IsUnknown() && !state.AutoScalePercentage.IsNull() && !state.AutoScalePercentage.IsUnknown() {
|
||||
if plan.AutoScalePercentage.ValueInt64() != state.AutoScalePercentage.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить auto_scale_percentage", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.AutoScaleQuotaGb.IsNull() && !plan.AutoScaleQuotaGb.IsUnknown() && !state.AutoScaleQuotaGb.IsNull() && !state.AutoScaleQuotaGb.IsUnknown() {
|
||||
if plan.AutoScaleQuotaGb.ValueString() != state.AutoScaleQuotaGb.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить auto_scale_quota_gb", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.AutoScaleTechWindow.IsNull() && !plan.AutoScaleTechWindow.IsUnknown() && !state.AutoScaleTechWindow.IsNull() && !state.AutoScaleTechWindow.IsUnknown() {
|
||||
if plan.AutoScaleTechWindow.ValueInt64() != state.AutoScaleTechWindow.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить auto_scale_tech_window", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.S3Uid.IsNull() && !plan.S3Uid.IsUnknown() && !state.S3Uid.IsNull() && !state.S3Uid.IsUnknown() {
|
||||
if plan.S3Uid.ValueString() != state.S3Uid.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить s3_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.S3Uid.IsNull() || config.S3Uid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "s3_uid is required.")
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.AutoscaleConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "autoscale_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AutoscaleConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.BackupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "backup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.BackupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.PostgresConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "postgres_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.PostgresConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -218,39 +184,19 @@ func (r *PostgresResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
if !config.AdoptExistingOnCreate.IsNull() && !config.AdoptExistingOnCreate.IsUnknown() {
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
if !config.S3Uid.IsNull() && !config.S3Uid.IsUnknown() {
|
||||
resolvedS3Uid, err := r.client.ResolveRefSvcParamValue(ctx, 12, config.S3Uid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve s3_uid", err.Error())
|
||||
} else if resolvedS3Uid != "" && resolvedS3Uid != config.S3Uid.ValueString() {
|
||||
config.S3Uid = types.StringValue(resolvedS3Uid)
|
||||
}
|
||||
}
|
||||
params := map[int]string{
|
||||
23: resources_core.FormatString(config.S3Uid),
|
||||
80: resources_core.FormatInt64(config.ResourceInstances),
|
||||
81: resources_core.FormatInt64(config.ResourceMemory),
|
||||
82: resources_core.FormatInt64(config.ResourceCPU),
|
||||
83: resources_core.FormatString(config.ResourceDisk),
|
||||
102: resources_core.FormatString(config.ResourceRealm),
|
||||
145: resources_core.FormatBool(config.NeedExternalAddressMaster),
|
||||
146: resources_core.FormatBool(config.NeedExternalAddressSlave),
|
||||
265: resources_core.FormatString(config.ExtBACKUPSCHEDULE),
|
||||
266: resources_core.FormatInt64(config.ExtBACKUPNUMTORETAIN),
|
||||
310: resources_core.FormatString(config.AppVersion),
|
||||
311: resources_core.FormatString(config.JsonParameters),
|
||||
312: resources_core.FormatBool(config.EnablePgPoolerMaster),
|
||||
313: resources_core.FormatBool(config.EnablePgPoolerSlave),
|
||||
314: resources_core.FormatBool(config.AllowNoSSL),
|
||||
329: resources_core.FormatBool(config.AutoScale),
|
||||
330: resources_core.FormatInt64(config.AutoScalePercentage),
|
||||
331: resources_core.FormatInt64(config.AutoScaleTechWindow),
|
||||
337: resources_core.FormatString(config.IpSpaceNameMaster),
|
||||
338: resources_core.FormatString(config.IpSpaceNameSlave),
|
||||
339: resources_core.FormatString(config.AutoScaleQuotaGb),
|
||||
788: resources_core.FormatString(config.ClusterConfiguration),
|
||||
789: resources_core.FormatString(config.StartupConfiguration),
|
||||
790: resources_core.FormatString(config.AccessConfiguration),
|
||||
791: resources_core.FormatString(config.PostgresConfiguration),
|
||||
792: resources_core.FormatString(config.PostgresConf),
|
||||
793: resources_core.FormatString(config.BackupConfiguration),
|
||||
794: resources_core.FormatString(config.AutoscaleConfiguration),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 90, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 90, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *PostgresResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -259,46 +205,53 @@ func (r *PostgresResource) Create(ctx context.Context, req resource.CreateReques
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.S3Uid.IsNull() || data.S3Uid.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "s3_uid is required.")
|
||||
if data.AutoscaleConfiguration.IsNull() || data.AutoscaleConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "autoscale_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.BackupConfiguration.IsNull() || data.BackupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "backup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.PostgresConfiguration.IsNull() || data.PostgresConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "postgres_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 90, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 90, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
23: resources_core.FormatString(data.S3Uid),
|
||||
80: resources_core.FormatInt64(data.ResourceInstances),
|
||||
81: resources_core.FormatInt64(data.ResourceMemory),
|
||||
82: resources_core.FormatInt64(data.ResourceCPU),
|
||||
83: resources_core.FormatString(data.ResourceDisk),
|
||||
102: resources_core.FormatString(data.ResourceRealm),
|
||||
145: resources_core.FormatBool(data.NeedExternalAddressMaster),
|
||||
146: resources_core.FormatBool(data.NeedExternalAddressSlave),
|
||||
265: resources_core.FormatString(data.ExtBACKUPSCHEDULE),
|
||||
266: resources_core.FormatInt64(data.ExtBACKUPNUMTORETAIN),
|
||||
310: resources_core.FormatString(data.AppVersion),
|
||||
311: resources_core.FormatString(data.JsonParameters),
|
||||
312: resources_core.FormatBool(data.EnablePgPoolerMaster),
|
||||
313: resources_core.FormatBool(data.EnablePgPoolerSlave),
|
||||
314: resources_core.FormatBool(data.AllowNoSSL),
|
||||
329: resources_core.FormatBool(data.AutoScale),
|
||||
330: resources_core.FormatInt64(data.AutoScalePercentage),
|
||||
331: resources_core.FormatInt64(data.AutoScaleTechWindow),
|
||||
337: resources_core.FormatString(data.IpSpaceNameMaster),
|
||||
338: resources_core.FormatString(data.IpSpaceNameSlave),
|
||||
339: resources_core.FormatString(data.AutoScaleQuotaGb),
|
||||
788: resources_core.FormatString(data.ClusterConfiguration),
|
||||
789: resources_core.FormatString(data.StartupConfiguration),
|
||||
790: resources_core.FormatString(data.AccessConfiguration),
|
||||
791: resources_core.FormatString(data.PostgresConfiguration),
|
||||
792: resources_core.FormatString(data.PostgresConf),
|
||||
793: resources_core.FormatString(data.BackupConfiguration),
|
||||
794: resources_core.FormatString(data.AutoscaleConfiguration),
|
||||
}
|
||||
|
||||
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, 90, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -316,26 +269,13 @@ func (r *PostgresResource) Create(ctx context.Context, req resource.CreateReques
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "allowNoSSL", Field: "AllowNoSSL", Type: "bool"},
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "autoScale", Field: "AutoScale", Type: "bool"},
|
||||
{Code: "autoScalePercentage", Field: "AutoScalePercentage", Type: "int64"},
|
||||
{Code: "autoScaleQuotaGb", Field: "AutoScaleQuotaGb", Type: "string"},
|
||||
{Code: "autoScaleTechWindow", Field: "AutoScaleTechWindow", Type: "int64"},
|
||||
{Code: "enablePgPoolerMaster", Field: "EnablePgPoolerMaster", Type: "bool"},
|
||||
{Code: "enablePgPoolerSlave", Field: "EnablePgPoolerSlave", Type: "bool"},
|
||||
{Code: "ext_BACKUP_NUM_TO_RETAIN", Field: "ExtBACKUPNUMTORETAIN", Type: "int64"},
|
||||
{Code: "ext_BACKUP_SCHEDULE", Field: "ExtBACKUPSCHEDULE", Type: "string"},
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "ipSpaceNameSlave", Field: "IpSpaceNameSlave", Type: "string"},
|
||||
{Code: "jsonParameters", Field: "JsonParameters", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "needExternalAddressSlave", Field: "NeedExternalAddressSlave", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "string"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "autoscaleConfiguration", Field: "AutoscaleConfiguration", Type: "string"},
|
||||
{Code: "backupConfiguration", Field: "BackupConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "postgresConf", Field: "PostgresConf", Type: "string"},
|
||||
{Code: "postgresConfiguration", Field: "PostgresConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -376,26 +316,13 @@ func (r *PostgresResource) Read(ctx context.Context, req resource.ReadRequest, r
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "allowNoSSL", Field: "AllowNoSSL", Type: "bool"},
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "autoScale", Field: "AutoScale", Type: "bool"},
|
||||
{Code: "autoScalePercentage", Field: "AutoScalePercentage", Type: "int64"},
|
||||
{Code: "autoScaleQuotaGb", Field: "AutoScaleQuotaGb", Type: "string"},
|
||||
{Code: "autoScaleTechWindow", Field: "AutoScaleTechWindow", Type: "int64"},
|
||||
{Code: "enablePgPoolerMaster", Field: "EnablePgPoolerMaster", Type: "bool"},
|
||||
{Code: "enablePgPoolerSlave", Field: "EnablePgPoolerSlave", Type: "bool"},
|
||||
{Code: "ext_BACKUP_NUM_TO_RETAIN", Field: "ExtBACKUPNUMTORETAIN", Type: "int64"},
|
||||
{Code: "ext_BACKUP_SCHEDULE", Field: "ExtBACKUPSCHEDULE", Type: "string"},
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "ipSpaceNameSlave", Field: "IpSpaceNameSlave", Type: "string"},
|
||||
{Code: "jsonParameters", Field: "JsonParameters", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "needExternalAddressSlave", Field: "NeedExternalAddressSlave", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "string"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "autoscaleConfiguration", Field: "AutoscaleConfiguration", Type: "string"},
|
||||
{Code: "backupConfiguration", Field: "BackupConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "postgresConf", Field: "PostgresConf", Type: "string"},
|
||||
{Code: "postgresConfiguration", Field: "PostgresConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -425,136 +352,55 @@ func (r *PostgresResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstances.IsNull() != state.ResourceInstances.IsNull() || plan.ResourceInstances.IsUnknown() != state.ResourceInstances.IsUnknown() {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemory.IsNull() != state.ResourceMemory.IsNull() || plan.ResourceMemory.IsUnknown() != state.ResourceMemory.IsUnknown() {
|
||||
if plan.AccessConfiguration.IsNull() != state.AccessConfiguration.IsNull() || plan.AccessConfiguration.IsUnknown() != state.AccessConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
} else if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPU.IsNull() != state.ResourceCPU.IsNull() || plan.ResourceCPU.IsUnknown() != state.ResourceCPU.IsUnknown() {
|
||||
if plan.PostgresConfiguration.IsNull() != state.PostgresConfiguration.IsNull() || plan.PostgresConfiguration.IsUnknown() != state.PostgresConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
} else if !plan.PostgresConfiguration.IsNull() && !plan.PostgresConfiguration.IsUnknown() && !state.PostgresConfiguration.IsNull() && !state.PostgresConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.PostgresConfiguration.ValueString(), state.PostgresConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceDisk.IsNull() != state.ResourceDisk.IsNull() || plan.ResourceDisk.IsUnknown() != state.ResourceDisk.IsUnknown() {
|
||||
if plan.PostgresConf.IsNull() != state.PostgresConf.IsNull() || plan.PostgresConf.IsUnknown() != state.PostgresConf.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceDisk.IsNull() && !plan.ResourceDisk.IsUnknown() && !state.ResourceDisk.IsNull() && !state.ResourceDisk.IsUnknown() {
|
||||
if plan.ResourceDisk.ValueString() != state.ResourceDisk.ValueString() {
|
||||
} else if !plan.PostgresConf.IsNull() && !plan.PostgresConf.IsUnknown() && !state.PostgresConf.IsNull() && !state.PostgresConf.IsUnknown() {
|
||||
if !strings.EqualFold(plan.PostgresConf.ValueString(), state.PostgresConf.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.NeedExternalAddressMaster.IsNull() != state.NeedExternalAddressMaster.IsNull() || plan.NeedExternalAddressMaster.IsUnknown() != state.NeedExternalAddressMaster.IsUnknown() {
|
||||
if plan.BackupConfiguration.IsNull() != state.BackupConfiguration.IsNull() || plan.BackupConfiguration.IsUnknown() != state.BackupConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.NeedExternalAddressMaster.IsNull() && !plan.NeedExternalAddressMaster.IsUnknown() && !state.NeedExternalAddressMaster.IsNull() && !state.NeedExternalAddressMaster.IsUnknown() {
|
||||
if plan.NeedExternalAddressMaster.ValueBool() != state.NeedExternalAddressMaster.ValueBool() {
|
||||
} else if !plan.BackupConfiguration.IsNull() && !plan.BackupConfiguration.IsUnknown() && !state.BackupConfiguration.IsNull() && !state.BackupConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.BackupConfiguration.ValueString(), state.BackupConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.NeedExternalAddressSlave.IsNull() != state.NeedExternalAddressSlave.IsNull() || plan.NeedExternalAddressSlave.IsUnknown() != state.NeedExternalAddressSlave.IsUnknown() {
|
||||
if plan.AutoscaleConfiguration.IsNull() != state.AutoscaleConfiguration.IsNull() || plan.AutoscaleConfiguration.IsUnknown() != state.AutoscaleConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.NeedExternalAddressSlave.IsNull() && !plan.NeedExternalAddressSlave.IsUnknown() && !state.NeedExternalAddressSlave.IsNull() && !state.NeedExternalAddressSlave.IsUnknown() {
|
||||
if plan.NeedExternalAddressSlave.ValueBool() != state.NeedExternalAddressSlave.ValueBool() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ExtBACKUPSCHEDULE.IsNull() != state.ExtBACKUPSCHEDULE.IsNull() || plan.ExtBACKUPSCHEDULE.IsUnknown() != state.ExtBACKUPSCHEDULE.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ExtBACKUPSCHEDULE.IsNull() && !plan.ExtBACKUPSCHEDULE.IsUnknown() && !state.ExtBACKUPSCHEDULE.IsNull() && !state.ExtBACKUPSCHEDULE.IsUnknown() {
|
||||
if plan.ExtBACKUPSCHEDULE.ValueString() != state.ExtBACKUPSCHEDULE.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ExtBACKUPNUMTORETAIN.IsNull() != state.ExtBACKUPNUMTORETAIN.IsNull() || plan.ExtBACKUPNUMTORETAIN.IsUnknown() != state.ExtBACKUPNUMTORETAIN.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ExtBACKUPNUMTORETAIN.IsNull() && !plan.ExtBACKUPNUMTORETAIN.IsUnknown() && !state.ExtBACKUPNUMTORETAIN.IsNull() && !state.ExtBACKUPNUMTORETAIN.IsUnknown() {
|
||||
if plan.ExtBACKUPNUMTORETAIN.ValueInt64() != state.ExtBACKUPNUMTORETAIN.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AppVersion.IsNull() != state.AppVersion.IsNull() || plan.AppVersion.IsUnknown() != state.AppVersion.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AppVersion.IsNull() && !plan.AppVersion.IsUnknown() && !state.AppVersion.IsNull() && !state.AppVersion.IsUnknown() {
|
||||
if plan.AppVersion.ValueString() != state.AppVersion.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.JsonParameters.IsNull() != state.JsonParameters.IsNull() || plan.JsonParameters.IsUnknown() != state.JsonParameters.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.JsonParameters.IsNull() && !plan.JsonParameters.IsUnknown() && !state.JsonParameters.IsNull() && !state.JsonParameters.IsUnknown() {
|
||||
if plan.JsonParameters.ValueString() != state.JsonParameters.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.EnablePgPoolerMaster.IsNull() != state.EnablePgPoolerMaster.IsNull() || plan.EnablePgPoolerMaster.IsUnknown() != state.EnablePgPoolerMaster.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.EnablePgPoolerMaster.IsNull() && !plan.EnablePgPoolerMaster.IsUnknown() && !state.EnablePgPoolerMaster.IsNull() && !state.EnablePgPoolerMaster.IsUnknown() {
|
||||
if plan.EnablePgPoolerMaster.ValueBool() != state.EnablePgPoolerMaster.ValueBool() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.EnablePgPoolerSlave.IsNull() != state.EnablePgPoolerSlave.IsNull() || plan.EnablePgPoolerSlave.IsUnknown() != state.EnablePgPoolerSlave.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.EnablePgPoolerSlave.IsNull() && !plan.EnablePgPoolerSlave.IsUnknown() && !state.EnablePgPoolerSlave.IsNull() && !state.EnablePgPoolerSlave.IsUnknown() {
|
||||
if plan.EnablePgPoolerSlave.ValueBool() != state.EnablePgPoolerSlave.ValueBool() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AllowNoSSL.IsNull() != state.AllowNoSSL.IsNull() || plan.AllowNoSSL.IsUnknown() != state.AllowNoSSL.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AllowNoSSL.IsNull() && !plan.AllowNoSSL.IsUnknown() && !state.AllowNoSSL.IsNull() && !state.AllowNoSSL.IsUnknown() {
|
||||
if plan.AllowNoSSL.ValueBool() != state.AllowNoSSL.ValueBool() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IpSpaceNameMaster.IsNull() != state.IpSpaceNameMaster.IsNull() || plan.IpSpaceNameMaster.IsUnknown() != state.IpSpaceNameMaster.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceNameMaster.IsNull() && !plan.IpSpaceNameMaster.IsUnknown() && !state.IpSpaceNameMaster.IsNull() && !state.IpSpaceNameMaster.IsUnknown() {
|
||||
if plan.IpSpaceNameMaster.ValueString() != state.IpSpaceNameMaster.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IpSpaceNameSlave.IsNull() != state.IpSpaceNameSlave.IsNull() || plan.IpSpaceNameSlave.IsUnknown() != state.IpSpaceNameSlave.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceNameSlave.IsNull() && !plan.IpSpaceNameSlave.IsUnknown() && !state.IpSpaceNameSlave.IsNull() && !state.IpSpaceNameSlave.IsUnknown() {
|
||||
if plan.IpSpaceNameSlave.ValueString() != state.IpSpaceNameSlave.ValueString() {
|
||||
} else if !plan.AutoscaleConfiguration.IsNull() && !plan.AutoscaleConfiguration.IsUnknown() && !state.AutoscaleConfiguration.IsNull() && !state.AutoscaleConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AutoscaleConfiguration.ValueString(), state.AutoscaleConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -575,27 +421,21 @@ func (r *PostgresResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
}
|
||||
|
||||
params := map[int]string{
|
||||
91: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
92: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
93: resources_core.FormatInt64(plan.ResourceCPU),
|
||||
94: resources_core.FormatString(plan.ResourceDisk),
|
||||
147: resources_core.FormatBool(plan.NeedExternalAddressMaster),
|
||||
148: resources_core.FormatBool(plan.NeedExternalAddressSlave),
|
||||
267: resources_core.FormatString(plan.ExtBACKUPSCHEDULE),
|
||||
268: resources_core.FormatInt64(plan.ExtBACKUPNUMTORETAIN),
|
||||
315: resources_core.FormatString(plan.AppVersion),
|
||||
316: resources_core.FormatString(plan.JsonParameters),
|
||||
317: resources_core.FormatBool(plan.EnablePgPoolerMaster),
|
||||
318: resources_core.FormatBool(plan.EnablePgPoolerSlave),
|
||||
319: resources_core.FormatBool(plan.AllowNoSSL),
|
||||
541: resources_core.FormatString(plan.IpSpaceNameMaster),
|
||||
542: resources_core.FormatString(plan.IpSpaceNameSlave),
|
||||
795: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
796: resources_core.FormatString(plan.AccessConfiguration),
|
||||
797: resources_core.FormatString(plan.PostgresConfiguration),
|
||||
798: resources_core.FormatString(plan.PostgresConf),
|
||||
799: resources_core.FormatString(plan.BackupConfiguration),
|
||||
800: resources_core.FormatString(plan.AutoscaleConfiguration),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -612,26 +452,13 @@ func (r *PostgresResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "allowNoSSL", Field: "AllowNoSSL", Type: "bool"},
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "autoScale", Field: "AutoScale", Type: "bool"},
|
||||
{Code: "autoScalePercentage", Field: "AutoScalePercentage", Type: "int64"},
|
||||
{Code: "autoScaleQuotaGb", Field: "AutoScaleQuotaGb", Type: "string"},
|
||||
{Code: "autoScaleTechWindow", Field: "AutoScaleTechWindow", Type: "int64"},
|
||||
{Code: "enablePgPoolerMaster", Field: "EnablePgPoolerMaster", Type: "bool"},
|
||||
{Code: "enablePgPoolerSlave", Field: "EnablePgPoolerSlave", Type: "bool"},
|
||||
{Code: "ext_BACKUP_NUM_TO_RETAIN", Field: "ExtBACKUPNUMTORETAIN", Type: "int64"},
|
||||
{Code: "ext_BACKUP_SCHEDULE", Field: "ExtBACKUPSCHEDULE", Type: "string"},
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "ipSpaceNameSlave", Field: "IpSpaceNameSlave", Type: "string"},
|
||||
{Code: "jsonParameters", Field: "JsonParameters", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "needExternalAddressSlave", Field: "NeedExternalAddressSlave", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "string"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "autoscaleConfiguration", Field: "AutoscaleConfiguration", Type: "string"},
|
||||
{Code: "backupConfiguration", Field: "BackupConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "postgresConf", Field: "PostgresConf", Type: "string"},
|
||||
{Code: "postgresConfiguration", Field: "PostgresConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -650,7 +477,6 @@ func (r *PostgresResource) Delete(ctx context.Context, req resource.DeleteReques
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -660,6 +486,9 @@ func (r *PostgresResource) Delete(ctx context.Context, req resource.DeleteReques
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,12 @@ type PostgresRestartResource struct {
|
||||
}
|
||||
|
||||
type PostgresRestartModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
PostgresID types.String `tfsdk:"postgres_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
PostgresID types.String `tfsdk:"postgres_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewPostgresRestartResource() resource.Resource {
|
||||
@@ -40,10 +43,12 @@ func (r *PostgresRestartResource) Metadata(ctx context.Context, req resource.Met
|
||||
|
||||
func (r *PostgresRestartResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"postgres_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"postgres_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -68,6 +73,9 @@ func (r *PostgresRestartResource) Create(ctx context.Context, req resource.Creat
|
||||
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, "restart", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -32,6 +32,7 @@ type PostgresUserModel struct {
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Role types.String `tfsdk:"role"`
|
||||
Username types.String `tfsdk:"username"`
|
||||
}
|
||||
@@ -46,7 +47,7 @@ func (r *PostgresUserResource) Metadata(ctx context.Context, req resource.Metada
|
||||
|
||||
func (r *PostgresUserResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"postgres_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
@@ -54,6 +55,7 @@ func (r *PostgresUserResource) Schema(ctx context.Context, req resource.SchemaRe
|
||||
"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(false)},
|
||||
"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."},
|
||||
"role": schema.StringAttribute{Required: true, MarkdownDescription: "Выбрать роль из списка Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
"username": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания. Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
}
|
||||
@@ -131,8 +133,28 @@ func (r *PostgresUserResource) Create(ctx context.Context, req resource.CreateRe
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", params, operationTimeout); err != nil {
|
||||
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(err) {
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", 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, "user", idParams))
|
||||
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 {
|
||||
@@ -149,7 +171,7 @@ func (r *PostgresUserResource) Create(ctx context.Context, req resource.CreateRe
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
|
||||
return
|
||||
}
|
||||
if targetValue != "" {
|
||||
@@ -214,6 +236,9 @@ func (r *PostgresUserResource) Update(ctx context.Context, req resource.UpdateRe
|
||||
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, "", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -286,6 +311,9 @@ func (r *PostgresUserResource) Delete(ctx context.Context, req resource.DeleteRe
|
||||
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, "delete_user", params, operationTimeout); err != nil {
|
||||
if resources_core.IsSubresourceMissingError(err) {
|
||||
if targetValue != "" {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: redis
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &RedisReconcileResource{}
|
||||
|
||||
// RedisReconcileResource triggers a one-time action.
|
||||
type RedisReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type RedisReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
RedisID types.String `tfsdk:"redis_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewRedisReconcileResource() resource.Resource {
|
||||
return &RedisReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *RedisReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_redis_reconcile"
|
||||
}
|
||||
|
||||
func (r *RedisReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"redis_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *RedisReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan RedisReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.RedisID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *RedisReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state RedisReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *RedisReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan RedisReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.RedisID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *RedisReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *RedisReconcileResource) 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
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,7 +11,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -27,28 +29,23 @@ type RedisResource struct {
|
||||
}
|
||||
|
||||
type RedisModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
IpSpaceNameMaster types.String `tfsdk:"ip_space_name_master"`
|
||||
IpSpaceNameSlave types.String `tfsdk:"ip_space_name_slave"`
|
||||
NeedExternalAddressMaster types.Bool `tfsdk:"need_external_address_master"`
|
||||
NeedExternalAddressSlave types.Bool `tfsdk:"need_external_address_slave"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
}
|
||||
|
||||
func NewRedisResource() resource.Resource {
|
||||
@@ -61,28 +58,23 @@ func (r *RedisResource) Metadata(ctx context.Context, req resource.MetadataReque
|
||||
|
||||
func (r *RedisResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"ip_space_name_master": schema.StringAttribute{Optional: true, MarkdownDescription: "Имя ipSpace для публикации VIP master. Не может быть пустым, если включен needExternalAddressMaster Не изменяется после создания."},
|
||||
"ip_space_name_slave": schema.StringAttribute{Optional: true, MarkdownDescription: "Имя ipSpace для публикации VIP slave. Не может быть пустым, если включен needExternalAddressSlave Не изменяется после создания."},
|
||||
"need_external_address_master": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Выделяет внешний IP для доступа к master Не изменяется после создания."},
|
||||
"need_external_address_slave": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Выделяет внешний IP для доступа к slave. Будет работать если slave в целом есть Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1000), MarkdownDescription: "Квота ядра пода (Указывается в mili) Не изменяется после создания."},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(30), MarkdownDescription: "Квота диска (Указывается в GIGAbytes) Не изменяется после создания."},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов Не изменяется после создания."},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1024), MarkdownDescription: "Квота памяти пода (Указывается в Megabytes) Не изменяется после создания."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
"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."},
|
||||
"access_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
}
|
||||
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
@@ -123,64 +115,36 @@ func (r *RedisResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.IpSpaceNameMaster.IsNull() && !plan.IpSpaceNameMaster.IsUnknown() && !state.IpSpaceNameMaster.IsNull() && !state.IpSpaceNameMaster.IsUnknown() {
|
||||
if plan.IpSpaceNameMaster.ValueString() != state.IpSpaceNameMaster.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить ip_space_name_master", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.IpSpaceNameSlave.IsNull() && !plan.IpSpaceNameSlave.IsUnknown() && !state.IpSpaceNameSlave.IsNull() && !state.IpSpaceNameSlave.IsUnknown() {
|
||||
if plan.IpSpaceNameSlave.ValueString() != state.IpSpaceNameSlave.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить ip_space_name_slave", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.NeedExternalAddressMaster.IsNull() && !plan.NeedExternalAddressMaster.IsUnknown() && !state.NeedExternalAddressMaster.IsNull() && !state.NeedExternalAddressMaster.IsUnknown() {
|
||||
if plan.NeedExternalAddressMaster.ValueBool() != state.NeedExternalAddressMaster.ValueBool() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить need_external_address_master", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.NeedExternalAddressSlave.IsNull() && !plan.NeedExternalAddressSlave.IsUnknown() && !state.NeedExternalAddressSlave.IsNull() && !state.NeedExternalAddressSlave.IsUnknown() {
|
||||
if plan.NeedExternalAddressSlave.ValueBool() != state.NeedExternalAddressSlave.ValueBool() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить need_external_address_slave", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_c_p_u", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceDisk.IsNull() && !plan.ResourceDisk.IsUnknown() && !state.ResourceDisk.IsNull() && !state.ResourceDisk.IsUnknown() {
|
||||
if plan.ResourceDisk.ValueInt64() != state.ResourceDisk.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_disk", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_instances", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_memory", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -192,18 +156,14 @@ func (r *RedisResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
params := map[int]string{
|
||||
112: resources_core.FormatInt64(config.ResourceCPU),
|
||||
113: resources_core.FormatInt64(config.ResourceMemory),
|
||||
114: resources_core.FormatInt64(config.ResourceDisk),
|
||||
181: resources_core.FormatInt64(config.ResourceInstances),
|
||||
182: resources_core.FormatString(config.ResourceRealm),
|
||||
183: resources_core.FormatBool(config.NeedExternalAddressMaster),
|
||||
184: resources_core.FormatBool(config.NeedExternalAddressSlave),
|
||||
548: resources_core.FormatString(config.IpSpaceNameMaster),
|
||||
549: resources_core.FormatString(config.IpSpaceNameSlave),
|
||||
1061: resources_core.FormatString(config.StartupConfiguration),
|
||||
1062: resources_core.FormatString(config.ClusterConfiguration),
|
||||
1063: resources_core.FormatString(config.AccessConfiguration),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 91, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 91, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *RedisResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -212,30 +172,37 @@ func (r *RedisResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 91, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 91, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
112: resources_core.FormatInt64(data.ResourceCPU),
|
||||
113: resources_core.FormatInt64(data.ResourceMemory),
|
||||
114: resources_core.FormatInt64(data.ResourceDisk),
|
||||
181: resources_core.FormatInt64(data.ResourceInstances),
|
||||
182: resources_core.FormatString(data.ResourceRealm),
|
||||
183: resources_core.FormatBool(data.NeedExternalAddressMaster),
|
||||
184: resources_core.FormatBool(data.NeedExternalAddressSlave),
|
||||
548: resources_core.FormatString(data.IpSpaceNameMaster),
|
||||
549: resources_core.FormatString(data.IpSpaceNameSlave),
|
||||
1061: resources_core.FormatString(data.StartupConfiguration),
|
||||
1062: resources_core.FormatString(data.ClusterConfiguration),
|
||||
1063: resources_core.FormatString(data.AccessConfiguration),
|
||||
}
|
||||
|
||||
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, 91, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -253,15 +220,9 @@ func (r *RedisResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "ipSpaceNameSlave", Field: "IpSpaceNameSlave", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "needExternalAddressSlave", Field: "NeedExternalAddressSlave", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -302,15 +263,9 @@ func (r *RedisResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "ipSpaceNameSlave", Field: "IpSpaceNameSlave", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "needExternalAddressSlave", Field: "NeedExternalAddressSlave", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -339,6 +294,24 @@ func (r *RedisResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
}
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AccessConfiguration.IsNull() != state.AccessConfiguration.IsNull() || plan.AccessConfiguration.IsUnknown() != state.AccessConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasServiceParamChanges {
|
||||
plan.ID = instanceID
|
||||
@@ -354,12 +327,18 @@ func (r *RedisResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
return
|
||||
}
|
||||
|
||||
params := map[int]string{}
|
||||
params := map[int]string{
|
||||
1064: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
1065: resources_core.FormatString(plan.AccessConfiguration),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -376,15 +355,9 @@ func (r *RedisResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "ipSpaceNameSlave", Field: "IpSpaceNameSlave", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "needExternalAddressSlave", Field: "NeedExternalAddressSlave", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -403,7 +376,6 @@ func (r *RedisResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -413,6 +385,9 @@ func (r *RedisResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,7 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -27,26 +30,27 @@ type MongodbResource struct {
|
||||
}
|
||||
|
||||
type MongodbModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
IpSpaceNameMaster types.String `tfsdk:"ip_space_name_master"`
|
||||
NeedExternalAddressMaster types.String `tfsdk:"need_external_address_master"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
IpSpaceNameMaster types.String `tfsdk:"ip_space_name_master"`
|
||||
MasterAccessList types.String `tfsdk:"master_access_list"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
}
|
||||
|
||||
func NewMongodbResource() resource.Resource {
|
||||
@@ -59,26 +63,27 @@ func (r *MongodbResource) Metadata(ctx context.Context, req resource.MetadataReq
|
||||
|
||||
func (r *MongodbResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"ip_space_name_master": schema.StringAttribute{Optional: true, MarkdownDescription: "Если включен предыдущий параметр, то поле необходимо заполнить. Пример: `internet-no-antiddos-v1` Не изменяется после создания."},
|
||||
"need_external_address_master": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("false"), MarkdownDescription: "Флаг, определяющий, нужно ли выделять внешний IP для доступа к master-брокеру. Пример: `true` или `false` Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Required: true, MarkdownDescription: "Пример: `1000` Не изменяется после создания."},
|
||||
"resource_disk": schema.Int64Attribute{Required: true, MarkdownDescription: "Пример: `20` Не изменяется после создания."},
|
||||
"resource_instances": schema.Int64Attribute{Required: true, MarkdownDescription: "Пример: `2` Не изменяется после создания."},
|
||||
"resource_memory": schema.Int64Attribute{Required: true, MarkdownDescription: "Пример: `2048` Не изменяется после создания."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Выбрать платформу из списка Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
"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."},
|
||||
"ip_space_name_master": schema.StringAttribute{Optional: true, MarkdownDescription: "Если включен предыдущий параметр, то поле необходимо заполнить. Пример: `internet-no-antiddos-v1`"},
|
||||
"master_access_list": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("[]"), MarkdownDescription: "Список IP-адресов, с которых разрешён доступ. Если оставить пустым — доступ открыт всем.", PlanModifiers: []planmodifier.String{resources_core.JsonNormalize()}},
|
||||
"resource_c_p_u": schema.Int64Attribute{Required: true, MarkdownDescription: "Пример: `1000`"},
|
||||
"resource_disk": schema.Int64Attribute{Required: true, MarkdownDescription: "Пример: `20`"},
|
||||
"resource_instances": schema.Int64Attribute{Required: true, MarkdownDescription: "Пример: `2`"},
|
||||
"resource_memory": schema.Int64Attribute{Required: true, MarkdownDescription: "Пример: `2048`"},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Выбрать платформу из списка Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
}
|
||||
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
@@ -119,70 +124,52 @@ func (r *MongodbResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.IpSpaceNameMaster.IsNull() && !plan.IpSpaceNameMaster.IsUnknown() && !state.IpSpaceNameMaster.IsNull() && !state.IpSpaceNameMaster.IsUnknown() {
|
||||
if plan.IpSpaceNameMaster.ValueString() != state.IpSpaceNameMaster.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить ip_space_name_master", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.NeedExternalAddressMaster.IsNull() && !plan.NeedExternalAddressMaster.IsUnknown() && !state.NeedExternalAddressMaster.IsNull() && !state.NeedExternalAddressMaster.IsUnknown() {
|
||||
if plan.NeedExternalAddressMaster.ValueString() != state.NeedExternalAddressMaster.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить need_external_address_master", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_c_p_u", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceDisk.IsNull() && !plan.ResourceDisk.IsUnknown() && !state.ResourceDisk.IsNull() && !state.ResourceDisk.IsUnknown() {
|
||||
if plan.ResourceDisk.ValueInt64() != state.ResourceDisk.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_disk", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_instances", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_memory", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceCPU.IsNull() || config.ResourceCPU.IsUnknown() {
|
||||
if config.ResourceCPU.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_c_p_u is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceDisk.IsNull() || config.ResourceDisk.IsUnknown() {
|
||||
if config.ResourceCPU.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceDisk.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_disk is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceInstances.IsNull() || config.ResourceInstances.IsUnknown() {
|
||||
if config.ResourceDisk.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceInstances.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_instances is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceMemory.IsNull() || config.ResourceMemory.IsUnknown() {
|
||||
if config.ResourceInstances.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceMemory.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_memory is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceMemory.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -197,11 +184,13 @@ func (r *MongodbResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
589: resources_core.FormatInt64(config.ResourceCPU),
|
||||
590: resources_core.FormatInt64(config.ResourceDisk),
|
||||
595: resources_core.FormatString(config.ResourceRealm),
|
||||
645: resources_core.FormatString(config.NeedExternalAddressMaster),
|
||||
646: resources_core.FormatString(config.IpSpaceNameMaster),
|
||||
889: resources_core.FormatString(config.MasterAccessList),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 92, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 92, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *MongodbResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -232,7 +221,9 @@ func (r *MongodbResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 92, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 92, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
487: resources_core.FormatInt64(data.ResourceInstances),
|
||||
@@ -240,14 +231,17 @@ func (r *MongodbResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
589: resources_core.FormatInt64(data.ResourceCPU),
|
||||
590: resources_core.FormatInt64(data.ResourceDisk),
|
||||
595: resources_core.FormatString(data.ResourceRealm),
|
||||
645: resources_core.FormatString(data.NeedExternalAddressMaster),
|
||||
646: resources_core.FormatString(data.IpSpaceNameMaster),
|
||||
889: resources_core.FormatString(data.MasterAccessList),
|
||||
}
|
||||
|
||||
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, 92, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -266,7 +260,7 @@ func (r *MongodbResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "string"},
|
||||
{Code: "masterAccessList", Field: "MasterAccessList", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
@@ -313,7 +307,7 @@ func (r *MongodbResource) Read(ctx context.Context, req resource.ReadRequest, re
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "string"},
|
||||
{Code: "masterAccessList", Field: "MasterAccessList", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
@@ -347,6 +341,60 @@ func (r *MongodbResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
}
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstances.IsNull() != state.ResourceInstances.IsNull() || plan.ResourceInstances.IsUnknown() != state.ResourceInstances.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemory.IsNull() != state.ResourceMemory.IsNull() || plan.ResourceMemory.IsUnknown() != state.ResourceMemory.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPU.IsNull() != state.ResourceCPU.IsNull() || plan.ResourceCPU.IsUnknown() != state.ResourceCPU.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceDisk.IsNull() != state.ResourceDisk.IsNull() || plan.ResourceDisk.IsUnknown() != state.ResourceDisk.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceDisk.IsNull() && !plan.ResourceDisk.IsUnknown() && !state.ResourceDisk.IsNull() && !state.ResourceDisk.IsUnknown() {
|
||||
if plan.ResourceDisk.ValueInt64() != state.ResourceDisk.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IpSpaceNameMaster.IsNull() != state.IpSpaceNameMaster.IsNull() || plan.IpSpaceNameMaster.IsUnknown() != state.IpSpaceNameMaster.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceNameMaster.IsNull() && !plan.IpSpaceNameMaster.IsUnknown() && !state.IpSpaceNameMaster.IsNull() && !state.IpSpaceNameMaster.IsUnknown() {
|
||||
if !strings.EqualFold(plan.IpSpaceNameMaster.ValueString(), state.IpSpaceNameMaster.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.MasterAccessList.IsNull() != state.MasterAccessList.IsNull() || plan.MasterAccessList.IsUnknown() != state.MasterAccessList.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.MasterAccessList.IsNull() && !plan.MasterAccessList.IsUnknown() && !state.MasterAccessList.IsNull() && !state.MasterAccessList.IsUnknown() {
|
||||
if !strings.EqualFold(plan.MasterAccessList.ValueString(), state.MasterAccessList.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasServiceParamChanges {
|
||||
plan.ID = instanceID
|
||||
@@ -362,12 +410,22 @@ func (r *MongodbResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
return
|
||||
}
|
||||
|
||||
params := map[int]string{}
|
||||
params := map[int]string{
|
||||
648: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
649: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
650: resources_core.FormatInt64(plan.ResourceCPU),
|
||||
651: resources_core.FormatInt64(plan.ResourceDisk),
|
||||
653: resources_core.FormatString(plan.IpSpaceNameMaster),
|
||||
890: resources_core.FormatString(plan.MasterAccessList),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -385,7 +443,7 @@ func (r *MongodbResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "string"},
|
||||
{Code: "masterAccessList", Field: "MasterAccessList", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
@@ -409,7 +467,6 @@ func (r *MongodbResource) Delete(ctx context.Context, req resource.DeleteRequest
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -419,6 +476,9 @@ func (r *MongodbResource) Delete(ctx context.Context, req resource.DeleteRequest
|
||||
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
|
||||
|
||||
@@ -32,6 +32,7 @@ type MongodbUserModel struct {
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
SkipMissingOnDelete types.Bool `tfsdk:"skip_missing_on_delete"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
DbName types.String `tfsdk:"db_name"`
|
||||
Role types.String `tfsdk:"role"`
|
||||
Username types.String `tfsdk:"username"`
|
||||
@@ -47,7 +48,7 @@ func (r *MongodbUserResource) Metadata(ctx context.Context, req resource.Metadat
|
||||
|
||||
func (r *MongodbUserResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"mongodb_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
@@ -55,6 +56,7 @@ func (r *MongodbUserResource) Schema(ctx context.Context, req resource.SchemaReq
|
||||
"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(false)},
|
||||
"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."},
|
||||
"db_name": schema.StringAttribute{Required: true},
|
||||
"role": schema.StringAttribute{Required: true},
|
||||
"username": schema.StringAttribute{Required: true, MarkdownDescription: "Изменение требует пересоздания.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||
@@ -136,8 +138,28 @@ func (r *MongodbUserResource) Create(ctx context.Context, req resource.CreateReq
|
||||
if !plan.OperationTimeout.IsNull() && !plan.OperationTimeout.IsUnknown() {
|
||||
operationTimeout = plan.OperationTimeout.ValueString()
|
||||
}
|
||||
if err := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", params, operationTimeout); err != nil {
|
||||
if adoptExistingOnCreate && resources_core.IsSubresourceAlreadyExistsError(err) {
|
||||
if !plan.LogLevel.IsNull() && !plan.LogLevel.IsUnknown() {
|
||||
ctx = core.CtxWithLogLevel(ctx, plan.LogLevel.ValueString())
|
||||
}
|
||||
createErr := resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", params, operationTimeout)
|
||||
if createErr != nil && resources_core.IsSubresourceTransientDependencyError(createErr) {
|
||||
createErr = resources_core.RunOperationByCodeWithTimeout(ctx, r.client, instanceUID, "create_user", 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, "user", idParams))
|
||||
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 {
|
||||
@@ -154,7 +176,7 @@ func (r *MongodbUserResource) Create(ctx context.Context, req resource.CreateReq
|
||||
resp.Diagnostics.AddError("Нарушена консистентность", "Операция вернула duplicate/exist, но объект не найден в state_out")
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
resp.Diagnostics.AddError("Ошибка клиента", createErr.Error())
|
||||
return
|
||||
}
|
||||
if targetValue != "" {
|
||||
@@ -222,6 +244,9 @@ func (r *MongodbUserResource) Update(ctx context.Context, req resource.UpdateReq
|
||||
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, "modify_user", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -292,6 +317,9 @@ func (r *MongodbUserResource) Delete(ctx context.Context, req resource.DeleteReq
|
||||
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, "delete_user", params, operationTimeout); err != nil {
|
||||
if resources_core.IsSubresourceMissingError(err) {
|
||||
if targetValue != "" {
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,10 +33,9 @@ type RabbitmqModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
IpSpaceNameMaster types.String `tfsdk:"ip_space_name_master"`
|
||||
IpSpaceNameSlave types.String `tfsdk:"ip_space_name_slave"`
|
||||
NeedExternalAddressMaster types.Bool `tfsdk:"need_external_address_master"`
|
||||
NeedExternalAddressSlave types.Bool `tfsdk:"need_external_address_slave"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
@@ -61,13 +63,12 @@ func (r *RabbitmqResource) Metadata(ctx context.Context, req resource.MetadataRe
|
||||
|
||||
func (r *RabbitmqResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"ip_space_name_master": schema.StringAttribute{Optional: true, MarkdownDescription: "Пример: `internet-no-antiddos-v1`"},
|
||||
"ip_space_name_slave": schema.StringAttribute{Optional: true, MarkdownDescription: "Пример: `internet-no-antiddos-v1`"},
|
||||
"need_external_address_master": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Флаг, определяющий, нужно ли выделять внешний IP для доступа к master. Пример: `true` или `false`"},
|
||||
"need_external_address_slave": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false), MarkdownDescription: "Выделяет внешний (белый) IP для доступа к slave"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1000), MarkdownDescription: "Пример: `2000`"},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(30), MarkdownDescription: "Пример: `20`"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Пример: `1`"},
|
||||
@@ -124,17 +125,23 @@ func (r *RabbitmqResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -152,8 +159,10 @@ func (r *RabbitmqResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
|
||||
225: resources_core.FormatBool(config.NeedExternalAddressMaster),
|
||||
543: resources_core.FormatString(config.IpSpaceNameMaster),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 93, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 93, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *RabbitmqResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -168,7 +177,9 @@ func (r *RabbitmqResource) Create(ctx context.Context, req resource.CreateReques
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 93, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 93, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
116: resources_core.FormatInt64(data.ResourceCPU),
|
||||
@@ -184,6 +195,9 @@ func (r *RabbitmqResource) Create(ctx context.Context, req resource.CreateReques
|
||||
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, 93, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -202,9 +216,7 @@ func (r *RabbitmqResource) Create(ctx context.Context, req resource.CreateReques
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "ipSpaceNameSlave", Field: "IpSpaceNameSlave", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "needExternalAddressSlave", Field: "NeedExternalAddressSlave", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
@@ -251,9 +263,7 @@ func (r *RabbitmqResource) Read(ctx context.Context, req resource.ReadRequest, r
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "ipSpaceNameSlave", Field: "IpSpaceNameSlave", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "needExternalAddressSlave", Field: "NeedExternalAddressSlave", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
@@ -332,29 +342,11 @@ func (r *RabbitmqResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.NeedExternalAddressSlave.IsNull() != state.NeedExternalAddressSlave.IsNull() || plan.NeedExternalAddressSlave.IsUnknown() != state.NeedExternalAddressSlave.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.NeedExternalAddressSlave.IsNull() && !plan.NeedExternalAddressSlave.IsUnknown() && !state.NeedExternalAddressSlave.IsNull() && !state.NeedExternalAddressSlave.IsUnknown() {
|
||||
if plan.NeedExternalAddressSlave.ValueBool() != state.NeedExternalAddressSlave.ValueBool() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IpSpaceNameMaster.IsNull() != state.IpSpaceNameMaster.IsNull() || plan.IpSpaceNameMaster.IsUnknown() != state.IpSpaceNameMaster.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceNameMaster.IsNull() && !plan.IpSpaceNameMaster.IsUnknown() && !state.IpSpaceNameMaster.IsNull() && !state.IpSpaceNameMaster.IsUnknown() {
|
||||
if plan.IpSpaceNameMaster.ValueString() != state.IpSpaceNameMaster.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IpSpaceNameSlave.IsNull() != state.IpSpaceNameSlave.IsNull() || plan.IpSpaceNameSlave.IsUnknown() != state.IpSpaceNameSlave.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IpSpaceNameSlave.IsNull() && !plan.IpSpaceNameSlave.IsUnknown() && !state.IpSpaceNameSlave.IsNull() && !state.IpSpaceNameSlave.IsUnknown() {
|
||||
if plan.IpSpaceNameSlave.ValueString() != state.IpSpaceNameSlave.ValueString() {
|
||||
if !strings.EqualFold(plan.IpSpaceNameMaster.ValueString(), state.IpSpaceNameMaster.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -380,15 +372,16 @@ func (r *RabbitmqResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
306: resources_core.FormatInt64(plan.ResourceDisk),
|
||||
307: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
308: resources_core.FormatBool(plan.NeedExternalAddressMaster),
|
||||
309: resources_core.FormatBool(plan.NeedExternalAddressSlave),
|
||||
545: resources_core.FormatString(plan.IpSpaceNameMaster),
|
||||
546: resources_core.FormatString(plan.IpSpaceNameSlave),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -406,9 +399,7 @@ func (r *RabbitmqResource) Update(ctx context.Context, req resource.UpdateReques
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "ipSpaceNameMaster", Field: "IpSpaceNameMaster", Type: "string"},
|
||||
{Code: "ipSpaceNameSlave", Field: "IpSpaceNameSlave", Type: "string"},
|
||||
{Code: "needExternalAddressMaster", Field: "NeedExternalAddressMaster", Type: "bool"},
|
||||
{Code: "needExternalAddressSlave", Field: "NeedExternalAddressSlave", Type: "bool"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
@@ -432,7 +423,6 @@ func (r *RabbitmqResource) Delete(ctx context.Context, req resource.DeleteReques
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -442,6 +432,9 @@ func (r *RabbitmqResource) Delete(ctx context.Context, req resource.DeleteReques
|
||||
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
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: lucee
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &LuceeReconcileResource{}
|
||||
|
||||
// LuceeReconcileResource triggers a one-time action.
|
||||
type LuceeReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type LuceeReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
LuceeID types.String `tfsdk:"lucee_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewLuceeReconcileResource() resource.Resource {
|
||||
return &LuceeReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *LuceeReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_lucee_reconcile"
|
||||
}
|
||||
|
||||
func (r *LuceeReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"lucee_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *LuceeReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan LuceeReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.LuceeID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *LuceeReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state LuceeReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *LuceeReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan LuceeReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.LuceeID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *LuceeReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *LuceeReconcileResource) 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
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,11 +25,13 @@ type LuceeRedeployResource struct {
|
||||
}
|
||||
|
||||
type LuceeRedeployModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
LuceeID types.String `tfsdk:"lucee_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
LuceeID types.String `tfsdk:"lucee_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
}
|
||||
|
||||
func NewLuceeRedeployResource() resource.Resource {
|
||||
@@ -41,11 +44,13 @@ func (r *LuceeRedeployResource) Metadata(ctx context.Context, req resource.Metad
|
||||
|
||||
func (r *LuceeRedeployResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"lucee_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания"},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"lucee_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
"startup_configuration": schema.StringAttribute{Required: true},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -56,8 +61,8 @@ func (r *LuceeRedeployResource) Create(ctx context.Context, req resource.CreateR
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.ResourceRealm.IsNull() || plan.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if plan.StartupConfiguration.IsNull() || plan.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,13 +74,16 @@ func (r *LuceeRedeployResource) Create(ctx context.Context, req resource.CreateR
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"resourceRealm": resources_core.FormatString(plan.ResourceRealm),
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
}
|
||||
|
||||
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, "redeploy", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -109,7 +117,7 @@ func (r *LuceeRedeployResource) Update(ctx context.Context, req resource.UpdateR
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"resourceRealm": resources_core.FormatString(plan.ResourceRealm),
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "redeploy", params); err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,8 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,15 +33,12 @@ type LuceeModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
AppVersion types.String `tfsdk:"app_version"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
GitPath types.String `tfsdk:"git_path"`
|
||||
HealthPath types.String `tfsdk:"health_path"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
AppConfiguration types.String `tfsdk:"app_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
JsonEnv types.String `tfsdk:"json_env"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
@@ -62,18 +61,15 @@ func (r *LuceeResource) Metadata(ctx context.Context, req resource.MetadataReque
|
||||
|
||||
func (r *LuceeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"app_version": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("5.4"), MarkdownDescription: "Версия Lucee"},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"git_path": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("https://github.com/xahys/testlucee"), MarkdownDescription: "Путь до Git, который будет webroot для сайта"},
|
||||
"health_path": schema.StringAttribute{Optional: true, MarkdownDescription: "Путь до healtcheck для запуска пода в Kubernetes, который отдаст 200 код"},
|
||||
"json_env": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("{ \"param\": \"value\" }"), MarkdownDescription: "Здесь вы можете задать переменные среды в формате KEY=VALUE. Они будут доступны внутри вашего приложения."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(300), MarkdownDescription: "Квота ядра пода (milicores)"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(512), MarkdownDescription: "Квота памяти пода (MBytes)"},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"access_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"app_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"json_env": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("{}"), MarkdownDescription: "Передаётся в формате КЛЮЧ:ЗНАЧЕНИЕ"},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -124,22 +120,52 @@ func (r *LuceeResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить access_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.AppConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "app_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AppConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -151,18 +177,16 @@ func (r *LuceeResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
params := map[int]string{
|
||||
103: resources_core.FormatString(config.Domain),
|
||||
104: resources_core.FormatString(config.GitPath),
|
||||
105: resources_core.FormatString(config.JsonEnv),
|
||||
106: resources_core.FormatInt64(config.ResourceCPU),
|
||||
107: resources_core.FormatInt64(config.ResourceMemory),
|
||||
108: resources_core.FormatString(config.ResourceRealm),
|
||||
132: resources_core.FormatString(config.HealthPath),
|
||||
139: resources_core.FormatInt64(config.ResourceInstances),
|
||||
263: resources_core.FormatString(config.AppVersion),
|
||||
1019: resources_core.FormatString(config.StartupConfiguration),
|
||||
1020: resources_core.FormatString(config.ClusterConfiguration),
|
||||
1021: resources_core.FormatString(config.AccessConfiguration),
|
||||
1022: resources_core.FormatString(config.AppConfiguration),
|
||||
1023: resources_core.FormatString(config.JsonEnv),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 94, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 94, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *LuceeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -171,30 +195,43 @@ func (r *LuceeResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.AppConfiguration.IsNull() || data.AppConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "app_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 94, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 94, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
103: resources_core.FormatString(data.Domain),
|
||||
104: resources_core.FormatString(data.GitPath),
|
||||
105: resources_core.FormatString(data.JsonEnv),
|
||||
106: resources_core.FormatInt64(data.ResourceCPU),
|
||||
107: resources_core.FormatInt64(data.ResourceMemory),
|
||||
108: resources_core.FormatString(data.ResourceRealm),
|
||||
132: resources_core.FormatString(data.HealthPath),
|
||||
139: resources_core.FormatInt64(data.ResourceInstances),
|
||||
263: resources_core.FormatString(data.AppVersion),
|
||||
1019: resources_core.FormatString(data.StartupConfiguration),
|
||||
1020: resources_core.FormatString(data.ClusterConfiguration),
|
||||
1021: resources_core.FormatString(data.AccessConfiguration),
|
||||
1022: resources_core.FormatString(data.AppConfiguration),
|
||||
1023: resources_core.FormatString(data.JsonEnv),
|
||||
}
|
||||
|
||||
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, 94, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -212,15 +249,11 @@ func (r *LuceeResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "gitPath", Field: "GitPath", Type: "string"},
|
||||
{Code: "healthPath", Field: "HealthPath", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -261,15 +294,11 @@ func (r *LuceeResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "gitPath", Field: "GitPath", Type: "string"},
|
||||
{Code: "healthPath", Field: "HealthPath", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -299,19 +328,19 @@ func (r *LuceeResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.GitPath.IsNull() != state.GitPath.IsNull() || plan.GitPath.IsUnknown() != state.GitPath.IsUnknown() {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.GitPath.IsNull() && !plan.GitPath.IsUnknown() && !state.GitPath.IsNull() && !state.GitPath.IsUnknown() {
|
||||
if plan.GitPath.ValueString() != state.GitPath.ValueString() {
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.HealthPath.IsNull() != state.HealthPath.IsNull() || plan.HealthPath.IsUnknown() != state.HealthPath.IsUnknown() {
|
||||
if plan.AppConfiguration.IsNull() != state.AppConfiguration.IsNull() || plan.AppConfiguration.IsUnknown() != state.AppConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.HealthPath.IsNull() && !plan.HealthPath.IsUnknown() && !state.HealthPath.IsNull() && !state.HealthPath.IsUnknown() {
|
||||
if plan.HealthPath.ValueString() != state.HealthPath.ValueString() {
|
||||
} else if !plan.AppConfiguration.IsNull() && !plan.AppConfiguration.IsUnknown() && !state.AppConfiguration.IsNull() && !state.AppConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AppConfiguration.ValueString(), state.AppConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -320,43 +349,7 @@ func (r *LuceeResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.JsonEnv.IsNull() != state.JsonEnv.IsNull() || plan.JsonEnv.IsUnknown() != state.JsonEnv.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.JsonEnv.IsNull() && !plan.JsonEnv.IsUnknown() && !state.JsonEnv.IsNull() && !state.JsonEnv.IsUnknown() {
|
||||
if plan.JsonEnv.ValueString() != state.JsonEnv.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPU.IsNull() != state.ResourceCPU.IsNull() || plan.ResourceCPU.IsUnknown() != state.ResourceCPU.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemory.IsNull() != state.ResourceMemory.IsNull() || plan.ResourceMemory.IsUnknown() != state.ResourceMemory.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstances.IsNull() != state.ResourceInstances.IsNull() || plan.ResourceInstances.IsUnknown() != state.ResourceInstances.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AppVersion.IsNull() != state.AppVersion.IsNull() || plan.AppVersion.IsUnknown() != state.AppVersion.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AppVersion.IsNull() && !plan.AppVersion.IsUnknown() && !state.AppVersion.IsNull() && !state.AppVersion.IsUnknown() {
|
||||
if plan.AppVersion.ValueString() != state.AppVersion.ValueString() {
|
||||
if !strings.EqualFold(plan.JsonEnv.ValueString(), state.JsonEnv.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -377,19 +370,18 @@ func (r *LuceeResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
}
|
||||
|
||||
params := map[int]string{
|
||||
133: resources_core.FormatString(plan.GitPath),
|
||||
134: resources_core.FormatString(plan.HealthPath),
|
||||
135: resources_core.FormatString(plan.JsonEnv),
|
||||
136: resources_core.FormatInt64(plan.ResourceCPU),
|
||||
137: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
138: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
264: resources_core.FormatString(plan.AppVersion),
|
||||
1024: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
1025: resources_core.FormatString(plan.AppConfiguration),
|
||||
1026: resources_core.FormatString(plan.JsonEnv),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -406,15 +398,11 @@ func (r *LuceeResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "gitPath", Field: "GitPath", Type: "string"},
|
||||
{Code: "healthPath", Field: "HealthPath", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -433,7 +421,6 @@ func (r *LuceeResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -443,6 +430,9 @@ func (r *LuceeResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,12 @@ type LuceeRestartResource struct {
|
||||
}
|
||||
|
||||
type LuceeRestartModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
LuceeID types.String `tfsdk:"lucee_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
LuceeID types.String `tfsdk:"lucee_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewLuceeRestartResource() resource.Resource {
|
||||
@@ -40,10 +43,12 @@ func (r *LuceeRestartResource) Metadata(ctx context.Context, req resource.Metada
|
||||
|
||||
func (r *LuceeRestartResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"lucee_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"lucee_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -68,6 +73,9 @@ func (r *LuceeRestartResource) Create(ctx context.Context, req resource.CreateRe
|
||||
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, "restart", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Code generated by tools/gen_v2. DO NOT EDIT.
|
||||
// Service: nodejs
|
||||
// Action: reconcile
|
||||
|
||||
var _ resource.Resource = &NodejsReconcileResource{}
|
||||
|
||||
// NodejsReconcileResource triggers a one-time action.
|
||||
type NodejsReconcileResource struct {
|
||||
client *core.UniversalClient
|
||||
}
|
||||
|
||||
type NodejsReconcileModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
NodejsID types.String `tfsdk:"nodejs_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
}
|
||||
|
||||
func NewNodejsReconcileResource() resource.Resource {
|
||||
return &NodejsReconcileResource{}
|
||||
}
|
||||
|
||||
func (r *NodejsReconcileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_nodejs_reconcile"
|
||||
}
|
||||
|
||||
func (r *NodejsReconcileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"nodejs_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
|
||||
func (r *NodejsReconcileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan NodejsReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.NodejsID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
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, "reconcile", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *NodejsReconcileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state NodejsReconcileModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *NodejsReconcileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan NodejsReconcileModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUID := strings.TrimSpace(plan.NodejsID.ValueString())
|
||||
runID := strings.TrimSpace(plan.RunID.ValueString())
|
||||
if instanceUID == "" || runID == "" {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", "отсутствует идентификатор экземпляра или run_id")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "reconcile", params); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(resources_core.BuildActionID(instanceUID, "reconcile", runID))
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *NodejsReconcileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
// No-op: removing the resource does not trigger a remote action.
|
||||
}
|
||||
|
||||
func (r *NodejsReconcileResource) 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
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,11 +25,13 @@ type NodejsRedeployResource struct {
|
||||
}
|
||||
|
||||
type NodejsRedeployModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
NodejsID types.String `tfsdk:"nodejs_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
NodejsID types.String `tfsdk:"nodejs_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
}
|
||||
|
||||
func NewNodejsRedeployResource() resource.Resource {
|
||||
@@ -41,11 +44,13 @@ func (r *NodejsRedeployResource) Metadata(ctx context.Context, req resource.Meta
|
||||
|
||||
func (r *NodejsRedeployResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"nodejs_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания"},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"nodejs_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
"startup_configuration": schema.StringAttribute{Required: true},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -56,8 +61,8 @@ func (r *NodejsRedeployResource) Create(ctx context.Context, req resource.Create
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if plan.ResourceRealm.IsNull() || plan.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if plan.StartupConfiguration.IsNull() || plan.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,13 +74,16 @@ func (r *NodejsRedeployResource) Create(ctx context.Context, req resource.Create
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"resourceRealm": resources_core.FormatString(plan.ResourceRealm),
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
}
|
||||
|
||||
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, "redeploy", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -109,7 +117,7 @@ func (r *NodejsRedeployResource) Update(ctx context.Context, req resource.Update
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"resourceRealm": resources_core.FormatString(plan.ResourceRealm),
|
||||
"startupConfiguration": resources_core.FormatString(plan.StartupConfiguration),
|
||||
}
|
||||
|
||||
if err := resources_core.RunOperationByCode(ctx, r.client, instanceUID, "redeploy", params); err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -10,8 +11,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,15 +33,12 @@ type NodejsModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
AppVersion types.String `tfsdk:"app_version"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
GitPath types.String `tfsdk:"git_path"`
|
||||
HealthPath types.String `tfsdk:"health_path"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
AccessConfiguration types.String `tfsdk:"access_configuration"`
|
||||
AppConfiguration types.String `tfsdk:"app_configuration"`
|
||||
ClusterConfiguration types.String `tfsdk:"cluster_configuration"`
|
||||
JsonEnv types.String `tfsdk:"json_env"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
StartupConfiguration types.String `tfsdk:"startup_configuration"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
@@ -62,18 +61,15 @@ func (r *NodejsResource) Metadata(ctx context.Context, req resource.MetadataRequ
|
||||
|
||||
func (r *NodejsResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"app_version": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("23"), MarkdownDescription: "Версия приложения"},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"git_path": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("https://github.com/xahys/testnode.git"), MarkdownDescription: "Путь до Git, который будет webroot для сайта"},
|
||||
"health_path": schema.StringAttribute{Optional: true, MarkdownDescription: "Путь до healtcheck для запуска пода в Kubernetes, который отдаст 200 код. Если оставить пустым, healtcheck'а не будет"},
|
||||
"json_env": schema.StringAttribute{Optional: true, MarkdownDescription: "Массив переменных, будут переданы в env приложения"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Квота ядра пода (milicores). 1000Mili = 1 Ядро"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1024), MarkdownDescription: "Квота памяти пода (MBytes)"},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"log_level": schema.StringAttribute{Optional: true, MarkdownDescription: "Operation stages log level: none (default), info, debug. Overrides provider-level log_level."},
|
||||
"access_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"app_configuration": schema.StringAttribute{Required: true},
|
||||
"cluster_configuration": schema.StringAttribute{Required: true},
|
||||
"json_env": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("{}"), MarkdownDescription: "Передаётся в формате КЛЮЧ:ЗНАЧЕНИЕ"},
|
||||
"startup_configuration": schema.StringAttribute{Required: true, MarkdownDescription: "Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
@@ -124,22 +120,52 @@ func (r *NodejsResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.AccessConfiguration.IsNull() && !plan.AccessConfiguration.IsUnknown() && !state.AccessConfiguration.IsNull() && !state.AccessConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.AccessConfiguration.ValueString(), state.AccessConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить access_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
if !plan.StartupConfiguration.IsNull() && !plan.StartupConfiguration.IsUnknown() && !state.StartupConfiguration.IsNull() && !state.StartupConfiguration.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.StartupConfiguration.ValueString(), state.StartupConfiguration.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить startup_configuration", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if config.AccessConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AccessConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.AppConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "app_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.AppConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.ClusterConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
if config.StartupConfiguration.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -151,18 +177,16 @@ func (r *NodejsResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan
|
||||
adoptExistingOnCreate = config.AdoptExistingOnCreate.ValueBool()
|
||||
}
|
||||
params := map[int]string{
|
||||
149: resources_core.FormatString(config.Domain),
|
||||
150: resources_core.FormatString(config.GitPath),
|
||||
151: resources_core.FormatString(config.HealthPath),
|
||||
152: resources_core.FormatString(config.JsonEnv),
|
||||
153: resources_core.FormatInt64(config.ResourceCPU),
|
||||
154: resources_core.FormatInt64(config.ResourceMemory),
|
||||
155: resources_core.FormatInt64(config.ResourceInstances),
|
||||
156: resources_core.FormatString(config.ResourceRealm),
|
||||
270: resources_core.FormatString(config.AppVersion),
|
||||
1028: resources_core.FormatString(config.StartupConfiguration),
|
||||
1029: resources_core.FormatString(config.ClusterConfiguration),
|
||||
1030: resources_core.FormatString(config.AccessConfiguration),
|
||||
1031: resources_core.FormatString(config.AppConfiguration),
|
||||
1032: resources_core.FormatString(config.JsonEnv),
|
||||
}
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 95, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 95, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *NodejsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -171,30 +195,43 @@ func (r *NodejsResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if data.ResourceRealm.IsNull() || data.ResourceRealm.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
if data.AccessConfiguration.IsNull() || data.AccessConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "access_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.AppConfiguration.IsNull() || data.AppConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "app_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.ClusterConfiguration.IsNull() || data.ClusterConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "cluster_configuration is required.")
|
||||
return
|
||||
}
|
||||
if data.StartupConfiguration.IsNull() || data.StartupConfiguration.IsUnknown() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "startup_configuration is required.")
|
||||
return
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 95, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 95, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
149: resources_core.FormatString(data.Domain),
|
||||
150: resources_core.FormatString(data.GitPath),
|
||||
151: resources_core.FormatString(data.HealthPath),
|
||||
152: resources_core.FormatString(data.JsonEnv),
|
||||
153: resources_core.FormatInt64(data.ResourceCPU),
|
||||
154: resources_core.FormatInt64(data.ResourceMemory),
|
||||
155: resources_core.FormatInt64(data.ResourceInstances),
|
||||
156: resources_core.FormatString(data.ResourceRealm),
|
||||
270: resources_core.FormatString(data.AppVersion),
|
||||
1028: resources_core.FormatString(data.StartupConfiguration),
|
||||
1029: resources_core.FormatString(data.ClusterConfiguration),
|
||||
1030: resources_core.FormatString(data.AccessConfiguration),
|
||||
1031: resources_core.FormatString(data.AppConfiguration),
|
||||
1032: resources_core.FormatString(data.JsonEnv),
|
||||
}
|
||||
|
||||
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, 95, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -212,15 +249,11 @@ func (r *NodejsResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "gitPath", Field: "GitPath", Type: "string"},
|
||||
{Code: "healthPath", Field: "HealthPath", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -261,15 +294,11 @@ func (r *NodejsResource) Read(ctx context.Context, req resource.ReadRequest, res
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "gitPath", Field: "GitPath", Type: "string"},
|
||||
{Code: "healthPath", Field: "HealthPath", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -299,19 +328,19 @@ func (r *NodejsResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
|
||||
hasServiceParamChanges := false
|
||||
if !hasServiceParamChanges {
|
||||
if plan.GitPath.IsNull() != state.GitPath.IsNull() || plan.GitPath.IsUnknown() != state.GitPath.IsUnknown() {
|
||||
if plan.ClusterConfiguration.IsNull() != state.ClusterConfiguration.IsNull() || plan.ClusterConfiguration.IsUnknown() != state.ClusterConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.GitPath.IsNull() && !plan.GitPath.IsUnknown() && !state.GitPath.IsNull() && !state.GitPath.IsUnknown() {
|
||||
if plan.GitPath.ValueString() != state.GitPath.ValueString() {
|
||||
} else if !plan.ClusterConfiguration.IsNull() && !plan.ClusterConfiguration.IsUnknown() && !state.ClusterConfiguration.IsNull() && !state.ClusterConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.ClusterConfiguration.ValueString(), state.ClusterConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.HealthPath.IsNull() != state.HealthPath.IsNull() || plan.HealthPath.IsUnknown() != state.HealthPath.IsUnknown() {
|
||||
if plan.AppConfiguration.IsNull() != state.AppConfiguration.IsNull() || plan.AppConfiguration.IsUnknown() != state.AppConfiguration.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.HealthPath.IsNull() && !plan.HealthPath.IsUnknown() && !state.HealthPath.IsNull() && !state.HealthPath.IsUnknown() {
|
||||
if plan.HealthPath.ValueString() != state.HealthPath.ValueString() {
|
||||
} else if !plan.AppConfiguration.IsNull() && !plan.AppConfiguration.IsUnknown() && !state.AppConfiguration.IsNull() && !state.AppConfiguration.IsUnknown() {
|
||||
if !strings.EqualFold(plan.AppConfiguration.ValueString(), state.AppConfiguration.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -320,43 +349,7 @@ func (r *NodejsResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
if plan.JsonEnv.IsNull() != state.JsonEnv.IsNull() || plan.JsonEnv.IsUnknown() != state.JsonEnv.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.JsonEnv.IsNull() && !plan.JsonEnv.IsUnknown() && !state.JsonEnv.IsNull() && !state.JsonEnv.IsUnknown() {
|
||||
if plan.JsonEnv.ValueString() != state.JsonEnv.ValueString() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceCPU.IsNull() != state.ResourceCPU.IsNull() || plan.ResourceCPU.IsUnknown() != state.ResourceCPU.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceCPU.IsNull() && !plan.ResourceCPU.IsUnknown() && !state.ResourceCPU.IsNull() && !state.ResourceCPU.IsUnknown() {
|
||||
if plan.ResourceCPU.ValueInt64() != state.ResourceCPU.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceMemory.IsNull() != state.ResourceMemory.IsNull() || plan.ResourceMemory.IsUnknown() != state.ResourceMemory.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceMemory.IsNull() && !plan.ResourceMemory.IsUnknown() && !state.ResourceMemory.IsNull() && !state.ResourceMemory.IsUnknown() {
|
||||
if plan.ResourceMemory.ValueInt64() != state.ResourceMemory.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.ResourceInstances.IsNull() != state.ResourceInstances.IsNull() || plan.ResourceInstances.IsUnknown() != state.ResourceInstances.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.ResourceInstances.IsNull() && !plan.ResourceInstances.IsUnknown() && !state.ResourceInstances.IsNull() && !state.ResourceInstances.IsUnknown() {
|
||||
if plan.ResourceInstances.ValueInt64() != state.ResourceInstances.ValueInt64() {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.AppVersion.IsNull() != state.AppVersion.IsNull() || plan.AppVersion.IsUnknown() != state.AppVersion.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.AppVersion.IsNull() && !plan.AppVersion.IsUnknown() && !state.AppVersion.IsNull() && !state.AppVersion.IsUnknown() {
|
||||
if plan.AppVersion.ValueString() != state.AppVersion.ValueString() {
|
||||
if !strings.EqualFold(plan.JsonEnv.ValueString(), state.JsonEnv.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
@@ -377,19 +370,18 @@ func (r *NodejsResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
}
|
||||
|
||||
params := map[int]string{
|
||||
157: resources_core.FormatString(plan.GitPath),
|
||||
158: resources_core.FormatString(plan.HealthPath),
|
||||
159: resources_core.FormatString(plan.JsonEnv),
|
||||
160: resources_core.FormatInt64(plan.ResourceCPU),
|
||||
161: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
162: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
271: resources_core.FormatString(plan.AppVersion),
|
||||
1033: resources_core.FormatString(plan.ClusterConfiguration),
|
||||
1034: resources_core.FormatString(plan.AppConfiguration),
|
||||
1035: resources_core.FormatString(plan.JsonEnv),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -406,15 +398,11 @@ func (r *NodejsResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
{Code: "vault_user_path"},
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "appVersion", Field: "AppVersion", Type: "string"},
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "gitPath", Field: "GitPath", Type: "string"},
|
||||
{Code: "healthPath", Field: "HealthPath", Type: "string"},
|
||||
{Code: "accessConfiguration", Field: "AccessConfiguration", Type: "string"},
|
||||
{Code: "appConfiguration", Field: "AppConfiguration", Type: "string"},
|
||||
{Code: "clusterConfiguration", Field: "ClusterConfiguration", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceInstances", Field: "ResourceInstances", Type: "int64"},
|
||||
{Code: "resourceMemory", Field: "ResourceMemory", Type: "int64"},
|
||||
{Code: "resourceRealm", Field: "ResourceRealm", Type: "string"},
|
||||
{Code: "startupConfiguration", Field: "StartupConfiguration", Type: "string"},
|
||||
})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
@@ -433,7 +421,6 @@ func (r *NodejsResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -443,6 +430,9 @@ func (r *NodejsResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,11 +25,13 @@ type PgadminRedeployResource struct {
|
||||
}
|
||||
|
||||
type PgadminRedeployModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
PgadminID types.String `tfsdk:"pgadmin_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
PgadminID types.String `tfsdk:"pgadmin_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
}
|
||||
|
||||
func NewPgadminRedeployResource() resource.Resource {
|
||||
@@ -41,11 +44,13 @@ func (r *PgadminRedeployResource) Metadata(ctx context.Context, req resource.Met
|
||||
|
||||
func (r *PgadminRedeployResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"pgadmin_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания"},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"pgadmin_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания"},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -76,6 +81,9 @@ func (r *PgadminRedeployResource) Create(ctx context.Context, req resource.Creat
|
||||
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, "redeploy", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,6 +34,7 @@ type PgadminModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
Login types.String `tfsdk:"login"`
|
||||
Password types.String `tfsdk:"password"`
|
||||
@@ -60,15 +64,16 @@ func (r *PgadminResource) Metadata(ctx context.Context, req resource.MetadataReq
|
||||
|
||||
func (r *PgadminResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"login": schema.StringAttribute{Required: true, MarkdownDescription: "Логин для подключения к pgAdmin, в формате email (login@email.net) Не изменяется после создания."},
|
||||
"password": schema.StringAttribute{Required: true, MarkdownDescription: "Пароль для подключения к pgAdmin Не изменяется после создания.", Sensitive: true},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(200), MarkdownDescription: "Квота ядра пода (milicores). 1000Mili = 1 Ядро"},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Квота ядра пода (milicores). 1000Mili = 1 Ядро"},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Квота диска (Указывается в GIGAbytes)"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(256), MarkdownDescription: "Квота памяти пода (MBytes)"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(512), MarkdownDescription: "Квота памяти пода (MBytes)"},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания Не изменяется после создания."},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
@@ -121,43 +126,64 @@ func (r *PgadminResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Domain.ValueString(), state.Domain.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Login.IsNull() && !plan.Login.IsUnknown() && !state.Login.IsNull() && !state.Login.IsUnknown() {
|
||||
if plan.Login.ValueString() != state.Login.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Login.ValueString(), state.Login.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить login", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.Password.IsNull() && !plan.Password.IsUnknown() && !state.Password.IsNull() && !state.Password.IsUnknown() {
|
||||
if plan.Password.ValueString() != state.Password.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Password.ValueString(), state.Password.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить password", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.Login.IsNull() || config.Login.IsUnknown() {
|
||||
if config.Login.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "login is required.")
|
||||
return
|
||||
}
|
||||
if config.Password.IsNull() || config.Password.IsUnknown() {
|
||||
if config.Login.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.Password.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "password is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.Password.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -175,8 +201,13 @@ func (r *PgadminResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
170: resources_core.FormatString(config.Login),
|
||||
171: resources_core.FormatString(config.Password),
|
||||
}
|
||||
desiredDomain := ""
|
||||
if !config.Domain.IsNull() && !config.Domain.IsUnknown() {
|
||||
desiredDomain = config.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 96, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 96, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *PgadminResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -199,7 +230,12 @@ func (r *PgadminResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 96, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||||
desiredDomain = data.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 96, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
164: resources_core.FormatString(data.Domain),
|
||||
@@ -215,6 +251,9 @@ func (r *PgadminResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
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, 96, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -366,6 +405,9 @@ func (r *PgadminResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -407,7 +449,6 @@ func (r *PgadminResource) Delete(ctx context.Context, req resource.DeleteRequest
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -417,6 +458,9 @@ func (r *PgadminResource) Delete(ctx context.Context, req resource.DeleteRequest
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -30,6 +33,7 @@ type NoderedModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
@@ -58,9 +62,10 @@ func (r *NoderedResource) Metadata(ctx context.Context, req resource.MetadataReq
|
||||
|
||||
func (r *NoderedResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"domain": schema.StringAttribute{Required: true, MarkdownDescription: "Наименование поддомена, будет добавлено в k8stemplate.dev.nubes.ru Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Квота ядра пода (Указывается в mili). 1000Mili = 1 Ядро Не изменяется после создания."},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Квота диска (Указывается в GIGAbytes) Не изменяется после создания."},
|
||||
@@ -118,7 +123,10 @@ func (r *NoderedResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Domain.ValueString(), state.Domain.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
@@ -148,21 +156,30 @@ func (r *NoderedResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.Domain.IsNull() || config.Domain.IsUnknown() {
|
||||
if config.Domain.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "domain is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.Domain.IsUnknown() {
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -179,8 +196,13 @@ func (r *NoderedResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
|
||||
197: resources_core.FormatString(config.Domain),
|
||||
323: resources_core.FormatInt64(config.ResourceInstances),
|
||||
}
|
||||
desiredDomain := ""
|
||||
if !config.Domain.IsNull() && !config.Domain.IsUnknown() {
|
||||
desiredDomain = config.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 97, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 97, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *NoderedResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -199,7 +221,12 @@ func (r *NoderedResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 97, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||||
desiredDomain = data.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 97, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
193: resources_core.FormatInt64(data.ResourceCPU),
|
||||
@@ -214,6 +241,9 @@ func (r *NoderedResource) Create(ctx context.Context, req resource.CreateRequest
|
||||
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, 97, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -332,6 +362,9 @@ func (r *NoderedResource) Update(ctx context.Context, req resource.UpdateRequest
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -372,16 +405,15 @@ func (r *NoderedResource) Delete(ctx context.Context, req resource.DeleteRequest
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
}
|
||||
deleteMode := "delete"
|
||||
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -24,11 +25,13 @@ type HttpRedeployResource struct {
|
||||
}
|
||||
|
||||
type HttpRedeployModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
HttpID types.String `tfsdk:"http_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
HttpID types.String `tfsdk:"http_id"`
|
||||
RunID types.String `tfsdk:"run_id"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
}
|
||||
|
||||
func NewHttpRedeployResource() resource.Resource {
|
||||
@@ -41,11 +44,13 @@ func (r *HttpRedeployResource) Metadata(ctx context.Context, req resource.Metada
|
||||
|
||||
func (r *HttpRedeployResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"http_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания"},
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"http_id": schema.StringAttribute{Required: true},
|
||||
"run_id": schema.StringAttribute{Required: true},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"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."},
|
||||
"resource_realm": schema.StringAttribute{Required: true, MarkdownDescription: "Платформа для развертывания"},
|
||||
}
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
}
|
||||
@@ -76,6 +81,9 @@ func (r *HttpRedeployResource) Create(ctx context.Context, req resource.CreateRe
|
||||
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, "redeploy", params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -31,7 +34,9 @@ type HttpModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
IngressAnnotations types.String `tfsdk:"ingress_annotations"`
|
||||
JsonEnv types.String `tfsdk:"json_env"`
|
||||
RegistryPath types.String `tfsdk:"registry_path"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
@@ -60,11 +65,13 @@ func (r *HttpResource) Metadata(ctx context.Context, req resource.MetadataReques
|
||||
|
||||
func (r *HttpResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"json_env": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("{"param": "value"}"), MarkdownDescription: "Массив переменных, будут переданы в env приложения Не изменяется после создания."},
|
||||
"ingress_annotations": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("{}"), MarkdownDescription: "Формат ключ/значение<br/>Поддерживает пока простые аннотации Не изменяется после создания."},
|
||||
"json_env": schema.StringAttribute{Optional: true, MarkdownDescription: "Массив переменных, будут переданы в env приложения Не изменяется после создания."},
|
||||
"registry_path": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("nexus-sa.tst.nubes.ru/docker-nubes/jolt"), MarkdownDescription: "Путь до image, который будет установлен как deployment Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(100), MarkdownDescription: "Квота ядра пода (Указывается в mili)"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
@@ -121,35 +128,59 @@ func (r *HttpResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Domain.ValueString(), state.Domain.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.IngressAnnotations.IsNull() && !plan.IngressAnnotations.IsUnknown() && !state.IngressAnnotations.IsNull() && !state.IngressAnnotations.IsUnknown() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.IngressAnnotations.ValueString(), state.IngressAnnotations.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить ingress_annotations", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.JsonEnv.IsNull() && !plan.JsonEnv.IsUnknown() && !state.JsonEnv.IsNull() && !state.JsonEnv.IsUnknown() {
|
||||
if plan.JsonEnv.ValueString() != state.JsonEnv.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.JsonEnv.ValueString(), state.JsonEnv.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить json_env", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.RegistryPath.IsNull() && !plan.RegistryPath.IsUnknown() && !state.RegistryPath.IsNull() && !state.RegistryPath.IsUnknown() {
|
||||
if plan.RegistryPath.ValueString() != state.RegistryPath.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.RegistryPath.ValueString(), state.RegistryPath.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить registry_path", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.ResourceRealm.IsNull() && !plan.ResourceRealm.IsUnknown() && !state.ResourceRealm.IsNull() && !state.ResourceRealm.IsUnknown() {
|
||||
if plan.ResourceRealm.ValueString() != state.ResourceRealm.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.ResourceRealm.ValueString(), state.ResourceRealm.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить resource_realm", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsNull() || config.ResourceRealm.IsUnknown() {
|
||||
if config.ResourceRealm.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "resource_realm is required.")
|
||||
return
|
||||
}
|
||||
if config.ResourceRealm.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -166,9 +197,15 @@ func (r *HttpResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
|
||||
216: resources_core.FormatString(config.ResourceRealm),
|
||||
217: resources_core.FormatString(config.RegistryPath),
|
||||
241: resources_core.FormatString(config.JsonEnv),
|
||||
782: resources_core.FormatString(config.IngressAnnotations),
|
||||
}
|
||||
desiredDomain := ""
|
||||
if !config.Domain.IsNull() && !config.Domain.IsUnknown() {
|
||||
desiredDomain = config.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 98, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 98, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *HttpResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -183,7 +220,12 @@ func (r *HttpResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 98, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||||
desiredDomain = data.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 98, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
212: resources_core.FormatString(data.Domain),
|
||||
@@ -193,12 +235,16 @@ func (r *HttpResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
216: resources_core.FormatString(data.ResourceRealm),
|
||||
217: resources_core.FormatString(data.RegistryPath),
|
||||
241: resources_core.FormatString(data.JsonEnv),
|
||||
782: resources_core.FormatString(data.IngressAnnotations),
|
||||
}
|
||||
|
||||
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, 98, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -217,6 +263,7 @@ func (r *HttpResource) Create(ctx context.Context, req resource.CreateRequest, r
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "ingressAnnotations", Field: "IngressAnnotations", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "registryPath", Field: "RegistryPath", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
@@ -264,6 +311,7 @@ func (r *HttpResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "ingressAnnotations", Field: "IngressAnnotations", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "registryPath", Field: "RegistryPath", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
@@ -350,6 +398,9 @@ func (r *HttpResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -367,6 +418,7 @@ func (r *HttpResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "ingressAnnotations", Field: "IngressAnnotations", Type: "string"},
|
||||
{Code: "jsonEnv", Field: "JsonEnv", Type: "string"},
|
||||
{Code: "registryPath", Field: "RegistryPath", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
@@ -391,7 +443,6 @@ func (r *HttpResource) Delete(ctx context.Context, req resource.DeleteRequest, r
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -401,6 +452,9 @@ func (r *HttpResource) Delete(ctx context.Context, req resource.DeleteRequest, r
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package resources_gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"terraform-provider-nubes/internal/core"
|
||||
"terraform-provider-nubes/internal/resources_core"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
@@ -28,25 +31,28 @@ type GiteaResource struct {
|
||||
}
|
||||
|
||||
type GiteaModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
PsqlUid types.String `tfsdk:"psql_uid"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
ResourceName types.String `tfsdk:"resource_name"`
|
||||
OperationTimeout types.String `tfsdk:"operation_timeout"`
|
||||
LogLevel types.String `tfsdk:"log_level"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
IngressExtraAnnotations types.String `tfsdk:"ingress_extra_annotations"`
|
||||
IniServerParams types.String `tfsdk:"ini_server_params"`
|
||||
PsqlUid types.String `tfsdk:"psql_uid"`
|
||||
ResourceCPU types.Int64 `tfsdk:"resource_c_p_u"`
|
||||
ResourceDisk types.Int64 `tfsdk:"resource_disk"`
|
||||
ResourceInstances types.Int64 `tfsdk:"resource_instances"`
|
||||
ResourceMemory types.Int64 `tfsdk:"resource_memory"`
|
||||
SuspendOnDestroy types.Bool `tfsdk:"suspend_on_destroy"`
|
||||
AdoptExistingOnCreate types.Bool `tfsdk:"adopt_existing_on_create"`
|
||||
StateParams types.Map `tfsdk:"state_params"`
|
||||
StateOut types.Map `tfsdk:"state_out"`
|
||||
StateParamsFlat types.Map `tfsdk:"state_params_flat"`
|
||||
StateOutFlat types.Map `tfsdk:"state_out_flat"`
|
||||
VaultSecrets types.Map `tfsdk:"vault_secrets"`
|
||||
VaultUrl types.String `tfsdk:"vault_url"`
|
||||
VaultUserPath types.String `tfsdk:"vault_user_path"`
|
||||
VaultFields types.List `tfsdk:"vault_fields"`
|
||||
}
|
||||
|
||||
func NewGiteaResource() resource.Resource {
|
||||
@@ -59,25 +65,28 @@ func (r *GiteaResource) Metadata(ctx context.Context, req resource.MetadataReque
|
||||
|
||||
func (r *GiteaResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
attrs := map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true},
|
||||
"resource_name": schema.StringAttribute{Required: true},
|
||||
"operation_timeout": schema.StringAttribute{Optional: true},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"psql_uid": schema.StringAttribute{Required: true, MarkdownDescription: "UUID Услуги k8sPgsql. Пользователя techGitea, базу данных gitea создаёт автоматически Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Квота ядра пода (Указывается в mili). 1000Mili = 1 Ядро"},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Квота диска (Указывается в GIGAbytes)"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(512), MarkdownDescription: "Квота памяти пода (Указывается в Megabytes)"},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
"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."},
|
||||
"domain": schema.StringAttribute{Optional: true, Computed: true, Default: stringdefault.StaticString("web01"), MarkdownDescription: "Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm Не изменяется после создания."},
|
||||
"ingress_extra_annotations": schema.StringAttribute{Optional: true},
|
||||
"ini_server_params": schema.StringAttribute{Optional: true},
|
||||
"psql_uid": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "UUID Услуги k8sPgsql. Пользователя techGitea, базу данных gitea создаёт автоматически Не изменяется после создания."},
|
||||
"resource_c_p_u": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(500), MarkdownDescription: "Квота ядра пода (Указывается в mili). 1000Mili = 1 Ядро"},
|
||||
"resource_disk": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Квота диска (Указывается в GIGAbytes)"},
|
||||
"resource_instances": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(1), MarkdownDescription: "Количество узлов"},
|
||||
"resource_memory": schema.Int64Attribute{Optional: true, Computed: true, Default: int64default.StaticInt64(512), MarkdownDescription: "Квота памяти пода (Указывается в Megabytes)"},
|
||||
"suspend_on_destroy": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true)},
|
||||
"adopt_existing_on_create": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(false)},
|
||||
"state_params": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_params_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"state_out_flat": schema.MapAttribute{Computed: true, ElementType: types.StringType},
|
||||
"vault_secrets": schema.MapAttribute{Computed: true, ElementType: types.StringType, Sensitive: true},
|
||||
"vault_url": schema.StringAttribute{Computed: true},
|
||||
"vault_user_path": schema.StringAttribute{Computed: true},
|
||||
"vault_fields": schema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
}
|
||||
|
||||
resp.Schema = schema.Schema{Attributes: attrs}
|
||||
@@ -112,22 +121,11 @@ func (r *GiteaResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
planChanged := false
|
||||
if !plan.PsqlUid.IsNull() && !plan.PsqlUid.IsUnknown() {
|
||||
resolvedPsqlUid, err := r.client.ResolveRefSvcParamValue(ctx, 90, plan.PsqlUid.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddWarning("Failed to resolve psql_uid", err.Error())
|
||||
} else if resolvedPsqlUid != "" && resolvedPsqlUid != plan.PsqlUid.ValueString() {
|
||||
plan.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
planChanged = true
|
||||
}
|
||||
}
|
||||
if planChanged {
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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".
|
||||
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 задается при создании и не может быть изменен. Создайте новый ресурс с другим именем.")
|
||||
@@ -135,23 +133,32 @@ func (r *GiteaResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
}
|
||||
}
|
||||
if !plan.Domain.IsNull() && !plan.Domain.IsUnknown() && !state.Domain.IsNull() && !state.Domain.IsUnknown() {
|
||||
if plan.Domain.ValueString() != state.Domain.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.Domain.ValueString(), state.Domain.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить domain", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !plan.PsqlUid.IsNull() && !plan.PsqlUid.IsUnknown() && !state.PsqlUid.IsNull() && !state.PsqlUid.IsUnknown() {
|
||||
if plan.PsqlUid.ValueString() != state.PsqlUid.ValueString() {
|
||||
// FIX(uuid-case): сравниваем без учёта регистра — API возвращает UUID
|
||||
// в lowercase, пользователь мог написать upper/mixed. Это одно и то же
|
||||
// значение, менять его нельзя только если оно реально другое.
|
||||
if !strings.EqualFold(plan.PsqlUid.ValueString(), state.PsqlUid.ValueString()) {
|
||||
resp.Diagnostics.AddError("Нельзя изменить psql_uid", "Параметр задается при создании и не может быть изменен.")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.PsqlUid.IsNull() || config.PsqlUid.IsUnknown() {
|
||||
if config.PsqlUid.IsNull() {
|
||||
resp.Diagnostics.AddError("Missing required attribute", "psql_uid is required.")
|
||||
return
|
||||
}
|
||||
if config.PsqlUid.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
|
||||
return
|
||||
@@ -175,9 +182,16 @@ func (r *GiteaResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanR
|
||||
235: resources_core.FormatInt64(config.ResourceInstances),
|
||||
237: resources_core.FormatString(config.Domain),
|
||||
238: resources_core.FormatString(config.PsqlUid),
|
||||
818: resources_core.FormatString(config.IngressExtraAnnotations),
|
||||
821: resources_core.FormatString(config.IniServerParams),
|
||||
}
|
||||
desiredDomain := ""
|
||||
if !config.Domain.IsNull() && !config.Domain.IsUnknown() {
|
||||
desiredDomain = config.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParams(ctx, r.client, 99, config.ResourceName.ValueString(), adoptExistingOnCreate, params)...)
|
||||
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, 99, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs)...)
|
||||
}
|
||||
|
||||
func (r *GiteaResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
@@ -190,6 +204,24 @@ func (r *GiteaResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
resp.Diagnostics.AddError("Missing required attribute", "psql_uid is required.")
|
||||
return
|
||||
}
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// ПРАВИЛО TERRAFORM (официальная документация):
|
||||
// «If an attribute value is configured, it is NEVER valid to change that
|
||||
// value in the plan.» — то есть plan ОБЯЗАН равняться config (= тому что
|
||||
// написал пользователь). Менять plan запрещено на уровне фреймворка.
|
||||
//
|
||||
// ПРОБЛЕМА: API нашего облака возвращает UUID в нижнем регистре.
|
||||
// Пользователь пишет: vapp_uid = "6214BA32-..." (верхний или смешанный).
|
||||
// После apply API вернул: "6214ba32-..." → state != plan → Terraform кричит:
|
||||
// «Provider produced inconsistent result after apply».
|
||||
//
|
||||
// РЕШЕНИЕ: корректировать STATE под PLAN, а не наоборот.
|
||||
// Шаг 1 (здесь): сохраняем оригинальное значение из plan ДО того как
|
||||
// ResolveRefSvcParamValue переведёт UUID в нижний регистр (нужен для API).
|
||||
// Шаг 2 (ниже, после RefreshResourceState): восстанавливаем оригинальный
|
||||
// регистр в state через strings.EqualFold (сравниваем без учёта регистра).
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
originalPsqlUid := data.PsqlUid
|
||||
if !data.PsqlUid.IsNull() && !data.PsqlUid.IsUnknown() {
|
||||
resolvedPsqlUid, err := r.client.ResolveRefSvcParamValue(ctx, 90, data.PsqlUid.ValueString())
|
||||
if err != nil {
|
||||
@@ -202,7 +234,12 @@ func (r *GiteaResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
}
|
||||
|
||||
resourceName := data.ResourceName.ValueString()
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnostics(ctx, r.client, 99, resourceName, data.AdoptExistingOnCreate.ValueBool())...)
|
||||
desiredDomain := ""
|
||||
if !data.Domain.IsNull() && !data.Domain.IsUnknown() {
|
||||
desiredDomain = data.Domain.ValueString()
|
||||
}
|
||||
domainServiceIDs := []int{81, 82, 88, 96, 97, 98, 99, 119, 149}
|
||||
resp.Diagnostics.Append(resources_core.CreateExistingResourceDiagnosticsWithDomainAndServices(ctx, r.client, 99, resourceName, data.AdoptExistingOnCreate.ValueBool(), desiredDomain, domainServiceIDs)...)
|
||||
|
||||
params := map[int]string{
|
||||
232: resources_core.FormatInt64(data.ResourceCPU),
|
||||
@@ -211,12 +248,17 @@ func (r *GiteaResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
235: resources_core.FormatInt64(data.ResourceInstances),
|
||||
237: resources_core.FormatString(data.Domain),
|
||||
238: resources_core.FormatString(data.PsqlUid),
|
||||
818: resources_core.FormatString(data.IngressExtraAnnotations),
|
||||
821: resources_core.FormatString(data.IniServerParams),
|
||||
}
|
||||
|
||||
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, 99, resourceName, data.AdoptExistingOnCreate.ValueBool(), params, operationTimeout)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
@@ -235,6 +277,8 @@ func (r *GiteaResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "ingressExtraAnnotations", Field: "IngressExtraAnnotations", Type: "string"},
|
||||
{Code: "iniServerParams", Field: "IniServerParams", Type: "string"},
|
||||
{Code: "psqlUid", Field: "PsqlUid", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
@@ -253,6 +297,16 @@ func (r *GiteaResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||
state.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Create).
|
||||
// EqualFold = «совпадают ли значения без учёта регистра?»
|
||||
// Если да — значит API вернул «туже» строку, только в другом регистре.
|
||||
// Заменяем state на original (то что было в plan/config пользователя).
|
||||
// Итог: plan=="6214BA32-..." и state=="6214BA32-..." → нет diff → нет taint.
|
||||
if !originalPsqlUid.IsNull() && !originalPsqlUid.IsUnknown() && !state.PsqlUid.IsNull() && !state.PsqlUid.IsUnknown() {
|
||||
if strings.EqualFold(state.PsqlUid.ValueString(), originalPsqlUid.ValueString()) {
|
||||
state.PsqlUid = originalPsqlUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -289,6 +343,8 @@ func (r *GiteaResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "ingressExtraAnnotations", Field: "IngressExtraAnnotations", Type: "string"},
|
||||
{Code: "iniServerParams", Field: "IniServerParams", Type: "string"},
|
||||
{Code: "psqlUid", Field: "PsqlUid", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
@@ -307,6 +363,16 @@ func (r *GiteaResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||
newState.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Read).
|
||||
// При чтении у нас нет plan — но предыдущий state уже хранит значение
|
||||
// в регистре пользователя (после первого Create оно было восстановлено).
|
||||
// Берём prior state (переменная state) как эталон регистра.
|
||||
// Если API вернул то же UUID только строчными буквами — восстанавливаем.
|
||||
if !state.PsqlUid.IsNull() && !state.PsqlUid.IsUnknown() && !newState.PsqlUid.IsNull() && !newState.PsqlUid.IsUnknown() {
|
||||
if strings.EqualFold(newState.PsqlUid.ValueString(), state.PsqlUid.ValueString()) {
|
||||
newState.PsqlUid = state.PsqlUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
|
||||
}
|
||||
@@ -366,6 +432,24 @@ func (r *GiteaResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IngressExtraAnnotations.IsNull() != state.IngressExtraAnnotations.IsNull() || plan.IngressExtraAnnotations.IsUnknown() != state.IngressExtraAnnotations.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IngressExtraAnnotations.IsNull() && !plan.IngressExtraAnnotations.IsUnknown() && !state.IngressExtraAnnotations.IsNull() && !state.IngressExtraAnnotations.IsUnknown() {
|
||||
if !strings.EqualFold(plan.IngressExtraAnnotations.ValueString(), state.IngressExtraAnnotations.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasServiceParamChanges {
|
||||
if plan.IniServerParams.IsNull() != state.IniServerParams.IsNull() || plan.IniServerParams.IsUnknown() != state.IniServerParams.IsUnknown() {
|
||||
hasServiceParamChanges = true
|
||||
} else if !plan.IniServerParams.IsNull() && !plan.IniServerParams.IsUnknown() && !state.IniServerParams.IsNull() && !state.IniServerParams.IsUnknown() {
|
||||
if !strings.EqualFold(plan.IniServerParams.ValueString(), state.IniServerParams.ValueString()) {
|
||||
hasServiceParamChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasServiceParamChanges {
|
||||
plan.ID = instanceID
|
||||
@@ -386,12 +470,17 @@ func (r *GiteaResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
260: resources_core.FormatInt64(plan.ResourceMemory),
|
||||
261: resources_core.FormatInt64(plan.ResourceDisk),
|
||||
262: resources_core.FormatInt64(plan.ResourceInstances),
|
||||
819: resources_core.FormatString(plan.IngressExtraAnnotations),
|
||||
820: resources_core.FormatString(plan.IniServerParams),
|
||||
}
|
||||
|
||||
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.UpdateResourceWithTimeout(ctx, r.client, instanceID.ValueString(), params, operationTimeout); err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка клиента", err.Error())
|
||||
return
|
||||
@@ -409,6 +498,8 @@ func (r *GiteaResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
{Code: "vault_fields"},
|
||||
}, []resources_core.InputField{
|
||||
{Code: "domain", Field: "Domain", Type: "string"},
|
||||
{Code: "ingressExtraAnnotations", Field: "IngressExtraAnnotations", Type: "string"},
|
||||
{Code: "iniServerParams", Field: "IniServerParams", Type: "string"},
|
||||
{Code: "psqlUid", Field: "PsqlUid", Type: "string"},
|
||||
{Code: "resourceCPU", Field: "ResourceCPU", Type: "int64"},
|
||||
{Code: "resourceDisk", Field: "ResourceDisk", Type: "int64"},
|
||||
@@ -427,6 +518,15 @@ func (r *GiteaResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||
state.PsqlUid = types.StringValue(resolvedPsqlUid)
|
||||
}
|
||||
}
|
||||
// Restore user-provided casing в state (Update).
|
||||
// После API-вызова modify state содержит значения в lower-case от API.
|
||||
// Plan == config == то что написал пользователь (регистр неизменён).
|
||||
// EqualFold: если UUID совпадает без учёта регистра — берём из plan.
|
||||
if !plan.PsqlUid.IsNull() && !plan.PsqlUid.IsUnknown() && !state.PsqlUid.IsNull() && !state.PsqlUid.IsUnknown() {
|
||||
if strings.EqualFold(state.PsqlUid.ValueString(), plan.PsqlUid.ValueString()) {
|
||||
state.PsqlUid = plan.PsqlUid
|
||||
}
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
@@ -440,7 +540,6 @@ func (r *GiteaResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
deleteMode := "state_only"
|
||||
if !state.SuspendOnDestroy.IsNull() && !state.SuspendOnDestroy.IsUnknown() && state.SuspendOnDestroy.ValueBool() {
|
||||
deleteMode = "suspend"
|
||||
@@ -450,6 +549,9 @@ func (r *GiteaResource) Delete(ctx context.Context, req resource.DeleteRequest,
|
||||
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
|
||||
|
||||
@@ -47,30 +47,50 @@ func AllResources() []func() resource.Resource {
|
||||
NewVcVmV2Resource,
|
||||
NewVcVmV3Resource,
|
||||
NewVcexternalipResource,
|
||||
NewVmpostgreResource,
|
||||
NewClickhouseDatabaseResource,
|
||||
NewClickhouseUserResource,
|
||||
NewK8sSthutrvalClusterUserResource,
|
||||
NewKafkaTopicResource,
|
||||
NewKafkaUserResource,
|
||||
NewMariadbDatabaseResource,
|
||||
NewMariadbUserResource,
|
||||
NewMongodbUserResource,
|
||||
NewNextcloudBackupResource,
|
||||
NewPostgresBackupResource,
|
||||
NewPostgresDatabaseResource,
|
||||
NewPostgresUserResource,
|
||||
NewS3SubUserResource,
|
||||
NewVcOrgUserResource,
|
||||
NewVcVdcGroupVdcResource,
|
||||
NewClickhouseReconcileResource,
|
||||
NewDnsrecordReconcileResource,
|
||||
NewDummyReconcileResource,
|
||||
NewDummyRedeployResource,
|
||||
NewFlaskReconcileResource,
|
||||
NewFlaskRedeployResource,
|
||||
NewHttpRedeployResource,
|
||||
NewK8sSthutrvalClusterReconcileResource,
|
||||
NewK8sSthutrvalClusterUpdateSecretsResource,
|
||||
NewLuceeReconcileResource,
|
||||
NewLuceeRedeployResource,
|
||||
NewLuceeRestartResource,
|
||||
NewMariadbReconcileResource,
|
||||
NewNextcloudReconcileResource,
|
||||
NewNextcloudRecoveryResource,
|
||||
NewNextcloudRestartResource,
|
||||
NewNodejsReconcileResource,
|
||||
NewNodejsRedeployResource,
|
||||
NewPgadminRedeployResource,
|
||||
NewPostgresReconcileResource,
|
||||
NewPostgresRecoveryResource,
|
||||
NewPostgresRestartResource,
|
||||
NewRedisReconcileResource,
|
||||
NewS3ReconcileResource,
|
||||
NewTemplateReconcileResource,
|
||||
NewVcNsxtReconcileResource,
|
||||
NewVcOrgReconcileResource,
|
||||
NewVcVdcReconcileResource,
|
||||
NewVcVdcGroupAddVdcResource,
|
||||
NewVcVdcGroupReconcileResource,
|
||||
NewVcVdcGroupRemoveVdcResource,
|
||||
NewVcVmV3ReconcileResource,
|
||||
NewVcVmV3RedeployResource,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
name: openwhisk
|
||||
service_id: 100
|
||||
service_display_name: Serverless Openwhisk (openwhisk_tenant)
|
||||
service_short_name: openwhisk
|
||||
service_man: Добавление и удаления пользователя для работы с Openwhisk
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: false
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 41
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 64
|
||||
code: username
|
||||
data_type: string
|
||||
required: true
|
||||
descr: Имя пользователя Openwhisk
|
||||
- name: delete
|
||||
id: 42
|
||||
kind: instance
|
||||
action: delete
|
||||
params:
|
||||
- id: 65
|
||||
code: username
|
||||
data_type: string
|
||||
required: true
|
||||
descr: Имя пользователя Openwhisk
|
||||
@@ -0,0 +1,94 @@
|
||||
name: dnszone
|
||||
service_id: 110
|
||||
service_display_name: DNS зона
|
||||
service_short_name: dnszone
|
||||
service_man: '##  Необходимо делегировать зону у регистратора на Авторитетные DNS (Сервера зон)<br/><br/>ns3.ngcloud.ru<br/>``` bash<br/>185.247.187.83<br/>2a10:aa80:0:5::3<br/>```<br/><br/>ns4.ngcloud.ru<br/>``` bash<br/>81.22.46.43<br/>2a10:aa80:0:6::3<br/>```'
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: false
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 43
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 66
|
||||
code: zoneEmail
|
||||
data_type: string
|
||||
required: true
|
||||
descr: Контактный e-mail
|
||||
- id: 67
|
||||
code: zoneName
|
||||
data_type: string
|
||||
required: true
|
||||
regex: ^([a-z0-9-]+\.)+[a-z0-9-]+$
|
||||
descr: Имя зоны
|
||||
man: Имя зоны
|
||||
- id: 68
|
||||
code: zoneKind
|
||||
data_type: string
|
||||
required: true
|
||||
default: NATIVE
|
||||
value_list:
|
||||
- NATIVE
|
||||
- SLAVE
|
||||
descr: Тип зоны
|
||||
- id: 69
|
||||
code: zoneMastersForSlave
|
||||
data_type: string
|
||||
required: false
|
||||
descr: Использовать если выбран тип SLAVE. IP адреса в формате через пробел для настройки Master-серверов
|
||||
- id: 70
|
||||
code: zoneTimeToRefresh
|
||||
data_type: string
|
||||
required: true
|
||||
default: "86400"
|
||||
descr: Время ожидания ответа вторичного DNS перед запросом SOA-записи с первичных серверов
|
||||
- id: 71
|
||||
code: zoneTimeToRetry
|
||||
data_type: string
|
||||
required: true
|
||||
default: "7200"
|
||||
descr: Время в течение которого сервер повторно попробует синхронизировать информацию с первичных DNS-серверов
|
||||
- id: 72
|
||||
code: zoneTimeToExpire
|
||||
data_type: string
|
||||
required: true
|
||||
default: "3600000"
|
||||
descr: Время в течение которого вторичный DNS будет пытаться завершить синхронизацию зоны с первичным
|
||||
- id: 73
|
||||
code: zoneTimeToLive
|
||||
data_type: string
|
||||
required: true
|
||||
default: "86400"
|
||||
descr: Время, в течение которого информация будет кешироваться другими DNS-серверами
|
||||
- id: 284
|
||||
code: resourceRealm
|
||||
data_type: string
|
||||
required: true
|
||||
func: getAvailableResourceRealms
|
||||
descr: Платформа для развертывания
|
||||
- name: delete
|
||||
id: 44
|
||||
kind: instance
|
||||
action: delete
|
||||
params: []
|
||||
@@ -0,0 +1,105 @@
|
||||
name: dnsrecord
|
||||
service_id: 111
|
||||
service_display_name: DNS запись
|
||||
service_short_name: dnsrecord
|
||||
service_man: Необходима DNS зона, в которой будет создана DNS запись.<br/>Выберите <br/>* Зону в которой будет создана запись<br/>* Тип записи<br/>* Имя записи<br/>* Значение записи
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: false
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 45
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 75
|
||||
code: zoneUid
|
||||
data_type: uuid
|
||||
required: true
|
||||
ref_svc_id: 109
|
||||
descr: UUID экземпляра услуги DNS-зоны
|
||||
man: UUID экземпляра услуги DNS-зоны
|
||||
sort: 10
|
||||
- id: 76
|
||||
code: recordType
|
||||
data_type: string
|
||||
required: true
|
||||
value_list:
|
||||
- A
|
||||
- AAAA
|
||||
- CNAME
|
||||
- MX
|
||||
- PTR
|
||||
- SOA
|
||||
- SRV
|
||||
- TXT
|
||||
- LUA
|
||||
- NS
|
||||
- SSHFP
|
||||
descr: Тип DNS-записи
|
||||
man: Тип DNS-записи
|
||||
sort: 20
|
||||
- id: 77
|
||||
code: recordName
|
||||
data_type: string
|
||||
required: true
|
||||
unique_scope: parent
|
||||
descr: Имя записи. Имя зоны в конце проставляется автоматически
|
||||
man: Имя записи. Имя зоны в конце проставляется автоматически
|
||||
sort: 30
|
||||
- id: 78
|
||||
code: recordInput
|
||||
data_type: string
|
||||
required: true
|
||||
descr: Значение. Необходима валидация, в зависимости от типа записи. Должно быть заменено на UUID-внешнийIP
|
||||
man: Значение. Необходима валидация, в зависимости от типа записи. Должно быть заменено на UUID-внешнийIP
|
||||
sort: 40
|
||||
is_modifiable: true
|
||||
- id: 450
|
||||
code: recordTTL
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
default: "3600"
|
||||
maxvalue: 604800
|
||||
minvalue: 30
|
||||
descr: Time-to-Leave записи. Раз в какое время запись будет обновлена
|
||||
sort: 50
|
||||
- name: delete
|
||||
id: 46
|
||||
kind: instance
|
||||
action: delete
|
||||
params: []
|
||||
- name: modify
|
||||
id: 90
|
||||
kind: instance
|
||||
action: modify
|
||||
params:
|
||||
- id: 256
|
||||
code: recordInput
|
||||
data_type: string
|
||||
required: true
|
||||
descr: Запись. Необходима валидация, в зависимости от типа записи. Должно быть заменено на UUID-внешнийIP
|
||||
- name: reconcile
|
||||
id: 408
|
||||
kind: action
|
||||
action: reconcile
|
||||
params: []
|
||||
@@ -0,0 +1,57 @@
|
||||
name: tenant
|
||||
service_id: 112
|
||||
service_display_name: Тенант в Grafana
|
||||
service_short_name: tenant
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: false
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 84
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 239
|
||||
code: grafanaOrgName
|
||||
data_type: string
|
||||
required: true
|
||||
regex: ^WZ[\d]+$
|
||||
descr: WZ-Код клиента
|
||||
sort: 10
|
||||
- id: 240
|
||||
code: resourceRealm
|
||||
data_type: string
|
||||
required: true
|
||||
func: getAvailableResourceRealms
|
||||
descr: Платформа для развертывания
|
||||
sort: 20
|
||||
- name: delete
|
||||
id: 86
|
||||
kind: instance
|
||||
action: delete
|
||||
params: []
|
||||
- name: modify
|
||||
id: 87
|
||||
kind: instance
|
||||
action: modify
|
||||
man: Просто удаляет все текущие датасоурсы и ставит их заново<br/>Полезно, когда нужно "ресетнуть" / "добавить"
|
||||
params: []
|
||||
@@ -0,0 +1,155 @@
|
||||
name: vc_complex
|
||||
service_id: 113
|
||||
service_display_name: Быстрый старт
|
||||
service_short_name: vcComplex
|
||||
service_man: '# Создает окружение для ВМ.<br/><br/>## Создает сервисы:<br/>**Организация**<br/>**Виртуальный датацентр**<br/>**Каталог**<br/>**Сетевой шлюз периметра**<br/><br/>'
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: false
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 107
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 332
|
||||
code: resourceRealm
|
||||
data_type: string
|
||||
required: true
|
||||
func: getAvailableResourceRealms
|
||||
descr: Платформа для развертывания
|
||||
man: Наименование ресурсной платформы
|
||||
sort: 1
|
||||
- id: 334
|
||||
code: vappName
|
||||
data_type: string
|
||||
required: true
|
||||
regex: ^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$
|
||||
descr: Имя vApp
|
||||
sort: 10
|
||||
- id: 358
|
||||
code: vdcProviderGateway
|
||||
data_type: string
|
||||
required: true
|
||||
default: sandbox-v1cl1-pvdc
|
||||
descr: Взять из свойств ресурсной платформы.<br/>Путь .vcd.providerVdc - взять KEY<br/><br/>providerGateway Cloud Director. Влияет ресурсы будут предоставлены. Является полем (List<String>) в свойствах рес. платформы ЛК, но сам параметр передаётся только как один String
|
||||
sort: 200
|
||||
- id: 359
|
||||
code: virtualServicesCount
|
||||
required: true
|
||||
default: "1"
|
||||
maxvalue: 4
|
||||
minvalue: 1
|
||||
descr: Кол-во виртуальных сервисов, которые выделяются на AVI. Во избежании коллапса пока выделяется до 4
|
||||
sort: 110
|
||||
- id: 360
|
||||
code: ipSpaceName
|
||||
data_type: string
|
||||
required: false
|
||||
default: internet-ipv4-v1
|
||||
descr: Имя ip Space для внешнего IP. Целесообразнее брать самый дешёвый, так как это vip для исходящего трафика. Необходимо задавать если включен needExternalAddressSNAT
|
||||
sort: 130
|
||||
- id: 399
|
||||
code: storageConfig
|
||||
data_type: json
|
||||
required: true
|
||||
default: '[{"name": "", "size": 1024}]'
|
||||
descr: Описывается как массив объектов, где описывается имя политики и её размер
|
||||
sort: 210
|
||||
- id: 400
|
||||
code: vdcNetworkPool
|
||||
data_type: string
|
||||
required: true
|
||||
default: nsxt-sandbox-geneve-np
|
||||
sort: 120
|
||||
- id: 401
|
||||
code: cpuGuaranteed
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
default: "20"
|
||||
value_list:
|
||||
- "20"
|
||||
- "50"
|
||||
- "80"
|
||||
sort: 20
|
||||
- id: 402
|
||||
code: memGuaranteed
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
default: "20"
|
||||
value_list:
|
||||
- "20"
|
||||
- "50"
|
||||
- "80"
|
||||
sort: 30
|
||||
- id: 403
|
||||
code: segroupName
|
||||
data_type: string
|
||||
required: false
|
||||
default: SEGROUP-SANDBOX-CL1-SHARED-01
|
||||
descr: Наименование segroup, в котором будут выделяться пулы на AVI
|
||||
sort: 100
|
||||
- id: 404
|
||||
code: needExternalAddressSNAT
|
||||
data_type: boolean
|
||||
required: true
|
||||
default: "true"
|
||||
value_list:
|
||||
- "false"
|
||||
- "true"
|
||||
descr: Выделяет внешний IP на Edge. Используется для SNAT всего трафика
|
||||
sort: 40
|
||||
- id: 405
|
||||
code: needEnableAVI
|
||||
data_type: boolean
|
||||
required: true
|
||||
default: "false"
|
||||
value_list:
|
||||
- "false"
|
||||
- "true"
|
||||
descr: Включает AVI в Cloud Director
|
||||
sort: 50
|
||||
- id: 566
|
||||
code: cpuAllocated
|
||||
data_type: string
|
||||
required: true
|
||||
default: "1"
|
||||
minvalue: 1
|
||||
descr: шт
|
||||
sort: 21
|
||||
- id: 567
|
||||
code: memAllocated
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
default: "10"
|
||||
minvalue: 1
|
||||
descr: gb
|
||||
sort: 31
|
||||
- id: 568
|
||||
code: vIPCount
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
default: "0"
|
||||
maxvalue: 20
|
||||
minvalue: 0
|
||||
descr: ш
|
||||
sort: 131
|
||||
@@ -0,0 +1,28 @@
|
||||
name: gitea_complex
|
||||
service_id: 114
|
||||
service_display_name: Комплексная услуга по созданию gitea
|
||||
service_short_name: GiteaComplex
|
||||
service_man: Комплексная услуга из ["gittea", "postgres"]
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: false
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations: []
|
||||
@@ -0,0 +1,203 @@
|
||||
name: mariadb
|
||||
service_id: 115
|
||||
service_display_name: Mariadb
|
||||
service_short_name: mariadb
|
||||
service_man: 'Mariadb кластер. Ресурсная платформа: k8s. Автоматизация: k8s operator.<br/>Автоматическое резервное копирование, ручное восстановление.'
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: true
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 138
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 877
|
||||
code: startupConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 10
|
||||
- id: 878
|
||||
code: clusterConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 20
|
||||
is_modifiable: true
|
||||
- id: 879
|
||||
code: accessConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 30
|
||||
is_modifiable: true
|
||||
- id: 880
|
||||
code: mariadbConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 40
|
||||
is_modifiable: true
|
||||
- id: 881
|
||||
code: backupConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 60
|
||||
is_modifiable: true
|
||||
- id: 882
|
||||
code: autoscaleConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 70
|
||||
is_modifiable: true
|
||||
- name: create_database
|
||||
id: 242
|
||||
kind: subresource
|
||||
action: create
|
||||
subresource: database
|
||||
params:
|
||||
- id: 734
|
||||
code: dbName
|
||||
data_type: string
|
||||
required: true
|
||||
default: db-
|
||||
regex: ^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$
|
||||
descr: 'Пример: `db01`'
|
||||
- name: create_user
|
||||
id: 243
|
||||
kind: subresource
|
||||
action: create
|
||||
subresource: user
|
||||
params:
|
||||
- id: 735
|
||||
code: username
|
||||
data_type: string
|
||||
required: true
|
||||
default: user
|
||||
regex: ^(?![0-9_])[a-zA-Z_][a-zA-Z0-9_]{0,62}$
|
||||
maxlength: 62
|
||||
minlength: 2
|
||||
descr: 'Пример: `myusername`'
|
||||
sort: 10
|
||||
- id: 736
|
||||
code: password
|
||||
data_type: string
|
||||
required: true
|
||||
maxlength: 62
|
||||
minlength: 8
|
||||
descr: Секрет будет записан в хранилище секретов и не будет отображаться публично
|
||||
sort: 20
|
||||
is_sensitive: true
|
||||
- id: 737
|
||||
code: dbName
|
||||
data_type: array-map-fixed
|
||||
required: true
|
||||
default: '[{}]'
|
||||
descr: Необходимо указать массив существующих баз
|
||||
man: Если базы не существует - при выполнении операции вернётся ошибка
|
||||
sort: 30
|
||||
- id: 738
|
||||
code: accessHosts
|
||||
data_type: string
|
||||
required: true
|
||||
default: '%'
|
||||
descr: 'Пример: `%`'
|
||||
sort: 40
|
||||
- name: delete
|
||||
id: 139
|
||||
kind: instance
|
||||
action: delete
|
||||
params:
|
||||
- id: 456
|
||||
code: deleteS3Bucket
|
||||
data_type: boolean
|
||||
required: true
|
||||
default: "true"
|
||||
value_list:
|
||||
- "false"
|
||||
- "true"
|
||||
descr: Также удалить технический S3 Bucket с бэкапами?
|
||||
sort: 10
|
||||
- name: delete_database
|
||||
id: 248
|
||||
kind: subresource
|
||||
action: delete
|
||||
subresource: database
|
||||
params:
|
||||
- id: 745
|
||||
code: dbName
|
||||
data_type: string
|
||||
required: true
|
||||
descr: ВСЕ ДАННЫЕ ВНУТРИ БД БУДУТ УДАЛЕНЫ
|
||||
- name: delete_user
|
||||
id: 247
|
||||
kind: subresource
|
||||
action: delete
|
||||
subresource: user
|
||||
params:
|
||||
- id: 744
|
||||
code: username
|
||||
data_type: string
|
||||
required: true
|
||||
descr: Имя пользователя, созданное через операцию `create_user`
|
||||
- name: modify
|
||||
id: 142
|
||||
kind: instance
|
||||
action: modify
|
||||
params:
|
||||
- id: 883
|
||||
code: clusterConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 10
|
||||
- id: 884
|
||||
code: accessConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 20
|
||||
- id: 885
|
||||
code: mariadbConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 30
|
||||
- id: 886
|
||||
code: backupConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 40
|
||||
- id: 887
|
||||
code: autoscaleConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 50
|
||||
- name: reconcile
|
||||
id: 287
|
||||
kind: action
|
||||
action: reconcile
|
||||
params: []
|
||||
- name: resume
|
||||
id: 141
|
||||
kind: instance
|
||||
action: resume
|
||||
params: []
|
||||
- name: suspend
|
||||
id: 140
|
||||
kind: instance
|
||||
action: suspend
|
||||
params: []
|
||||
@@ -0,0 +1,291 @@
|
||||
name: kafka
|
||||
service_id: 116
|
||||
service_display_name: ApacheKafka
|
||||
service_short_name: kafka
|
||||
service_man: "# Инструкция по развертыванию Apache Kafka через платформу<br/><br/>---<br/>## 1. Общая информация<br/>Брокер сообщений Apache Kafka — это отказоустойчивая распределённая платформа для обмена сообщениями и потоковой обработки данных в реальном времени.<br/>Позволяет надёжно передавать события между сервисами и системами, интегрироваться с внешними решениями и использовать как внутренние, так и защищённые TLS-подключения. Развёртывание полностью автоматизировано платформой.<br/><br/>## Доступные операции<br/>- **create** — Создание кластера<br/>- **delete** — Удаление кластера<br/>- **suspend** — Остановка кластера<br/>- **resume** — Запуск кластера<br/><br/>- **create_topic** — Создание топика<br/>- **modify_topic** — Модификация топика<br/>- **delete_topic** — Удаление топика<br/><br/>- **create_user** — Создание пользователя<br/>- **modify_user** — Модификация пользователя<br/>- **delete_user** — Удаление пользователя<br/><br/><br/>---<br/>## 2. Параметры развертывания кластера<br/>Ниже перечислены параметры, которые необходимо указать при создании ресурса Kafka:<br/><br/>* **Платформа для развертывания:**<br/> Платформа для развертывания (среда, в которой будет развернут кластер). Пример: `\tk8s-3.ext.nubes.ru`.<br/><br/>* **Количество узлов в сети:**<br/> Количество узлов (брокеров) в кластере Kafka<br/><br/>* **Квота (megabyte) памяти пода:**<br/> Квота памяти пода в мегабайтах. Пример: `2048`<br/><br/>* **Квота (millicore ) ядра пода:**<br/> Квота CPU пода в миликорах (1000 mCPU = 1 vCPU). Пример: `1000`<br/><br/>* **Размер (gigabyte) диска для базы данных:**<br/> Размер диска для хранения данных в гигабайтах. Пример: `20`<br/><br/>* **Выделяет внешний IP для доступа к master:**<br/> Флаг, определяющий, нужно ли выделять внешний IP для доступа к master-брокеру. Пример: `true` или `false`<br/><br/>* **Имя ipSpace для публикации внешнего IP**<br/> Если включен предыдущий параметр, то поле необходимо заполнить. Пример: `internet-no-antiddos-v1`<br/><br/>---<br/>## 4. Выходные параметры<br/>После успешного развертывания ресурса Kafka платформа возвращает ряд параметров.<br/>Ключевым из них является:<br/><br/>**internalConnect**<br/>Это внутренний эндпоинт, который используется для подключения к кластеру Kafka из других сервисов, развернутых внутри той же сети.<br/>Эндпоинт предоставляет доступ к брокерам Kafka по внутренним портам, указанным в конфигурации слушателей.<br/><br/>**externalConnect**<br/>Это внешний эндпоинт (fqdn), который используется для подключения к кластеру Kafka. Для подключения используеться 9093 порт.<br/><br/>Для подключения используются следующие порты:<br/><br/>* 9092 — внутренний нешифрованный доступ (plain).<br/>* 9093 — внутренний и внешний доступ по TLS, с обязательной TLS-аутентификацией.<br/><br/>---<br/>## 5. Дополнительная информация<br/>Для создания топиков, пользователей и управления ими, а также для интеграции с кластерами Kafka используйте соответствующие сервисы и раздел "Облачные сервисы".<br/>---"
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: true
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 148
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 461
|
||||
code: resourceInstances
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
value_list:
|
||||
- "1"
|
||||
- "2"
|
||||
- "3"
|
||||
- "4"
|
||||
- "5"
|
||||
minvalue: 1
|
||||
descr: 'Пример: `2`'
|
||||
sort: 20
|
||||
is_modifiable: true
|
||||
- id: 462
|
||||
code: resourceMemory
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
minvalue: 1000
|
||||
descr: 'Пример: `2048`'
|
||||
sort: 30
|
||||
is_modifiable: true
|
||||
- id: 463
|
||||
code: resourceCPU
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
minvalue: 1000
|
||||
descr: 'Пример: `2000`'
|
||||
sort: 40
|
||||
is_modifiable: true
|
||||
- id: 464
|
||||
code: resourceDisk
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
descr: 'Пример: `20`'
|
||||
sort: 50
|
||||
is_modifiable: true
|
||||
- id: 465
|
||||
code: needExternalAddressMaster
|
||||
data_type: boolean
|
||||
required: true
|
||||
default: "false"
|
||||
value_list:
|
||||
- "false"
|
||||
- "true"
|
||||
descr: 'Флаг, определяющий, нужно ли выделять внешний IP для доступа к master-брокеру. Пример: `true` или `false`'
|
||||
sort: 60
|
||||
is_modifiable: true
|
||||
- id: 466
|
||||
code: ipSpaceNameMaster
|
||||
data_type: string
|
||||
required: false
|
||||
descr: 'Если включен предыдущий параметр, то поле необходимо заполнить. Пример: `internet-no-antiddos-v1`'
|
||||
sort: 70
|
||||
is_modifiable: true
|
||||
- id: 469
|
||||
code: resourceRealm
|
||||
data_type: string
|
||||
required: true
|
||||
func: getAvailableResourceRealms
|
||||
descr: Выбрать платформу из списка
|
||||
sort: 5
|
||||
- name: create_topic
|
||||
id: 165
|
||||
kind: subresource
|
||||
action: create
|
||||
subresource: topic
|
||||
man: '# Инструкция по управлению топиками Apache Kafka через платформу<br/><br/>## 1. Общая информация<br/><br/>---<br/><br/>Топик — это основная сущность в Apache Kafka, предназначенная для приёма сообщений от продюсеров и передачи их консюмерам.<br/>Платформа позволяет создавать, удалять и изменять параметры топиков без прямого доступа к брокерам, обеспечивая удобное управление через интерфейс или API.<br/><br/>---<br/><br/>## Доступные операции<br/><br/>- **create_topic** — Создание топика<br/>- **modify_topic** — Модификация топика<br/>- **delete_topic** — Удаление топика<br/><br/>---<br/><br/>## 2. Параметры создания топика<br/><br/>Ниже перечислены параметры, которые необходимо указать при создании нового топика:<br/><br/>* **Имя топика**<br/> Название топика.<br/> Должно быть уникальным в пределах кластера.<br/> Пример: `orders-events`.<br/><br/>* **Партиции**<br/> Количество партиций топика.<br/> Рекомендуется выбирать с учётом уровня параллелизма потребителей и предполагаемой нагрузки.<br/> Пример: `6`. Здесь важно рассказать о том как происходит расчет оптимального количества<br/> Обратите внимание: уменьшить количество партиций после создания невозможно, поэтому лучше закладывать небольшой запас на будущий рост нагрузки.<br/><br/>* **Реплики**<br/> Количество реплик для каждой партиции.<br/> Обычно равно количеству доступных брокеров или меньше.<br/> Пример: `3`.<br/><br/><br/>---<br/><br/>## 4. Параметры модификации топика<br/><br/>Для уже существующих топиков можно изменять параметры. Все параметры являются опциональными:<br/><br/>* **Имя топика**<br/> Название топика.<br/> Пример: `orders-events`.<br/><br/>* **Партиции**<br/> Новое количество партиций.<br/> Пример: `12`.<br/><br/>* **Реплики**<br/> Новое количество реплик.<br/> Пример: `2`.<br/><br/>---<br/><br/>## 5. Рекомендации<br/><br/>* Планируйте количество партиций с запасом — уменьшение их числа невозможно.<br/>* Количество реплик должно соответствовать требуемому уровню отказоустойчивости, но не превышать количество брокеров.<br/>* Изменение параметров топика в рабочем окружении может временно влиять на производительность. Рекомендуется проводить крупные изменения в периоды минимальной нагрузки.<br/><br/>---'
|
||||
params:
|
||||
- id: 523
|
||||
code: nameTopic
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `orders-events`'
|
||||
sort: 10
|
||||
- id: 524
|
||||
code: partitions
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
descr: Число партиций можно увеличить, но уменьшить их нельзя.
|
||||
- id: 525
|
||||
code: replicas
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
descr: Количество реплик не может привышать количество реплик брокера
|
||||
sort: 20
|
||||
- name: create_user
|
||||
id: 168
|
||||
kind: subresource
|
||||
action: create
|
||||
subresource: user
|
||||
man: '# Инструкция по управлению пользователями Apache Kafka.<br/><br/>---<br/><br/>## 1. Общая информация<br/><br/>Пользователи в Apache Kafka используются для аутентификации и авторизации при подключении к кластеру.<br/>Платформа позволяет создавать, удалять и изменять пользователей без прямого доступа к брокерам, а также настраивать права доступа к топикам и группам.<br/>Это обеспечивает централизованное и безопасное управление доступом к ресурсам Kafka.<br/><br/>---<br/><br/>## Доступные операции<br/><br/>- **create_user** — Создание пользователя<br/>- **modify_user** — Модификация пользователя<br/>- **delete_user** — Удаление пользователя<br/><br/>---<br/><br/>## 2. Параметры создания пользователя<br/><br/>Ниже перечислены параметры, которые необходимо указать при создании нового пользователя:<br/><br/>* **Имя пользователя**<br/> Имя создаваемого пользователя. Используется для подключения к Kafka и настройки прав.<br/> Пример: `username-admin`<br/><br/>* **Топик**<br/> Название топика, к которому будут назначены права для данного пользователя.<br/> Пример: `orders-events`.<br/> Использовать маску нельзя (`orders*`), только полное название или `*`.<br/><br/>* **Операции**<br/> Список разрешённых операций на указанном топике.<br/> Пример: `Describe, Read, Write`<br/><br/>* **Группа**<br/> Имя группы, которой разрешён доступ. Используется при настройке консюмер-групп.<br/> Пример: `order-consumers`.<br/><br/>* **Разрешённые хосты**<br/> Список хостов или подсетей, с которых разрешено подключение пользователя.<br/> Пример: `192.168.1.0/24` или `*`.<br/><br/>---<br/><br/>## 3. Параметры модификации пользователя<br/><br/>Ниже перечислены параметры, которые необходимо указать при модификации пользователя:<br/><br/>* **Имя пользователя**<br/> Имя создаваемого пользователя. Используется для подключения к Kafka и настройки прав.<br/> Пример: `username-admin`<br/><br/>* **Топик**<br/> Название топика, к которому будут назначены права для данного пользователя.<br/> Пример: `orders-events`.<br/> Использовать маску нельзя (`orders*`), только полное название или `*`.<br/><br/>* **Операции**<br/> Список разрешённых операций на указанном топике.<br/> Пример: `Describe, Read, Write`<br/><br/>* **Группа**<br/> Имя группы, которой разрешён доступ. Используется при настройке консюмер-групп.<br/> Пример: `order-consumers`.<br/><br/>* **Разрешённые хосты**<br/> Список хостов или подсетей, с которых разрешено подключение пользователя.<br/> Пример: `192.168.1.0/24` или `*`.<br/><br/>---<br/><br/>## 4. Параметры удалении пользователя<br/><br/>Ниже перечислены параметры, которые необходимо указать при удалении пользователя:<br/><br/>* **Имя пользователя**<br/> Имя удаляемого пользователя.<br/> Пример: `username-admin`<br/><br/>## 5. Выходные параметры<br/><br/>После успешного создания пользователя платформа возвращает параметры, необходимые для подключения:<br/><br/>* **ca.crt**<br/> Корневой сертификат (Certificate Authority), используемый для проверки подлинности сервера при TLS-подключении.<br/> Файл содержит публичный ключ удостоверяющего центра, которому доверяют брокеры и клиенты. Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.crt<br/> ```<br/><br/>* **ca.p12**<br/> PKCS#12-хранилище с корневым сертификатом (Certificate Authority), используемое для проверки подлинности сервера при TLS-подключении.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.p12<br/> ```<br/><br/>* **ca.password**<br/> Пароль для доступа к хранилищу `ca.p12`. Выдаеться в чистом виде. Раскодировать не нужно.<br/><br/>* **user.crt**<br/> Клиентский сертификат, подтверждающий личность пользователя при подключении к Kafka.<br/> Используется совместно с приватным ключом `user.key` для аутентификации.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.crt<br/> ```<br/><br/>* **user.key**<br/> Приватный ключ пользователя, применяемый для установления защищённого TLS-соединения и подтверждения подлинности.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.key<br/> ```<br/><br/>* **user.p12**<br/> PKCS#12-хранилище с клиентским сертификатом и приватным ключом, предназначенное для аутентификации пользователя в Kafka.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo "<содержание поля>" | base64 -d > <название>.p12<br/> ```<br/><br/>* **user.password**<br/> Пароль для доступа к хранилищу `user.p12`. Выдаеться в чистом виде. Раскодировать не нужно.<br/><br/>Эти параметры используются для настройки TLS-подключения.<br/><br/>---<br/><br/>## 6. Пример конфигурации клиента внутри кластера<br/><br/>Пример пода:<br/><br/>```yaml<br/>apiVersion: v1<br/>kind: Pod<br/>metadata:<br/> name: kafka-4<br/>spec:<br/> volumes:<br/> - name: work<br/> emptyDir: {}<br/> containers:<br/> - name: kcat<br/> image: quay.io/strimzi/kafka:latest-kafka-4.0.0<br/> command: ["sleep", "5600"]<br/> volumeMounts:<br/> - name: work<br/> mountPath: /work<br/> restartPolicy: Never<br/>```<br/><br/>Создаём под и сохраняем сертификаты:<br/><br/>```bash<br/>kubectl cp user.p12 kafka-4:/work/<br/>kubectl cp ca.p12 kafka-4:/work/<br/>```<br/><br/>Создаём файлы конфигурации:<br/><br/>```bash<br/>cat > /work/user-read.properties <<''EOF''<br/>security.protocol=SSL<br/>ssl.keystore.type=PKCS12<br/>ssl.keystore.location=/work/user.p12<br/>ssl.keystore.password=<user.password><br/>ssl.truststore.type=PKCS12<br/>ssl.truststore.location=/work/ca.p12<br/>ssl.truststore.password=<ca.password><br/>EOF<br/>```<br/><br/>```bash<br/>cat > /work/user-write.properties <<''EOF''<br/>security.protocol=SSL<br/>ssl.keystore.type=PKCS12<br/>ssl.keystore.location=/work/user-write.p12<br/>ssl.keystore.password=<user.password><br/>ssl.truststore.type=PKCS12<br/>ssl.truststore.location=/work/ca.p12<br/>ssl.truststore.password=<ca.password><br/>EOF<br/>```<br/><br/>Листинг доступных топиков:<br/><br/>```bash<br/>/opt/kafka/bin/kafka-topics.sh \<br/> --bootstrap-server kafkak8s-bootstrap-external:9093 \<br/> --command-config /work/user-read.properties \<br/> --list<br/>```<br/><br/>Запись сообщений:<br/><br/>```bash<br/>/opt/kafka/bin/kafka-console-producer.sh \<br/> --bootstrap-server <адрес сервис брокера>:9093 \<br/> --topic <topic> \<br/> --producer.config /work/user-write.properties<br/>```<br/><br/>Чтение сообщений:<br/><br/>```bash<br/>/opt/kafka/bin/kafka-console-consumer.sh \<br/> --bootstrap-server <адрес сервис брокера>:9093 \<br/> --topic <topic> \<br/> --from-beginning \<br/> --group <group> \<br/> --consumer.config /work/user-read.properties \<br/> --max-messages 50<br/>```<br/><br/>---<br/><br/>## 7. Пример внешнего подключения с использованием **kcat**<br/><br/>**Листинг топиков**<br/><br/>```bash<br/>kcat -b <externalConnect.bootstrap.fqdn>:9093 \<br/> -L \<br/> -X security.protocol=SSL \<br/> -X ssl.ca.location=./ca.crt \<br/> -X ssl.certificate.location=./user-read.crt \<br/> -X ssl.key.location=./user-read.key \<br/> -X ssl.endpoint.identification.algorithm=none<br/>```<br/><br/>**Запись сообщения**<br/><br/>```bash<br/> echo "hello from kcat" | kcat -P \<br/> -b <externalConnect.bootstrap.fqdn>:9093 \<br/> -t write-test \<br/> -X security.protocol=SSL \<br/> -X ssl.ca.location=./ca.crt \<br/> -X ssl.certificate.location=./user-write.crt \<br/> -X ssl.key.location=./user-write.key \<br/> -X ssl.endpoint.identification.algorithm=none<br/>```<br/><br/>**Чтение сообщений**<br/><br/>```bash<br/>kcat -C \<br/> -b <externalConnect.bootstrap.fqdn>:9093 \<br/> -t write-test \<br/> -o beginning \<br/> -q \<br/> -X security.protocol=SSL \<br/> -X ssl.ca.location=./ca.crt \<br/> -X ssl.certificate.location=./user-read.crt \<br/> -X ssl.key.location=./user-read.key \<br/> -X ssl.endpoint.identification.algorithm=none<br/>```<br/><br/>---<br/><br/><br/>## 9. Рекомендации<br/><br/>* Минимизируйте набор разрешённых операций для повышения безопасности.<br/>* Используйте отдельные учётные записи для продюсеров, консюмеров и системных сервисов.<br/>* Ограничивайте доступ по IP или подсетям с помощью параметра **Разрешённые хосты**.<br/>* После изменения прав или групп пользователя рекомендуется проверить подключение и доступ к топикам.<br/><br/>---'
|
||||
params:
|
||||
- id: 530
|
||||
code: username
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `username-admin`'
|
||||
- id: 531
|
||||
code: nameTopic
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `orders-events`. Использовать маску нельзя(`orders*`), только полное название или `*`'
|
||||
- id: 532
|
||||
code: operations
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `Create, Describe, Read, Write`'
|
||||
- id: 533
|
||||
code: group
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `order-consumers` или `*`'
|
||||
- id: 534
|
||||
code: accessHosts
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `192.168.1.0/24` или `*`'
|
||||
- name: delete
|
||||
id: 149
|
||||
kind: instance
|
||||
action: delete
|
||||
params: []
|
||||
- name: delete_topic
|
||||
id: 167
|
||||
kind: subresource
|
||||
action: delete
|
||||
subresource: topic
|
||||
params:
|
||||
- id: 529
|
||||
code: nameTopic
|
||||
data_type: string
|
||||
required: true
|
||||
descr: Необходимо выбрать топик, который необходимо удалить
|
||||
- name: delete_user
|
||||
id: 170
|
||||
kind: subresource
|
||||
action: delete
|
||||
subresource: user
|
||||
man: '# Инструкция по управлению пользователями Apache Kafka.<br/><br/>---<br/><br/>## 1. Общая информация<br/><br/>Пользователи в Apache Kafka используются для аутентификации и авторизации при подключении к кластеру.<br/>Платформа позволяет создавать, удалять и изменять пользователей без прямого доступа к брокерам, а также настраивать права доступа к топикам и группам.<br/>Это обеспечивает централизованное и безопасное управление доступом к ресурсам Kafka.<br/><br/>---<br/><br/>## Доступные операции<br/><br/>- **create_user** — Создание пользователя<br/>- **modify_user** — Модификация пользователя<br/>- **delete_user** — Удаление пользователя<br/><br/>---<br/><br/>## 2. Параметры создания пользователя<br/><br/>Ниже перечислены параметры, которые необходимо указать при создании нового пользователя:<br/><br/>* **Имя пользователя**<br/> Имя создаваемого пользователя. Используется для подключения к Kafka и настройки прав.<br/> Пример: `username-admin`<br/><br/>* **Топик**<br/> Название топика, к которому будут назначены права для данного пользователя.<br/> Пример: `orders-events`.<br/> Использовать маску нельзя (`orders*`), только полное название или `*`.<br/><br/>* **Операции**<br/> Список разрешённых операций на указанном топике.<br/> Пример: `Describe, Read, Write`<br/><br/>* **Группа**<br/> Имя группы, которой разрешён доступ. Используется при настройке консюмер-групп.<br/> Пример: `order-consumers`.<br/><br/>* **Разрешённые хосты**<br/> Список хостов или подсетей, с которых разрешено подключение пользователя.<br/> Пример: `192.168.1.0/24` или `*`.<br/><br/>---<br/><br/>## 3. Параметры модификации пользователя<br/><br/>Ниже перечислены параметры, которые необходимо указать при модификации пользователя:<br/><br/>* **Имя пользователя**<br/> Имя создаваемого пользователя. Используется для подключения к Kafka и настройки прав.<br/> Пример: `username-admin`<br/><br/>* **Топик**<br/> Название топика, к которому будут назначены права для данного пользователя.<br/> Пример: `orders-events`.<br/> Использовать маску нельзя (`orders*`), только полное название или `*`.<br/><br/>* **Операции**<br/> Список разрешённых операций на указанном топике.<br/> Пример: `Describe, Read, Write`<br/><br/>* **Группа**<br/> Имя группы, которой разрешён доступ. Используется при настройке консюмер-групп.<br/> Пример: `order-consumers`.<br/><br/>* **Разрешённые хосты**<br/> Список хостов или подсетей, с которых разрешено подключение пользователя.<br/> Пример: `192.168.1.0/24` или `*`.<br/><br/>---<br/><br/>## 4. Параметры удалении пользователя<br/><br/>Ниже перечислены параметры, которые необходимо указать при удалении пользователя:<br/><br/>* **Имя пользователя**<br/> Имя удаляемого пользователя.<br/> Пример: `username-admin`<br/><br/>## 5. Выходные параметры<br/><br/>После успешного создания пользователя платформа возвращает параметры, необходимые для подключения:<br/><br/>* **ca.crt**<br/> Корневой сертификат (Certificate Authority), используемый для проверки подлинности сервера при TLS-подключении.<br/> Файл содержит публичный ключ удостоверяющего центра, которому доверяют брокеры и клиенты. Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.crt<br/> ```<br/><br/>* **ca.p12**<br/> PKCS#12-хранилище с корневым сертификатом (Certificate Authority), используемое для проверки подлинности сервера при TLS-подключении.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.p12<br/> ```<br/><br/>* **ca.password**<br/> Пароль для доступа к хранилищу `ca.p12`. Выдаеться в чистом виде. Раскодировать не нужно.<br/><br/>* **user.crt**<br/> Клиентский сертификат, подтверждающий личность пользователя при подключении к Kafka.<br/> Используется совместно с приватным ключом `user.key` для аутентификации.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.crt<br/> ```<br/><br/>* **user.key**<br/> Приватный ключ пользователя, применяемый для установления защищённого TLS-соединения и подтверждения подлинности.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.key<br/> ```<br/><br/>* **user.p12**<br/> PKCS#12-хранилище с клиентским сертификатом и приватным ключом, предназначенное для аутентификации пользователя в Kafka.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo "<содержание поля>" | base64 -d > <название>.p12<br/> ```<br/><br/>* **user.password**<br/> Пароль для доступа к хранилищу `user.p12`. Выдаеться в чистом виде. Раскодировать не нужно.<br/><br/>Эти параметры используются для настройки TLS-подключения.<br/><br/>---<br/><br/>## 6. Пример конфигурации клиента внутри кластера<br/><br/>Пример пода:<br/><br/>```yaml<br/>apiVersion: v1<br/>kind: Pod<br/>metadata:<br/> name: kafka-4<br/>spec:<br/> volumes:<br/> - name: work<br/> emptyDir: {}<br/> containers:<br/> - name: kcat<br/> image: quay.io/strimzi/kafka:latest-kafka-4.0.0<br/> command: ["sleep", "5600"]<br/> volumeMounts:<br/> - name: work<br/> mountPath: /work<br/> restartPolicy: Never<br/>```<br/><br/>Создаём под и сохраняем сертификаты:<br/><br/>```bash<br/>kubectl cp user.p12 kafka-4:/work/<br/>kubectl cp ca.p12 kafka-4:/work/<br/>```<br/><br/>Создаём файлы конфигурации:<br/><br/>```bash<br/>cat > /work/user-read.properties <<''EOF''<br/>security.protocol=SSL<br/>ssl.keystore.type=PKCS12<br/>ssl.keystore.location=/work/user.p12<br/>ssl.keystore.password=<user.password><br/>ssl.truststore.type=PKCS12<br/>ssl.truststore.location=/work/ca.p12<br/>ssl.truststore.password=<ca.password><br/>EOF<br/>```<br/><br/>```bash<br/>cat > /work/user-write.properties <<''EOF''<br/>security.protocol=SSL<br/>ssl.keystore.type=PKCS12<br/>ssl.keystore.location=/work/user-write.p12<br/>ssl.keystore.password=<user.password><br/>ssl.truststore.type=PKCS12<br/>ssl.truststore.location=/work/ca.p12<br/>ssl.truststore.password=<ca.password><br/>EOF<br/>```<br/><br/>Листинг доступных топиков:<br/><br/>```bash<br/>/opt/kafka/bin/kafka-topics.sh \<br/> --bootstrap-server kafkak8s-bootstrap-external:9093 \<br/> --command-config /work/user-read.properties \<br/> --list<br/>```<br/><br/>Запись сообщений:<br/><br/>```bash<br/>/opt/kafka/bin/kafka-console-producer.sh \<br/> --bootstrap-server <адрес сервис брокера>:9093 \<br/> --topic <topic> \<br/> --producer.config /work/user-write.properties<br/>```<br/><br/>Чтение сообщений:<br/><br/>```bash<br/>/opt/kafka/bin/kafka-console-consumer.sh \<br/> --bootstrap-server <адрес сервис брокера>:9093 \<br/> --topic <topic> \<br/> --from-beginning \<br/> --group <group> \<br/> --consumer.config /work/user-read.properties \<br/> --max-messages 50<br/>```<br/><br/>---<br/><br/>## 7. Пример внешнего подключения с использованием **kcat**<br/><br/>**Листинг топиков**<br/><br/>```bash<br/>kcat -b <externalConnect.bootstrap.fqdn>:9093 \<br/> -L \<br/> -X security.protocol=SSL \<br/> -X ssl.ca.location=./ca.crt \<br/> -X ssl.certificate.location=./user-read.crt \<br/> -X ssl.key.location=./user-read.key \<br/> -X ssl.endpoint.identification.algorithm=none<br/>```<br/><br/>**Запись сообщения**<br/><br/>```bash<br/> echo "hello from kcat" | kcat -P \<br/> -b <externalConnect.bootstrap.fqdn>:9093 \<br/> -t write-test \<br/> -X security.protocol=SSL \<br/> -X ssl.ca.location=./ca.crt \<br/> -X ssl.certificate.location=./user-write.crt \<br/> -X ssl.key.location=./user-write.key \<br/> -X ssl.endpoint.identification.algorithm=none<br/>```<br/><br/>**Чтение сообщений**<br/><br/>```bash<br/>kcat -C \<br/> -b <externalConnect.bootstrap.fqdn>:9093 \<br/> -t write-test \<br/> -o beginning \<br/> -q \<br/> -X security.protocol=SSL \<br/> -X ssl.ca.location=./ca.crt \<br/> -X ssl.certificate.location=./user-read.crt \<br/> -X ssl.key.location=./user-read.key \<br/> -X ssl.endpoint.identification.algorithm=none<br/>```<br/><br/>---<br/><br/><br/>## 9. Рекомендации<br/><br/>* Минимизируйте набор разрешённых операций для повышения безопасности.<br/>* Используйте отдельные учётные записи для продюсеров, консюмеров и системных сервисов.<br/>* Ограничивайте доступ по IP или подсетям с помощью параметра **Разрешённые хосты**.<br/>* После изменения прав или групп пользователя рекомендуется проверить подключение и доступ к топикам.<br/><br/>---'
|
||||
params:
|
||||
- id: 540
|
||||
code: username
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `username-admin`'
|
||||
- name: modify
|
||||
id: 162
|
||||
kind: instance
|
||||
action: modify
|
||||
params:
|
||||
- id: 501
|
||||
code: resourceInstances
|
||||
data_type: integer > 0
|
||||
required: false
|
||||
value_list:
|
||||
- "1"
|
||||
- "2"
|
||||
- "3"
|
||||
- "4"
|
||||
- "5"
|
||||
descr: 'Пример: `2`'
|
||||
- id: 502
|
||||
code: resourceMemory
|
||||
data_type: integer > 0
|
||||
required: false
|
||||
minvalue: 1000
|
||||
descr: 'Пример: `2048`'
|
||||
- id: 503
|
||||
code: resourceCPU
|
||||
required: false
|
||||
minvalue: 1000
|
||||
descr: 'Пример: `2000`'
|
||||
- id: 504
|
||||
code: resourceDisk
|
||||
data_type: integer > 0
|
||||
required: false
|
||||
descr: 'Пример: `20`'
|
||||
- id: 505
|
||||
code: needExternalAddressMaster
|
||||
data_type: boolean
|
||||
required: false
|
||||
default: "false"
|
||||
value_list:
|
||||
- "false"
|
||||
- "true"
|
||||
descr: 'Флаг, определяющий, нужно ли выделять внешний IP для доступа к master-брокеру. Пример: `true` или `false`'
|
||||
- id: 506
|
||||
code: ipSpaceNameMaster
|
||||
data_type: string
|
||||
required: false
|
||||
descr: 'Если включен предыдущий параметр, то поле необходимо заполнить. Пример: `internet-no-antiddos-v1`'
|
||||
- name: modify_topic
|
||||
id: 166
|
||||
kind: subresource
|
||||
action: modify
|
||||
subresource: topic
|
||||
man: '# Инструкция по управлению топиками Apache Kafka через платформу<br/><br/>## 1. Общая информация<br/><br/>---<br/><br/>Топик — это основная сущность в Apache Kafka, предназначенная для приёма сообщений от продюсеров и передачи их консюмерам.<br/>Платформа позволяет создавать, удалять и изменять параметры топиков без прямого доступа к брокерам, обеспечивая удобное управление через интерфейс или API.<br/><br/>---<br/><br/>## Доступные операции<br/><br/>- **create_topic** — Создание топика<br/>- **modify_topic** — Модификация топика<br/>- **delete_topic** — Удаление топика<br/><br/>---<br/><br/>## 2. Параметры создания топика<br/><br/>Ниже перечислены параметры, которые необходимо указать при создании нового топика:<br/><br/>* **Имя топика**<br/> Название топика.<br/> Должно быть уникальным в пределах кластера.<br/> Пример: `orders-events`.<br/><br/>* **Партиции**<br/> Количество партиций топика.<br/> Рекомендуется выбирать с учётом уровня параллелизма потребителей и предполагаемой нагрузки.<br/> Пример: `6`. Здесь важно рассказать о том как происходит расчет оптимального количества<br/> Обратите внимание: уменьшить количество партиций после создания невозможно, поэтому лучше закладывать небольшой запас на будущий рост нагрузки.<br/><br/>* **Реплики**<br/> Количество реплик для каждой партиции.<br/> Обычно равно количеству доступных брокеров или меньше.<br/> Пример: `3`.<br/><br/><br/>---<br/><br/>## 4. Параметры модификации топика<br/><br/>Для уже существующих топиков можно изменять параметры. Все параметры являются опциональными:<br/><br/>* **Имя топика**<br/> Название топика.<br/> Пример: `orders-events`.<br/><br/>* **Партиции**<br/> Новое количество партиций.<br/> Пример: `12`.<br/><br/>* **Реплики**<br/> Новое количество реплик.<br/> Пример: `2`.<br/><br/>---<br/><br/>## 5. Рекомендации<br/><br/>* Планируйте количество партиций с запасом — уменьшение их числа невозможно.<br/>* Количество реплик должно соответствовать требуемому уровню отказоустойчивости, но не превышать количество брокеров.<br/>* Изменение параметров топика в рабочем окружении может временно влиять на производительность. Рекомендуется проводить крупные изменения в периоды минимальной нагрузки.<br/><br/>---'
|
||||
params:
|
||||
- id: 526
|
||||
code: nameTopic
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `orders-events`'
|
||||
sort: 10
|
||||
- id: 527
|
||||
code: partitions
|
||||
required: false
|
||||
descr: Число партиций на кластер ≤ 100 × (число брокеров) × (фактор репликации). Пример `50`
|
||||
sort: 30
|
||||
- id: 528
|
||||
code: replicas
|
||||
required: false
|
||||
descr: Количество партиций ≤ (число брокеров). Пример `2`
|
||||
sort: 20
|
||||
- name: modify_user
|
||||
id: 169
|
||||
kind: subresource
|
||||
action: modify
|
||||
subresource: user
|
||||
man: '# Инструкция по управлению пользователями Apache Kafka.<br/><br/>---<br/><br/>## 1. Общая информация<br/><br/>Пользователи в Apache Kafka используются для аутентификации и авторизации при подключении к кластеру.<br/>Платформа позволяет создавать, удалять и изменять пользователей без прямого доступа к брокерам, а также настраивать права доступа к топикам и группам.<br/>Это обеспечивает централизованное и безопасное управление доступом к ресурсам Kafka.<br/><br/>---<br/><br/>## Доступные операции<br/><br/>- **create_user** — Создание пользователя<br/>- **modify_user** — Модификация пользователя<br/>- **delete_user** — Удаление пользователя<br/><br/>---<br/><br/>## 2. Параметры создания пользователя<br/><br/>Ниже перечислены параметры, которые необходимо указать при создании нового пользователя:<br/><br/>* **Имя пользователя**<br/> Имя создаваемого пользователя. Используется для подключения к Kafka и настройки прав.<br/> Пример: `username-admin`<br/><br/>* **Топик**<br/> Название топика, к которому будут назначены права для данного пользователя.<br/> Пример: `orders-events`.<br/> Использовать маску нельзя (`orders*`), только полное название или `*`.<br/><br/>* **Операции**<br/> Список разрешённых операций на указанном топике.<br/> Пример: `Describe, Read, Write`<br/><br/>* **Группа**<br/> Имя группы, которой разрешён доступ. Используется при настройке консюмер-групп.<br/> Пример: `order-consumers`.<br/><br/>* **Разрешённые хосты**<br/> Список хостов или подсетей, с которых разрешено подключение пользователя.<br/> Пример: `192.168.1.0/24` или `*`.<br/><br/>---<br/><br/>## 3. Параметры модификации пользователя<br/><br/>Ниже перечислены параметры, которые необходимо указать при модификации пользователя:<br/><br/>* **Имя пользователя**<br/> Имя создаваемого пользователя. Используется для подключения к Kafka и настройки прав.<br/> Пример: `username-admin`<br/><br/>* **Топик**<br/> Название топика, к которому будут назначены права для данного пользователя.<br/> Пример: `orders-events`.<br/> Использовать маску нельзя (`orders*`), только полное название или `*`.<br/><br/>* **Операции**<br/> Список разрешённых операций на указанном топике.<br/> Пример: `Describe, Read, Write`<br/><br/>* **Группа**<br/> Имя группы, которой разрешён доступ. Используется при настройке консюмер-групп.<br/> Пример: `order-consumers`.<br/><br/>* **Разрешённые хосты**<br/> Список хостов или подсетей, с которых разрешено подключение пользователя.<br/> Пример: `192.168.1.0/24` или `*`.<br/><br/>---<br/><br/>## 4. Параметры удалении пользователя<br/><br/>Ниже перечислены параметры, которые необходимо указать при удалении пользователя:<br/><br/>* **Имя пользователя**<br/> Имя удаляемого пользователя.<br/> Пример: `username-admin`<br/><br/>## 5. Выходные параметры<br/><br/>После успешного создания пользователя платформа возвращает параметры, необходимые для подключения:<br/><br/>* **ca.crt**<br/> Корневой сертификат (Certificate Authority), используемый для проверки подлинности сервера при TLS-подключении.<br/> Файл содержит публичный ключ удостоверяющего центра, которому доверяют брокеры и клиенты. Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.crt<br/> ```<br/><br/>* **ca.p12**<br/> PKCS#12-хранилище с корневым сертификатом (Certificate Authority), используемое для проверки подлинности сервера при TLS-подключении.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.p12<br/> ```<br/><br/>* **ca.password**<br/> Пароль для доступа к хранилищу `ca.p12`. Выдаеться в чистом виде. Раскодировать не нужно.<br/><br/>* **user.crt**<br/> Клиентский сертификат, подтверждающий личность пользователя при подключении к Kafka.<br/> Используется совместно с приватным ключом `user.key` для аутентификации.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.crt<br/> ```<br/><br/>* **user.key**<br/> Приватный ключ пользователя, применяемый для установления защищённого TLS-соединения и подтверждения подлинности.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo ''<содержание поля>'' | base64 -d > <название>.key<br/> ```<br/><br/>* **user.p12**<br/> PKCS#12-хранилище с клиентским сертификатом и приватным ключом, предназначенное для аутентификации пользователя в Kafka.<br/> Выдаеться в формате base64 для получения файла сертификата использовать команду<br/> ```bash<br/> echo "<содержание поля>" | base64 -d > <название>.p12<br/> ```<br/><br/>* **user.password**<br/> Пароль для доступа к хранилищу `user.p12`. Выдаеться в чистом виде. Раскодировать не нужно.<br/><br/>Эти параметры используются для настройки TLS-подключения.<br/><br/>---<br/><br/>## 6. Пример конфигурации клиента внутри кластера<br/><br/>Пример пода:<br/><br/>```yaml<br/>apiVersion: v1<br/>kind: Pod<br/>metadata:<br/> name: kafka-4<br/>spec:<br/> volumes:<br/> - name: work<br/> emptyDir: {}<br/> containers:<br/> - name: kcat<br/> image: quay.io/strimzi/kafka:latest-kafka-4.0.0<br/> command: ["sleep", "5600"]<br/> volumeMounts:<br/> - name: work<br/> mountPath: /work<br/> restartPolicy: Never<br/>```<br/><br/>Создаём под и сохраняем сертификаты:<br/><br/>```bash<br/>kubectl cp user.p12 kafka-4:/work/<br/>kubectl cp ca.p12 kafka-4:/work/<br/>```<br/><br/>Создаём файлы конфигурации:<br/><br/>```bash<br/>cat > /work/user-read.properties <<''EOF''<br/>security.protocol=SSL<br/>ssl.keystore.type=PKCS12<br/>ssl.keystore.location=/work/user.p12<br/>ssl.keystore.password=<user.password><br/>ssl.truststore.type=PKCS12<br/>ssl.truststore.location=/work/ca.p12<br/>ssl.truststore.password=<ca.password><br/>EOF<br/>```<br/><br/>```bash<br/>cat > /work/user-write.properties <<''EOF''<br/>security.protocol=SSL<br/>ssl.keystore.type=PKCS12<br/>ssl.keystore.location=/work/user-write.p12<br/>ssl.keystore.password=<user.password><br/>ssl.truststore.type=PKCS12<br/>ssl.truststore.location=/work/ca.p12<br/>ssl.truststore.password=<ca.password><br/>EOF<br/>```<br/><br/>Листинг доступных топиков:<br/><br/>```bash<br/>/opt/kafka/bin/kafka-topics.sh \<br/> --bootstrap-server kafkak8s-bootstrap-external:9093 \<br/> --command-config /work/user-read.properties \<br/> --list<br/>```<br/><br/>Запись сообщений:<br/><br/>```bash<br/>/opt/kafka/bin/kafka-console-producer.sh \<br/> --bootstrap-server <адрес сервис брокера>:9093 \<br/> --topic <topic> \<br/> --producer.config /work/user-write.properties<br/>```<br/><br/>Чтение сообщений:<br/><br/>```bash<br/>/opt/kafka/bin/kafka-console-consumer.sh \<br/> --bootstrap-server <адрес сервис брокера>:9093 \<br/> --topic <topic> \<br/> --from-beginning \<br/> --group <group> \<br/> --consumer.config /work/user-read.properties \<br/> --max-messages 50<br/>```<br/><br/>---<br/><br/>## 7. Пример внешнего подключения с использованием **kcat**<br/><br/>**Листинг топиков**<br/><br/>```bash<br/>kcat -b <externalConnect.bootstrap.fqdn>:9093 \<br/> -L \<br/> -X security.protocol=SSL \<br/> -X ssl.ca.location=./ca.crt \<br/> -X ssl.certificate.location=./user-read.crt \<br/> -X ssl.key.location=./user-read.key \<br/> -X ssl.endpoint.identification.algorithm=none<br/>```<br/><br/>**Запись сообщения**<br/><br/>```bash<br/> echo "hello from kcat" | kcat -P \<br/> -b <externalConnect.bootstrap.fqdn>:9093 \<br/> -t write-test \<br/> -X security.protocol=SSL \<br/> -X ssl.ca.location=./ca.crt \<br/> -X ssl.certificate.location=./user-write.crt \<br/> -X ssl.key.location=./user-write.key \<br/> -X ssl.endpoint.identification.algorithm=none<br/>```<br/><br/>**Чтение сообщений**<br/><br/>```bash<br/>kcat -C \<br/> -b <externalConnect.bootstrap.fqdn>:9093 \<br/> -t write-test \<br/> -o beginning \<br/> -q \<br/> -X security.protocol=SSL \<br/> -X ssl.ca.location=./ca.crt \<br/> -X ssl.certificate.location=./user-read.crt \<br/> -X ssl.key.location=./user-read.key \<br/> -X ssl.endpoint.identification.algorithm=none<br/>```<br/><br/>---<br/><br/><br/>## 9. Рекомендации<br/><br/>* Минимизируйте набор разрешённых операций для повышения безопасности.<br/>* Используйте отдельные учётные записи для продюсеров, консюмеров и системных сервисов.<br/>* Ограничивайте доступ по IP или подсетям с помощью параметра **Разрешённые хосты**.<br/>* После изменения прав или групп пользователя рекомендуется проверить подключение и доступ к топикам.<br/><br/>---'
|
||||
params:
|
||||
- id: 535
|
||||
code: nameTopic
|
||||
data_type: string
|
||||
required: false
|
||||
descr: 'Пример: `orders-events`. Использовать маску нельзя(`orders*`), только полное название или `*`'
|
||||
- id: 536
|
||||
code: operations
|
||||
data_type: string
|
||||
required: false
|
||||
descr: 'Пример: `Create, Describe, Read, Write`'
|
||||
- id: 537
|
||||
code: group
|
||||
data_type: string
|
||||
required: false
|
||||
descr: 'Пример: `order-consumers` или `*`'
|
||||
- id: 538
|
||||
code: accessHosts
|
||||
data_type: string
|
||||
required: false
|
||||
descr: 'Пример: `192.168.1.0/24` или `*`'
|
||||
- id: 539
|
||||
code: username
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `username-admin`'
|
||||
sort: 10
|
||||
- name: resume
|
||||
id: 161
|
||||
kind: instance
|
||||
action: resume
|
||||
params: []
|
||||
- name: suspend
|
||||
id: 160
|
||||
kind: instance
|
||||
action: suspend
|
||||
params: []
|
||||
@@ -0,0 +1,81 @@
|
||||
name: nifi
|
||||
service_id: 117
|
||||
service_display_name: Nifi
|
||||
service_short_name: nifi
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: false
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 150
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 471
|
||||
code: kafkaUid
|
||||
data_type: uuid
|
||||
required: true
|
||||
ref_svc_id: 116
|
||||
descr: Выбрать кластер из списка
|
||||
sort: 10
|
||||
- id: 472
|
||||
code: partitions
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
descr: Число партиций на кластер ≤ 100 × (число брокеров) × (фактор репликации). Пример `50`
|
||||
man: 'Обратите внимание: уменьшить количество партиций после создания невозможно, поэтому лучше закладывать небольшой запас на будущий рост нагрузки.'
|
||||
sort: 30
|
||||
is_modifiable: true
|
||||
- id: 473
|
||||
code: replicas
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
descr: Количество партиций ≤ (число брокеров). Пример `2`
|
||||
sort: 40
|
||||
is_modifiable: true
|
||||
- id: 474
|
||||
code: nameTopic
|
||||
data_type: string
|
||||
required: true
|
||||
descr: 'Пример: `orders-events`'
|
||||
sort: 20
|
||||
- name: delete
|
||||
id: 151
|
||||
kind: instance
|
||||
action: delete
|
||||
params: []
|
||||
- name: modify
|
||||
id: 154
|
||||
kind: instance
|
||||
action: modify
|
||||
params:
|
||||
- id: 477
|
||||
code: partitions
|
||||
data_type: integer > 0
|
||||
required: false
|
||||
descr: Число партиций на кластер ≤ 100 × (число брокеров) × (фактор репликации). Пример `50`
|
||||
man: 'Обратите внимание: уменьшить количество партиций после создания невозможно, поэтому лучше закладывать небольшой запас на будущий рост нагрузки.'
|
||||
- id: 478
|
||||
code: replicas
|
||||
data_type: integer > 0
|
||||
required: false
|
||||
descr: Количество партиций ≤ (число брокеров). Пример `2`
|
||||
@@ -0,0 +1,104 @@
|
||||
name: akhq
|
||||
service_id: 119
|
||||
service_display_name: Akhq
|
||||
service_short_name: Akhq
|
||||
service_man: '# Инструкция по развертыванию Apache Kafka через платформу<br/><br/>---<br/>## 1. Общая информация<br/>AKHQ — это веб-интерфейс для работы с кластерами Apache Kafka.<br/>Позволяет просматривать топики, consumer group''ы, сообщения, ACL, схему безопасности, состояние брокеров и метаданные.<br/>Поддерживает работу с несколькими кластерами Kafka одновременно, включая внутренние и внешние (TLS) подключения.<br/><br/>## Доступные операции<br/>- **create** — Создание ресурса AKHQ<br/>- **suspend** — Остановка ресурса<br/>- **resume** — Возобновление работы ресурса<br/>- **modify** — Модификация настроек<br/>- **delete** — Удаление ресурса<br/><br/>---<br/>## 2. Параметры развертывания кластера<br/>Ниже перечислены параметры, которые необходимо указать при создании ресурса Kafka:<br/><br/>* **Кластер Kafka к которому нужно настроить подключение**<br/>Для использования AKHQ необходимо предварительно развернуть сервис Kafka. Пример: `kafka-external`.<br/><br/>* **Платформа для развертывания:**<br/>Платформа для развертывания (среда, в которой будет развернут кластер). Пример: `k8s-3.ext.nubes.ru`.<br/><br/>* **Количество узлов в сети:**<br/>Количество узлов для работы Akhq. Пример `2`<br/><br/>* **Квота (megabyte) памяти пода:**<br/>Квота памяти пода в мегабайтах. Пример: `512`<br/><br/>* **Квота (millicore ) ядра пода:**<br/>Квота CPU пода в миликорах (1000 mCPU = 1 vCPU). Пример: `500`<br/><br/>* **Имя домена:**<br/>Dns Имя по которому будет происходить обращение к сервису. Указать необходимо уникальное имя до . ( то есть если необходимо получить dns ui) Пример: `kafka-external`<br/><br/>---<br/>## 4. Выходные параметры<br/>После успешного развертывания ресурса Kafka платформа возвращает ряд параметров.<br/>Ключевым из них является:<br/><br/>**connectionUrl**<br/>Это полное доменное имя (FQDN), по которому будет доступен интерфейс AKHQ. После развёртывания подождите несколько минут — UI станет доступен по HTTPS, как только все сервисы полностью запустятся.<br/><br/>**Учетные данные**<br/>Здесь представлены поля для ввода логина и пароля, с помощью которых вы сможете авторизоваться в сервисе.<br/><br/>---<br/>## 5. Дополнительная информация<br/>Учётные записи, созданные для работы с AKHQ, предоставляют доступ только для просмотра данных. Для управления Kafka, пожалуйста, используйте возможности личного кабинета.<br/>---'
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: true
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 175
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 550
|
||||
code: resourceRealm
|
||||
data_type: string
|
||||
required: true
|
||||
func: getAvailableResourceRealms
|
||||
descr: Выбрать платформу из списка
|
||||
sort: 10
|
||||
- id: 551
|
||||
code: resourceMemory
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
default: "512"
|
||||
minvalue: 512
|
||||
descr: 'Пример: `1024`'
|
||||
sort: 20
|
||||
- id: 552
|
||||
code: resourceCPU
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
default: "500"
|
||||
minvalue: 500
|
||||
descr: 'Пример: `1000`'
|
||||
sort: 30
|
||||
- id: 553
|
||||
code: domain
|
||||
data_type: string
|
||||
required: true
|
||||
regex: ^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
|
||||
descr: Если будет указан FQDN, то происходят проверки DNS на валидность А-записи, или будет добавлено в зону resourceRealm
|
||||
sort: 40
|
||||
- id: 554
|
||||
code: resourceInstances
|
||||
data_type: integer > 0
|
||||
required: true
|
||||
default: "1"
|
||||
minvalue: 1
|
||||
sort: 11
|
||||
- id: 555
|
||||
code: kafkaUid
|
||||
data_type: uuid
|
||||
required: true
|
||||
ref_svc_id: 116
|
||||
descr: Выбрать кластер из списка
|
||||
sort: 5
|
||||
is_modifiable: true
|
||||
- name: delete
|
||||
id: 176
|
||||
kind: instance
|
||||
action: delete
|
||||
params: []
|
||||
- name: modify
|
||||
id: 180
|
||||
kind: instance
|
||||
action: modify
|
||||
params:
|
||||
- id: 565
|
||||
code: kafkaUid
|
||||
data_type: uuid
|
||||
required: true
|
||||
ref_svc_id: 116
|
||||
descr: Выбрать кластер из списка
|
||||
- name: resume
|
||||
id: 178
|
||||
kind: instance
|
||||
action: resume
|
||||
params: []
|
||||
- name: suspend
|
||||
id: 177
|
||||
kind: instance
|
||||
action: suspend
|
||||
params: []
|
||||
@@ -0,0 +1,211 @@
|
||||
name: clickhouse
|
||||
service_id: 120
|
||||
service_display_name: ClickHouse
|
||||
service_short_name: clickhouse
|
||||
service_man: '# Инструкция по управлению кластерами и пользователями ClickHouse<br/><br/>---<br/><br/>## Общая информация<br/><br/>ClickHouse — это колоночная система управления базами данных, предназначенная для обработки аналитических запросов в реальном времени. Платформа позволяет создавать, удалять, приостанавливать и возобновлять кластеры ClickHouse, а также управлять базами данных и пользователями.<br/><br/>Кластер создаётся через оператор Altinity (`https://github.com/Altinity/clickhouse-operator`) в ресурсной платформе типа `k8s`<br/><br/>Кластеры могут создаваться шардированными с произвольным количеством реплик. Для обеспечения отказоустойчивости и координации обязательно включение параметра `Создать ClickHouse-Keeper`.<br/><br/>Кластера создаются с обязательным бекапом. Для этого необходимо предварительно создать услугу `S3 Object Storage`.<br/>---<br/><br/>## Доступные операции<br/><br/>* **create** — Создание кластера<br/>* **delete** — Удаление кластера<br/>* **suspend** — Приостановка работы кластера<br/>* **resume** — Возобновление работы кластера<br/>* **create_database** — Создание базы данных<br/>* **delete_database** — Удаление базы данных<br/>* **create_user** — Создание пользователя и настройка прав доступа к базе данных<br/>* **delete_user** — Удаление пользователя<br/><br/>## Процесс создания и первого подключения<br/><br/>1. Выполнить операцию `create` - будет создан кластер<br/>2. Выполнить операцию `create_database` - будет создана база данных<br/>3. Выполнить операцию `create_user` - будет создан пользователь (выбрать доступ подключения к БД, созданной в п.2)<br/><br/>## Примеры подключения<br/><br/>### Подключение через HTTP-интерфейс<br/><br/>```bash<br/># Проверка доступности кластера<br/>curl "http://<username>:<password>@<host>:8123/" -d "SELECT 1"<br/><br/># Выполнение запроса<br/>curl "http://<username>:<password>@<host>:8123/" -d "SHOW DATABASES"<br/>```<br/><br/>## Простые кейсы<br/>*Создать БД и обычной таблицы*<br/>```clickhouse<br/>CREATE TABLE database.table<br/>(<br/> id UInt64,<br/> value String<br/>)<br/>ENGINE = MergeTree()<br/>ORDER BY id;<br/><br/>INSERT INTO database.table SELECT number, toString(number) FROM numbers(10000000);<br/><br/>SELECT count() FROM database.table;<br/>```<br/>Базы и таблицы не реплицируются без специального ключа (`ON CLUSTER ''clickhousek8s''`)<br/>В случае создания в стиле, описанным выше, необходимо будет выполнять действия на всех шардах/репликах<br/><br/><br/>*Создание БД и шардированной таблицы*<br/>```clickhouse<br/>CREATE TABLE database.localUsers<br/>(<br/> id UInt64,<br/> value String<br/>)<br/>ENGINE = MergeTree()<br/>ORDER BY id;<br/><br/>CREATE TABLE database.shardedTable<br/>AS localUsers<br/>ENGINE = Distributed(''clickhousek8s'', ''database'', ''localUsers'', id);<br/><br/>INSERT INTO shardedTable SELECT number, toString(number) FROM numbers(10000000);<br/><br/>SELECT count() FROM shardedTable;<br/>```<br/>Важный момент - в данном примере таблицы придётся также выполнять на всех нодах ClickHouse<br/><br/>*Создание реплицируемой и шардированной таблицы*<br/>```clickhouse<br/>CREATE TABLE IF NOT EXISTS database.localUsers ON cluster ''clickhousek8s''<br/>(<br/> user_id UInt64,<br/> name String,<br/> email String<br/>)<br/>ENGINE = ReplicatedMergeTree(<br/> ''/clickhouse/tables/{shard}/localUsers'',<br/> ''{replica}''<br/>)<br/>ORDER BY user_id;<br/><br/>CREATE TABLE IF NOT EXISTS database.users ON cluster ''clickhousek8s''<br/>AS database.localUsers<br/>ENGINE = Distributed(''clickhousek8s'', ''database'', ''localUsers'', rand());<br/><br/>INSERT INTO database.users VALUES (1, ''Alice'', ''alice@example.com'');<br/>INSERT INTO database.users VALUES (2, ''Bob'', ''bob@example.com'');<br/>INSERT INTO database.users VALUES (3, ''Charlie'', ''charlie@example.com'');<br/>INSERT INTO database.users VALUES (4, ''David'', ''david@example.com'');<br/>INSERT INTO database.users VALUES (5, ''Gosha'', ''gosha@example.com'');<br/>INSERT INTO database.users VALUES (6, ''Eve'', ''eve@example.com'');<br/>INSERT INTO database.users VALUES (7, ''Frank'', ''frank@example.com'');<br/>INSERT INTO database.users VALUES (8, ''Grace'', ''grace@example.com'');<br/>INSERT INTO database.users VALUES (9, ''Hannah'', ''hannah@example.com'');<br/>INSERT INTO database.users VALUES (10, ''Ivan'', ''ivan@example.com'');<br/><br/>SELECT * FROM database.localUsers;<br/>SELECT * FROM database.users;<br/><br/>SELECT COUNT(*) AS total_users FROM database.users;<br/>SELECT COUNT(*) AS total_users_local FROM database.localUsers;<br/>```'
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: true
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 181
|
||||
kind: instance
|
||||
action: create
|
||||
man: Операция создания кластера ClickHouse<br/><br/>Для подключения к кластеру также необходимо создать базу данных через операцию `create_database` и создание пользователя через операцию `create_user`
|
||||
params:
|
||||
- id: 837
|
||||
code: startupConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 10
|
||||
- id: 838
|
||||
code: clusterConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 20
|
||||
is_modifiable: true
|
||||
- id: 839
|
||||
code: accessConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 40
|
||||
is_modifiable: true
|
||||
- id: 840
|
||||
code: clickhouseKeeperConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 30
|
||||
is_modifiable: true
|
||||
- id: 841
|
||||
code: clickhouseConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 50
|
||||
is_modifiable: true
|
||||
- id: 842
|
||||
code: backupConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 70
|
||||
is_modifiable: true
|
||||
- id: 843
|
||||
code: autoscaleConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 80
|
||||
is_modifiable: true
|
||||
- id: 851
|
||||
code: clickhouseFilesConfiguration
|
||||
data_type: array-map-fixed
|
||||
required: false
|
||||
man: 'Формат:<br/> - path: somethings3.xml<br/> xmlConfig: ''<clickhouse>......</clickhouse>'''
|
||||
sort: 60
|
||||
is_modifiable: true
|
||||
- name: create_database
|
||||
id: 186
|
||||
kind: subresource
|
||||
action: create
|
||||
subresource: database
|
||||
man: База данных создаётся на всех репликах кластера ClickHouse
|
||||
params:
|
||||
- id: 600
|
||||
code: dbName
|
||||
data_type: string
|
||||
required: true
|
||||
default: db01
|
||||
regex: ^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$
|
||||
descr: 'Пример: `db01`'
|
||||
sort: 10
|
||||
- name: create_user
|
||||
id: 185
|
||||
kind: subresource
|
||||
action: create
|
||||
subresource: user
|
||||
params:
|
||||
- id: 596
|
||||
code: username
|
||||
data_type: string
|
||||
required: true
|
||||
default: myusername
|
||||
regex: ^(?![0-9_])[a-zA-Z_][a-zA-Z0-9_]{0,62}$
|
||||
maxlength: 62
|
||||
minlength: 2
|
||||
descr: 'Пример: `myusername`'
|
||||
sort: 10
|
||||
- id: 598
|
||||
code: dbName
|
||||
data_type: array-map-fixed
|
||||
required: true
|
||||
default: '[{}]'
|
||||
sort: 30
|
||||
- id: 599
|
||||
code: accessHosts
|
||||
data_type: string
|
||||
required: true
|
||||
default: ::/0
|
||||
descr: 'Пример: `::/0`'
|
||||
sort: 40
|
||||
- name: delete
|
||||
id: 184
|
||||
kind: instance
|
||||
action: delete
|
||||
man: Удаление возможно только после остановки сервиса через операцию `suspend`<br/>После выполнения операции необходимо выждать определенное кол-во времени
|
||||
params: []
|
||||
- name: delete_database
|
||||
id: 188
|
||||
kind: subresource
|
||||
action: delete
|
||||
subresource: database
|
||||
man: Необратимое действие!!!<br/>Трижды подумай, заблокируй телефон тимлида и не выполняй это действие
|
||||
params:
|
||||
- id: 602
|
||||
code: dbName
|
||||
data_type: string
|
||||
required: true
|
||||
descr: ВСЕ ДАННЫЕ ВНУТРИ БД БУДУТ УДАЛЕНЫ
|
||||
- name: delete_user
|
||||
id: 187
|
||||
kind: subresource
|
||||
action: delete
|
||||
subresource: user
|
||||
params:
|
||||
- id: 601
|
||||
code: username
|
||||
required: true
|
||||
descr: Имя пользователя, созданное через операцию `create_user`
|
||||
- name: modify
|
||||
id: 189
|
||||
kind: instance
|
||||
action: modify
|
||||
params:
|
||||
- id: 844
|
||||
code: clusterConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 10
|
||||
- id: 845
|
||||
code: clickhouseKeeperConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 20
|
||||
- id: 846
|
||||
code: accessConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 30
|
||||
- id: 847
|
||||
code: clickhouseConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 40
|
||||
- id: 848
|
||||
code: backupConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 60
|
||||
- id: 849
|
||||
code: autoscaleConfiguration
|
||||
data_type: map-fixed
|
||||
required: true
|
||||
sort: 70
|
||||
- id: 850
|
||||
code: clickhouseFilesConfiguration
|
||||
data_type: array-map-fixed
|
||||
required: false
|
||||
man: 'Формат:<br/> - path: somethings3.xml<br/> xmlConfig: ''<clickhouse>......</clickhouse>'''
|
||||
sort: 50
|
||||
- name: reconcile
|
||||
id: 276
|
||||
kind: action
|
||||
action: reconcile
|
||||
params: []
|
||||
- name: resume
|
||||
id: 183
|
||||
kind: instance
|
||||
action: resume
|
||||
params: []
|
||||
- name: suspend
|
||||
id: 182
|
||||
kind: instance
|
||||
action: suspend
|
||||
man: Операция приводит к удалению всех подов<br/>Persistent Volume остаются нетронутыми
|
||||
params: []
|
||||
@@ -0,0 +1,127 @@
|
||||
name: s3
|
||||
service_id: 12
|
||||
service_display_name: S3 Object Storage
|
||||
service_short_name: s3
|
||||
service_man: Руководство доступно по ссылке:<br/>https://docs.s3.msk-1.ngcloud.ru/s3/s3.description.html
|
||||
lifecycle:
|
||||
suspend_on_destroy_default: true
|
||||
adopt_existing_on_create_default: false
|
||||
outputs:
|
||||
params:
|
||||
- code: state_params
|
||||
type: map
|
||||
- code: state_out
|
||||
type: map
|
||||
- code: state_params_flat
|
||||
type: map
|
||||
- code: state_out_flat
|
||||
type: map
|
||||
- code: vault_secrets
|
||||
type: map
|
||||
sensitive: true
|
||||
- code: vault_url
|
||||
type: string
|
||||
- code: vault_user_path
|
||||
type: string
|
||||
- code: vault_fields
|
||||
type: list
|
||||
operations:
|
||||
- name: create
|
||||
id: 1
|
||||
kind: instance
|
||||
action: create
|
||||
params:
|
||||
- id: 163
|
||||
code: resourceRealm
|
||||
required: true
|
||||
func: getAvailableResourceRealms
|
||||
descr: Платформа для развертывания
|
||||
man: кластер S3 для подключения
|
||||
sort: 20
|
||||
- id: 320
|
||||
code: displayName
|
||||
data_type: string
|
||||
required: true
|
||||
regex: ^[a-zA-Z0-9_]{1,61}[a-zA-Z0-9]$
|
||||
descr: Человеко-читаемое имя (транслит из Elma) - передаётся имя клиента
|
||||
sort: 10
|
||||
- name: create_sub_user
|
||||
id: 6
|
||||
kind: subresource
|
||||
action: create
|
||||
subresource: sub_user
|
||||
params:
|
||||
- id: 60
|
||||
code: subUserName
|
||||
data_type: string
|
||||
required: true
|
||||
- id: 61
|
||||
code: subUserAccess
|
||||
data_type: string
|
||||
required: true
|
||||
value_list:
|
||||
- read
|
||||
- write
|
||||
- readwrite
|
||||
- full
|
||||
- name: delete
|
||||
id: 5
|
||||
kind: instance
|
||||
action: delete
|
||||
params: []
|
||||
- name: delete_sub_user
|
||||
id: 7
|
||||
kind: subresource
|
||||
action: delete
|
||||
subresource: sub_user
|
||||
params:
|
||||
- id: 63
|
||||
code: subUserName
|
||||
data_type: string
|
||||
required: true
|
||||
descr: subUserName. По идее услуга уже создана - должен передаваться из ЛК
|
||||
- name: modify
|
||||
id: 4
|
||||
kind: instance
|
||||
action: modify
|
||||
man: При первичном вызове операции услуга Trial прекращает действие
|
||||
params:
|
||||
- id: 50
|
||||
code: maxSizeGbPerUser
|
||||
data_type: integer > 0
|
||||
required: false
|
||||
maxvalue: 50000
|
||||
minvalue: 1
|
||||
descr: maxSizeGb. Передаём руками
|
||||
man: maxSizeGb. Передаём руками
|
||||
- id: 51
|
||||
code: maxObjectsPerBucket
|
||||
data_type: integer > 0
|
||||
required: false
|
||||
maxvalue: 1e+07
|
||||
minvalue: 1
|
||||
descr: Квота на бакет. Кол-во объектов. Передаём руками
|
||||
man: Квота на бакет. Кол-во объектов. Передаём руками
|
||||
- id: 52
|
||||
code: maxBucketsPerUser
|
||||
data_type: integer > 0
|
||||
required: false
|
||||
maxvalue: 1000
|
||||
minvalue: 1
|
||||
descr: Квота на пользователя. Количество бакетов пользователя. Передаём руками
|
||||
man: Квота на пользователя. Количество бакетов пользователя. Передаём руками
|
||||
- name: reconcile
|
||||
id: 407
|
||||
kind: action
|
||||
action: reconcile
|
||||
params: []
|
||||
- name: resume
|
||||
id: 3
|
||||
kind: instance
|
||||
action: resume
|
||||
params: []
|
||||
- name: suspend
|
||||
id: 2
|
||||
kind: instance
|
||||
action: suspend
|
||||
params: []
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user