fix: refactor sqlserver to handle encryption correctly (#31)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 6s
Publish / Publish provider (push) Successful in 6m59s

## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Reviewed-on: #31
Reviewed-by: Andre_Harms <andre.harms@stackit.cloud>
This commit is contained in:
Marcel_Henselin 2026-02-04 07:30:00 +00:00
parent 80d1d12278
commit 4549ba63e5
Signed by: tf-provider.git.onstackit.cloud
GPG key ID: 6D7E8A1ED8955A9C
23 changed files with 4203 additions and 1413 deletions

View file

@ -7,21 +7,17 @@ import (
"fmt"
"net/http"
"github.com/hashicorp/terraform-plugin-framework/types"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
sqlserverflexalpha "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance/datasources_gen"
sqlserverflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance/resources_gen"
sqlserverflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/utils"
sqlserverflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
// Ensure the implementation satisfies the expected interfaces.
@ -62,165 +58,167 @@ func (r *instanceDataSource) Configure(ctx context.Context, req datasource.Confi
}
// Schema defines the schema for the data source.
func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{
"main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the SQLServer Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"access_scope": "The access scope of the instance. (e.g. SNA)",
"acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
"backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")`,
"region": "The resource region. If not defined, the provider region is used.",
"encryption": "The encryption block.",
"network": "The network block.",
"keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.",
"key_id": "STACKIT KMS - Key ID of the encryption key to use.",
"key_version": "STACKIT KMS - Key version to use in the encryption key.",
"service:account": "STACKIT KMS - service account to use in the encryption key.",
"instance_address": "The returned instance IP address of the SQLServer Flex instance.",
"router_address": "The returned router IP address of the SQLServer Flex instance.",
}
func (r *instanceDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
//descriptions := map[string]string{
// "main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.",
// "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
// "instance_id": "ID of the SQLServer Flex instance.",
// "project_id": "STACKIT project ID to which the instance is associated.",
// "name": "Instance name.",
// "access_scope": "The access scope of the instance. (e.g. SNA)",
// "acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
// "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")`,
// "region": "The resource region. If not defined, the provider region is used.",
// "encryption": "The encryption block.",
// "network": "The network block.",
// "keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.",
// "key_id": "STACKIT KMS - Key ID of the encryption key to use.",
// "key_version": "STACKIT KMS - Key version to use in the encryption key.",
// "service:account": "STACKIT KMS - service account to use in the encryption key.",
// "instance_address": "The returned instance IP address of the SQLServer Flex instance.",
// "router_address": "The returned router IP address of the SQLServer Flex instance.",
//}
resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
},
"instance_id": schema.StringAttribute{
Description: descriptions["instance_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Computed: true,
},
"backup_schedule": schema.StringAttribute{
Description: descriptions["backup_schedule"],
Computed: true,
},
"is_deletable": schema.BoolAttribute{
Description: descriptions["is_deletable"],
Computed: true,
},
"flavor": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"description": schema.StringAttribute{
Computed: true,
},
"cpu": schema.Int64Attribute{
Computed: true,
},
"ram": schema.Int64Attribute{
Computed: true,
},
"node_type": schema.StringAttribute{
Computed: true,
},
},
},
"replicas": schema.Int64Attribute{
Computed: true,
},
"storage": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
Computed: true,
},
"size": schema.Int64Attribute{
Computed: true,
},
},
},
"version": schema.StringAttribute{
Computed: true,
},
"status": schema.StringAttribute{
Computed: true,
},
"edition": schema.StringAttribute{
Computed: true,
},
"retention_days": schema.Int64Attribute{
Computed: true,
},
"region": schema.StringAttribute{
// the region cannot be found, so it has to be passed
Optional: true,
Description: descriptions["region"],
},
"encryption": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"key_id": schema.StringAttribute{
Description: descriptions["key_id"],
Computed: true,
},
"key_version": schema.StringAttribute{
Description: descriptions["key_version"],
Computed: true,
},
"keyring_id": schema.StringAttribute{
Description: descriptions["keyring_id"],
Computed: true,
},
"service_account": schema.StringAttribute{
Description: descriptions["service_account"],
Computed: true,
},
},
Description: descriptions["encryption"],
},
"network": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"access_scope": schema.StringAttribute{
Description: descriptions["access_scope"],
Computed: true,
},
"instance_address": schema.StringAttribute{
Description: descriptions["instance_address"],
Computed: true,
},
"router_address": schema.StringAttribute{
Description: descriptions["router_address"],
Computed: true,
},
"acl": schema.ListAttribute{
Description: descriptions["acl"],
ElementType: types.StringType,
Computed: true,
},
},
Description: descriptions["network"],
},
},
}
resp.Schema = sqlserverflexalpha.InstanceDataSourceSchema(ctx)
//resp.Schema = schema.Schema{
// Description: descriptions["main"],
// Attributes: map[string]schema.Attribute{
// "id": schema.StringAttribute{
// Description: descriptions["id"],
// Computed: true,
// },
// "instance_id": schema.StringAttribute{
// Description: descriptions["instance_id"],
// Required: true,
// Validators: []validator.String{
// validate.UUID(),
// validate.NoSeparator(),
// },
// },
// "project_id": schema.StringAttribute{
// Description: descriptions["project_id"],
// Required: true,
// Validators: []validator.String{
// validate.UUID(),
// validate.NoSeparator(),
// },
// },
// "name": schema.StringAttribute{
// Description: descriptions["name"],
// Computed: true,
// },
// "backup_schedule": schema.StringAttribute{
// Description: descriptions["backup_schedule"],
// Computed: true,
// },
// "is_deletable": schema.BoolAttribute{
// Description: descriptions["is_deletable"],
// Computed: true,
// },
// "flavor": schema.SingleNestedAttribute{
// Computed: true,
// Attributes: map[string]schema.Attribute{
// "id": schema.StringAttribute{
// Computed: true,
// },
// "description": schema.StringAttribute{
// Computed: true,
// },
// "cpu": schema.Int64Attribute{
// Computed: true,
// },
// "ram": schema.Int64Attribute{
// Computed: true,
// },
// "node_type": schema.StringAttribute{
// Computed: true,
// },
// },
// },
// "replicas": schema.Int64Attribute{
// Computed: true,
// },
// "storage": schema.SingleNestedAttribute{
// Computed: true,
// Attributes: map[string]schema.Attribute{
// "class": schema.StringAttribute{
// Computed: true,
// },
// "size": schema.Int64Attribute{
// Computed: true,
// },
// },
// },
// "version": schema.StringAttribute{
// Computed: true,
// },
// "status": schema.StringAttribute{
// Computed: true,
// },
// "edition": schema.StringAttribute{
// Computed: true,
// },
// "retention_days": schema.Int64Attribute{
// Computed: true,
// },
// "region": schema.StringAttribute{
// // the region cannot be found, so it has to be passed
// Optional: true,
// Description: descriptions["region"],
// },
// "encryption": schema.SingleNestedAttribute{
// Computed: true,
// Attributes: map[string]schema.Attribute{
// "key_id": schema.StringAttribute{
// Description: descriptions["key_id"],
// Computed: true,
// },
// "key_version": schema.StringAttribute{
// Description: descriptions["key_version"],
// Computed: true,
// },
// "keyring_id": schema.StringAttribute{
// Description: descriptions["keyring_id"],
// Computed: true,
// },
// "service_account": schema.StringAttribute{
// Description: descriptions["service_account"],
// Computed: true,
// },
// },
// Description: descriptions["encryption"],
// },
// "network": schema.SingleNestedAttribute{
// Computed: true,
// Attributes: map[string]schema.Attribute{
// "access_scope": schema.StringAttribute{
// Description: descriptions["access_scope"],
// Computed: true,
// },
// "instance_address": schema.StringAttribute{
// Description: descriptions["instance_address"],
// Computed: true,
// },
// "router_address": schema.StringAttribute{
// Description: descriptions["router_address"],
// Computed: true,
// },
// "acl": schema.ListAttribute{
// Description: descriptions["acl"],
// ElementType: types.StringType,
// Computed: true,
// },
// },
// Description: descriptions["network"],
// },
// },
//}
}
// Read refreshes the Terraform state with the latest data.
func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@ -253,34 +251,35 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
ctx = core.LogResponse(ctx)
var storage = &storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
//var storage = &storageModel{}
//if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
// diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
// resp.Diagnostics.Append(diags...)
// if resp.Diagnostics.HasError() {
// return
// }
//}
//
//var encryption = &encryptionModel{}
//if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
// diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
// resp.Diagnostics.Append(diags...)
// if resp.Diagnostics.HasError() {
// return
// }
//}
//
//var network = &networkModel{}
//if !model.Network.IsNull() && !model.Network.IsUnknown() {
// diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
// resp.Diagnostics.Append(diags...)
// if resp.Diagnostics.HasError() {
// return
// }
//}
var encryption = &encryptionModel{}
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var network = &networkModel{}
if !model.Network.IsNull() && !model.Network.IsUnknown() {
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
err = mapResponseToModel(ctx, instanceResp, &model, resp.Diagnostics)
//err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return

View file

@ -6,202 +6,299 @@ import (
"math"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
sqlserverflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
sqlserverflexResGen "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance/resources_gen"
)
func mapFields(
func mapResponseToModel(
ctx context.Context,
resp *sqlserverflex.GetInstanceResponse,
model *Model,
storage *storageModel,
encryption *encryptionModel,
network *networkModel,
region string,
m *sqlserverflexResGen.InstanceModel,
tfDiags diag.Diagnostics,
) error {
if resp == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
instance := resp
var instanceId string
if model.InstanceId.ValueString() != "" {
instanceId = model.InstanceId.ValueString()
} else if instance.Id != nil {
instanceId = *instance.Id
} else {
return fmt.Errorf("instance id not present")
}
var storageValues map[string]attr.Value
if instance.Storage == nil {
storageValues = map[string]attr.Value{
"class": storage.Class,
"size": storage.Size,
}
} else {
storageValues = map[string]attr.Value{
"class": types.StringValue(*instance.Storage.Class),
"size": types.Int64PointerValue(instance.Storage.Size),
}
}
storageObject, diags := types.ObjectValue(storageTypes, storageValues)
m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
m.Edition = types.StringValue(string(resp.GetEdition()))
m.Encryption = handleEncryption(m, resp)
m.FlavorId = types.StringValue(resp.GetFlavorId())
m.Id = types.StringValue(resp.GetId())
m.InstanceId = types.StringValue(resp.GetId())
m.IsDeletable = types.BoolValue(resp.GetIsDeletable())
m.Name = types.StringValue(resp.GetName())
netAcl, diags := types.ListValueFrom(ctx, types.StringType, resp.Network.GetAcl())
tfDiags.Append(diags...)
if diags.HasError() {
return fmt.Errorf("creating storage: %w", core.DiagsToError(diags))
return fmt.Errorf(
"error converting network acl response value",
)
}
var encryptionValues map[string]attr.Value
if instance.Encryption == nil {
encryptionValues = map[string]attr.Value{
"keyring_id": encryption.KeyRingId,
"key_id": encryption.KeyId,
"key_version": encryption.KeyVersion,
"service_account": encryption.ServiceAccount,
}
} else {
encryptionValues = map[string]attr.Value{
"keyring_id": types.StringValue(*instance.Encryption.KekKeyRingId),
"key_id": types.StringValue(*instance.Encryption.KekKeyId),
"key_version": types.StringValue(*instance.Encryption.KekKeyVersion),
"service_account": types.StringValue(*instance.Encryption.ServiceAccount),
}
}
encryptionObject, diags := types.ObjectValue(encryptionTypes, encryptionValues)
net, diags := sqlserverflexResGen.NewNetworkValue(
sqlserverflexResGen.NetworkValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"access_scope": types.StringValue(string(resp.Network.GetAccessScope())),
"acl": netAcl,
"instance_address": types.StringValue(resp.Network.GetInstanceAddress()),
"router_address": types.StringValue(resp.Network.GetRouterAddress()),
},
)
tfDiags.Append(diags...)
if diags.HasError() {
return fmt.Errorf("creating encryption: %w", core.DiagsToError(diags))
return fmt.Errorf(
"error converting network response value",
"access_scope",
types.StringValue(string(resp.Network.GetAccessScope())),
"acl",
netAcl,
"instance_address",
types.StringValue(resp.Network.GetInstanceAddress()),
"router_address",
types.StringValue(resp.Network.GetRouterAddress()),
)
}
m.Network = net
m.Replicas = types.Int64Value(int64(resp.GetReplicas()))
m.RetentionDays = types.Int64Value(resp.GetRetentionDays())
m.Status = types.StringValue(string(resp.GetStatus()))
var networkValues map[string]attr.Value
if instance.Network == nil {
networkValues = map[string]attr.Value{
"acl": network.ACL,
"access_scope": network.AccessScope,
"instance_address": network.InstanceAddress,
"router_address": network.RouterAddress,
}
} else {
aclList, diags := types.ListValueFrom(ctx, types.StringType, *instance.Network.Acl)
if diags.HasError() {
return fmt.Errorf("creating network (acl list): %w", core.DiagsToError(diags))
}
var routerAddress string
if instance.Network.RouterAddress != nil {
routerAddress = *instance.Network.RouterAddress
diags.AddWarning("field missing while mapping fields", "router_address was empty in API response")
}
if instance.Network.InstanceAddress == nil {
return fmt.Errorf("creating network: no instance address returned")
}
networkValues = map[string]attr.Value{
"acl": aclList,
"access_scope": types.StringValue(string(*instance.Network.AccessScope)),
"instance_address": types.StringValue(*instance.Network.InstanceAddress),
"router_address": types.StringValue(routerAddress),
}
}
networkObject, diags := types.ObjectValue(networkTypes, networkValues)
stor, diags := sqlserverflexResGen.NewStorageValue(
sqlserverflexResGen.StorageValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"class": types.StringValue(resp.Storage.GetClass()),
"size": types.Int64Value(resp.Storage.GetSize()),
},
)
tfDiags.Append(diags...)
if diags.HasError() {
return fmt.Errorf("creating network: %w", core.DiagsToError(diags))
return fmt.Errorf("error converting storage response value")
}
m.Storage = stor
simplifiedModelBackupSchedule := utils.SimplifyBackupSchedule(model.BackupSchedule.ValueString())
// If the value returned by the API is different from the one in the model after simplification,
// we update the model so that it causes an error in Terraform
if simplifiedModelBackupSchedule != types.StringPointerValue(instance.BackupSchedule).ValueString() {
model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule)
}
if instance.Replicas == nil {
return fmt.Errorf("instance has no replicas set")
}
if instance.RetentionDays == nil {
return fmt.Errorf("instance has no retention days set")
}
if instance.Version == nil {
return fmt.Errorf("instance has no version set")
}
if instance.Edition == nil {
return fmt.Errorf("instance has no edition set")
}
if instance.Status == nil {
return fmt.Errorf("instance has no status set")
}
if instance.IsDeletable == nil {
return fmt.Errorf("instance has no IsDeletable set")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
model.InstanceId = types.StringValue(instanceId)
model.Name = types.StringPointerValue(instance.Name)
model.FlavorId = types.StringPointerValue(instance.FlavorId)
model.Replicas = types.Int64Value(int64(*instance.Replicas))
model.Storage = storageObject
model.Version = types.StringValue(string(*instance.Version))
model.Edition = types.StringValue(string(*instance.Edition))
model.Region = types.StringValue(region)
model.Encryption = encryptionObject
model.Network = networkObject
model.RetentionDays = types.Int64Value(*instance.RetentionDays)
model.Status = types.StringValue(string(*instance.Status))
model.IsDeletable = types.BoolValue(*instance.IsDeletable)
m.Version = types.StringValue(string(resp.GetVersion()))
return nil
}
func handleEncryption(
m *sqlserverflexResGen.InstanceModel,
resp *sqlserverflex.GetInstanceResponse,
) sqlserverflexResGen.EncryptionValue {
if !resp.HasEncryption() ||
resp.Encryption == nil ||
resp.Encryption.KekKeyId == nil ||
resp.Encryption.KekKeyRingId == nil ||
resp.Encryption.KekKeyVersion == nil ||
resp.Encryption.ServiceAccount == nil {
if m.Encryption.IsNull() || m.Encryption.IsUnknown() {
return sqlserverflexResGen.NewEncryptionValueNull()
}
return m.Encryption
}
enc := sqlserverflexResGen.NewEncryptionValueNull()
if kVal, ok := resp.Encryption.GetKekKeyIdOk(); ok {
enc.KekKeyId = types.StringValue(kVal)
}
if kkVal, ok := resp.Encryption.GetKekKeyRingIdOk(); ok {
enc.KekKeyRingId = types.StringValue(kkVal)
}
if kkvVal, ok := resp.Encryption.GetKekKeyVersionOk(); ok {
enc.KekKeyVersion = types.StringValue(kkvVal)
}
if sa, ok := resp.Encryption.GetServiceAccountOk(); ok {
enc.ServiceAccount = types.StringValue(sa)
}
return enc
}
//func mapFields(
// ctx context.Context,
// resp *sqlserverflex.GetInstanceResponse,
// model *Model,
// storage *storageModel,
// encryption *encryptionModel,
// network *networkModel,
// region string,
//) error {
// if resp == nil {
// return fmt.Errorf("response input is nil")
// }
// if model == nil {
// return fmt.Errorf("model input is nil")
// }
// instance := resp
//
// var instanceId string
// if model.InstanceId.ValueString() != "" {
// instanceId = model.InstanceId.ValueString()
// } else if instance.Id != nil {
// instanceId = *instance.Id
// } else {
// return fmt.Errorf("instance id not present")
// }
//
// var storageValues map[string]attr.Value
// if instance.Storage == nil {
// storageValues = map[string]attr.Value{
// "class": storage.Class,
// "size": storage.Size,
// }
// } else {
// storageValues = map[string]attr.Value{
// "class": types.StringValue(*instance.Storage.Class),
// "size": types.Int64PointerValue(instance.Storage.Size),
// }
// }
// storageObject, diags := types.ObjectValue(storageTypes, storageValues)
// if diags.HasError() {
// return fmt.Errorf("creating storage: %w", core.DiagsToError(diags))
// }
//
// var encryptionValues map[string]attr.Value
// if instance.Encryption == nil {
// encryptionValues = map[string]attr.Value{
// "keyring_id": encryption.KeyRingId,
// "key_id": encryption.KeyId,
// "key_version": encryption.KeyVersion,
// "service_account": encryption.ServiceAccount,
// }
// } else {
// encryptionValues = map[string]attr.Value{
// "keyring_id": types.StringValue(*instance.Encryption.KekKeyRingId),
// "key_id": types.StringValue(*instance.Encryption.KekKeyId),
// "key_version": types.StringValue(*instance.Encryption.KekKeyVersion),
// "service_account": types.StringValue(*instance.Encryption.ServiceAccount),
// }
// }
// encryptionObject, diags := types.ObjectValue(encryptionTypes, encryptionValues)
// if diags.HasError() {
// return fmt.Errorf("creating encryption: %w", core.DiagsToError(diags))
// }
//
// var networkValues map[string]attr.Value
// if instance.Network == nil {
// networkValues = map[string]attr.Value{
// "acl": network.ACL,
// "access_scope": network.AccessScope,
// "instance_address": network.InstanceAddress,
// "router_address": network.RouterAddress,
// }
// } else {
// aclList, diags := types.ListValueFrom(ctx, types.StringType, *instance.Network.Acl)
// if diags.HasError() {
// return fmt.Errorf("creating network (acl list): %w", core.DiagsToError(diags))
// }
//
// var routerAddress string
// if instance.Network.RouterAddress != nil {
// routerAddress = *instance.Network.RouterAddress
// diags.AddWarning("field missing while mapping fields", "router_address was empty in API response")
// }
// if instance.Network.InstanceAddress == nil {
// return fmt.Errorf("creating network: no instance address returned")
// }
// networkValues = map[string]attr.Value{
// "acl": aclList,
// "access_scope": types.StringValue(string(*instance.Network.AccessScope)),
// "instance_address": types.StringValue(*instance.Network.InstanceAddress),
// "router_address": types.StringValue(routerAddress),
// }
// }
// networkObject, diags := types.ObjectValue(networkTypes, networkValues)
// if diags.HasError() {
// return fmt.Errorf("creating network: %w", core.DiagsToError(diags))
// }
//
// simplifiedModelBackupSchedule := utils.SimplifyBackupSchedule(model.BackupSchedule.ValueString())
// // If the value returned by the API is different from the one in the model after simplification,
// // we update the model so that it causes an error in Terraform
// if simplifiedModelBackupSchedule != types.StringPointerValue(instance.BackupSchedule).ValueString() {
// model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule)
// }
//
// if instance.Replicas == nil {
// return fmt.Errorf("instance has no replicas set")
// }
//
// if instance.RetentionDays == nil {
// return fmt.Errorf("instance has no retention days set")
// }
//
// if instance.Version == nil {
// return fmt.Errorf("instance has no version set")
// }
//
// if instance.Edition == nil {
// return fmt.Errorf("instance has no edition set")
// }
//
// if instance.Status == nil {
// return fmt.Errorf("instance has no status set")
// }
//
// if instance.IsDeletable == nil {
// return fmt.Errorf("instance has no IsDeletable set")
// }
//
// model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
// model.InstanceId = types.StringValue(instanceId)
// model.Name = types.StringPointerValue(instance.Name)
// model.FlavorId = types.StringPointerValue(instance.FlavorId)
// model.Replicas = types.Int64Value(int64(*instance.Replicas))
// model.Storage = storageObject
// model.Version = types.StringValue(string(*instance.Version))
// model.Edition = types.StringValue(string(*instance.Edition))
// model.Region = types.StringValue(region)
// model.Encryption = encryptionObject
// model.Network = networkObject
// model.RetentionDays = types.Int64Value(*instance.RetentionDays)
// model.Status = types.StringValue(string(*instance.Status))
// model.IsDeletable = types.BoolValue(*instance.IsDeletable)
// return nil
//}
func toCreatePayload(
model *Model,
storage *storageModel,
encryption *encryptionModel,
network *networkModel,
ctx context.Context,
model *sqlserverflexResGen.InstanceModel,
) (*sqlserverflex.CreateInstanceRequestPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
storagePayload := &sqlserverflex.CreateInstanceRequestPayloadGetStorageArgType{}
if storage != nil {
storagePayload.Class = conversion.StringValueToPointer(storage.Class)
storagePayload.Size = conversion.Int64ValueToPointer(storage.Size)
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
storagePayload.Class = model.Storage.Class.ValueStringPointer()
storagePayload.Size = model.Storage.Size.ValueInt64Pointer()
}
var encryptionPayload *sqlserverflex.CreateInstanceRequestPayloadGetEncryptionArgType
if encryption != nil &&
!encryption.KeyId.IsNull() && !encryption.KeyId.IsUnknown() &&
!encryption.KeyRingId.IsNull() && !encryption.KeyRingId.IsUnknown() &&
!encryption.KeyVersion.IsNull() && !encryption.KeyVersion.IsUnknown() &&
!encryption.ServiceAccount.IsNull() && !encryption.ServiceAccount.IsUnknown() {
var encryptionPayload *sqlserverflex.CreateInstanceRequestPayloadGetEncryptionArgType = nil
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() &&
!model.Encryption.KekKeyId.IsNull() && model.Encryption.KekKeyId.IsUnknown() && model.Encryption.KekKeyId.ValueString() != "" &&
!model.Encryption.KekKeyRingId.IsNull() && !model.Encryption.KekKeyRingId.IsUnknown() && model.Encryption.KekKeyRingId.ValueString() != "" &&
!model.Encryption.KekKeyVersion.IsNull() && !model.Encryption.KekKeyVersion.IsUnknown() && model.Encryption.KekKeyVersion.ValueString() != "" &&
!model.Encryption.ServiceAccount.IsNull() && !model.Encryption.ServiceAccount.IsUnknown() && model.Encryption.ServiceAccount.ValueString() != "" {
encryptionPayload = &sqlserverflex.CreateInstanceRequestPayloadGetEncryptionArgType{
KekKeyId: conversion.StringValueToPointer(encryption.KeyId),
KekKeyRingId: conversion.StringValueToPointer(encryption.KeyVersion),
KekKeyVersion: conversion.StringValueToPointer(encryption.KeyRingId),
ServiceAccount: conversion.StringValueToPointer(encryption.ServiceAccount),
}
}
var aclElements []string
if network != nil && !network.ACL.IsNull() && !network.ACL.IsUnknown() {
aclElements = make([]string, 0, len(network.ACL.Elements()))
diags := network.ACL.ElementsAs(context.TODO(), &aclElements, false)
if diags.HasError() {
return nil, fmt.Errorf("creating network: %w", core.DiagsToError(diags))
KekKeyId: model.Encryption.KekKeyId.ValueStringPointer(),
KekKeyRingId: model.Encryption.KekKeyVersion.ValueStringPointer(),
KekKeyVersion: model.Encryption.KekKeyRingId.ValueStringPointer(),
ServiceAccount: model.Encryption.ServiceAccount.ValueStringPointer(),
}
}
networkPayload := &sqlserverflex.CreateInstanceRequestPayloadGetNetworkArgType{}
if network != nil {
networkPayload.AccessScope = sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope))
networkPayload.Acl = &aclElements
if !model.Network.IsNull() && !model.Network.IsUnknown() {
networkPayload.AccessScope = sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(
model.Network.AccessScope.ValueStringPointer(),
)
var resList []string
diags := model.Network.Acl.ElementsAs(ctx, &resList, false)
if diags.HasError() {
return nil, fmt.Errorf("error converting network acl list")
}
networkPayload.Acl = &resList
}
return &sqlserverflex.CreateInstanceRequestPayload{
@ -216,66 +313,80 @@ func toCreatePayload(
}, nil
}
//nolint:unused // TODO: remove if not needed later
func toUpdatePartiallyPayload(
model *Model,
storage *storageModel,
network *networkModel,
) (*sqlserverflex.UpdateInstancePartiallyRequestPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
storagePayload := &sqlserverflex.UpdateInstanceRequestPayloadGetStorageArgType{}
if storage != nil {
storagePayload.Size = conversion.Int64ValueToPointer(storage.Size)
}
var aclElements []string
if network != nil && !network.ACL.IsNull() && !network.ACL.IsUnknown() {
aclElements = make([]string, 0, len(network.ACL.Elements()))
diags := network.ACL.ElementsAs(context.TODO(), &aclElements, false)
if diags.HasError() {
return nil, fmt.Errorf("creating network: %w", core.DiagsToError(diags))
}
}
networkPayload := &sqlserverflex.UpdateInstancePartiallyRequestPayloadGetNetworkArgType{}
if network != nil {
networkPayload.AccessScope = sqlserverflex.UpdateInstancePartiallyRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope))
networkPayload.Acl = &aclElements
}
if model.Replicas.ValueInt64() > math.MaxInt32 {
return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64())
}
replCount := int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above
return &sqlserverflex.UpdateInstancePartiallyRequestPayload{
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
FlavorId: conversion.StringValueToPointer(model.FlavorId),
Name: conversion.StringValueToPointer(model.Name),
Network: networkPayload,
Replicas: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetReplicasAttributeType(&replCount),
RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays),
Storage: storagePayload,
Version: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetVersionAttributeType(conversion.StringValueToPointer(model.Version)),
}, nil
}
////nolint:unused // TODO: remove if not needed later
//func toUpdatePartiallyPayload(
// model *Model,
// storage *storageModel,
// network *networkModel,
//) (*sqlserverflex.UpdateInstancePartiallyRequestPayload, error) {
// if model == nil {
// return nil, fmt.Errorf("nil model")
// }
//
// storagePayload := &sqlserverflex.UpdateInstanceRequestPayloadGetStorageArgType{}
// if storage != nil {
// storagePayload.Size = conversion.Int64ValueToPointer(storage.Size)
// }
//
// var aclElements []string
// if network != nil && !network.ACL.IsNull() && !network.ACL.IsUnknown() {
// aclElements = make([]string, 0, len(network.ACL.Elements()))
// diags := network.ACL.ElementsAs(context.TODO(), &aclElements, false)
// if diags.HasError() {
// return nil, fmt.Errorf("creating network: %w", core.DiagsToError(diags))
// }
// }
//
// networkPayload := &sqlserverflex.UpdateInstancePartiallyRequestPayloadGetNetworkArgType{}
// if network != nil {
// networkPayload.AccessScope = sqlserverflex.UpdateInstancePartiallyRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope))
// networkPayload.Acl = &aclElements
// }
//
// if model.Replicas.ValueInt64() > math.MaxInt32 {
// return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64())
// }
// replCount := int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above
// return &sqlserverflex.UpdateInstancePartiallyRequestPayload{
// BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
// FlavorId: conversion.StringValueToPointer(model.FlavorId),
// Name: conversion.StringValueToPointer(model.Name),
// Network: networkPayload,
// Replicas: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetReplicasAttributeType(&replCount),
// RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays),
// Storage: storagePayload,
// Version: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetVersionAttributeType(conversion.StringValueToPointer(model.Version)),
// }, nil
//}
// TODO: check func with his args
func toUpdatePayload(
_ *Model,
_ *storageModel,
_ *networkModel,
ctx context.Context,
m *sqlserverflexResGen.InstanceModel,
resp *resource.UpdateResponse,
) (*sqlserverflex.UpdateInstanceRequestPayload, error) {
if m.Replicas.ValueInt64() > math.MaxUint32 {
return nil, fmt.Errorf("replicas value is too big for uint32")
}
replVal := sqlserverflex.Replicas(uint32(m.Replicas.ValueInt64()))
var netAcl []string
diags := m.Network.Acl.ElementsAs(ctx, &netAcl, false)
resp.Diagnostics.Append(diags...)
if diags.HasError() {
return nil, fmt.Errorf("error converting model network acl value")
}
return &sqlserverflex.UpdateInstanceRequestPayload{
BackupSchedule: nil,
FlavorId: nil,
Name: nil,
Network: nil,
Replicas: nil,
RetentionDays: nil,
Storage: nil,
Version: nil,
BackupSchedule: m.BackupSchedule.ValueStringPointer(),
FlavorId: m.FlavorId.ValueStringPointer(),
Name: m.Name.ValueStringPointer(),
Network: &sqlserverflex.CreateInstanceRequestPayloadNetwork{
AccessScope: sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(m.Network.AccessScope.ValueStringPointer()),
Acl: &netAcl,
},
Replicas: &replVal,
RetentionDays: m.RetentionDays.ValueInt64Pointer(),
Storage: &sqlserverflex.StorageUpdate{Size: m.Storage.Size.ValueInt64Pointer()},
Version: sqlserverflex.UpdateInstanceRequestPayloadGetVersionAttributeType(m.Version.ValueStringPointer()),
}, nil
}

View file

@ -21,7 +21,6 @@ fields:
- name: 'name'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'backup_schedule'
modifiers:
@ -31,24 +30,28 @@ fields:
validators:
- validate.NoSeparator
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'encryption.kek_key_version'
validators:
- validate.NoSeparator
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'encryption.kek_key_ring_id'
validators:
- validate.NoSeparator
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'encryption.service_account'
validators:
- validate.NoSeparator
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'network.access_scope'
@ -76,6 +79,7 @@ fields:
- name: 'region'
modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'retention_days'

View file

@ -4,35 +4,25 @@ package sqlserverflex
import (
"context"
_ "embed"
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
sqlserverflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance/resources_gen"
sqlserverflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
"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"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha"
wait "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/sqlserverflexalpha"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
@ -44,6 +34,7 @@ var (
_ resource.ResourceWithConfigure = &instanceResource{}
_ resource.ResourceWithImportState = &instanceResource{}
_ resource.ResourceWithModifyPlan = &instanceResource{}
_ resource.ResourceWithIdentity = &instanceResource{}
)
//nolint:unused // TODO: remove if not needed later
@ -52,63 +43,10 @@ var validNodeTypes []string = []string{
"Replica",
}
type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
InstanceId types.String `tfsdk:"instance_id"`
ProjectId types.String `tfsdk:"project_id"`
Name types.String `tfsdk:"name"`
BackupSchedule types.String `tfsdk:"backup_schedule"`
FlavorId types.String `tfsdk:"flavor_id"`
Encryption types.Object `tfsdk:"encryption"`
IsDeletable types.Bool `tfsdk:"is_deletable"`
Storage types.Object `tfsdk:"storage"`
Status types.String `tfsdk:"status"`
Version types.String `tfsdk:"version"`
Replicas types.Int64 `tfsdk:"replicas"`
Region types.String `tfsdk:"region"`
Network types.Object `tfsdk:"network"`
Edition types.String `tfsdk:"edition"`
RetentionDays types.Int64 `tfsdk:"retention_days"`
}
type encryptionModel struct {
KeyRingId types.String `tfsdk:"keyring_id"`
KeyId types.String `tfsdk:"key_id"`
KeyVersion types.String `tfsdk:"key_version"`
ServiceAccount types.String `tfsdk:"service_account"`
}
var encryptionTypes = map[string]attr.Type{
"keyring_id": basetypes.StringType{},
"key_id": basetypes.StringType{},
"key_version": basetypes.StringType{},
"service_account": basetypes.StringType{},
}
type networkModel struct {
ACL types.List `tfsdk:"acl"`
AccessScope types.String `tfsdk:"access_scope"`
InstanceAddress types.String `tfsdk:"instance_address"`
RouterAddress types.String `tfsdk:"router_address"`
}
var networkTypes = map[string]attr.Type{
"acl": basetypes.ListType{ElemType: types.StringType},
"access_scope": basetypes.StringType{},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}
// Struct corresponding to Model.Storage
type storageModel struct {
Class types.String `tfsdk:"class"`
Size types.Int64 `tfsdk:"size"`
}
// Types corresponding to storageModel
var storageTypes = map[string]attr.Type{
"class": basetypes.StringType{},
"size": basetypes.Int64Type{},
type InstanceResourceIdentityModel struct {
ProjectID types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
InstanceID types.String `tfsdk:"instance_id"`
}
// NewInstanceResource is a helper function to simplify the provider implementation.
@ -154,17 +92,21 @@ func (r *instanceResource) ModifyPlan(
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
var configModel Model
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
}
var configModel sqlserverflexalpha2.InstanceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
if resp.Diagnostics.HasError() {
return
}
var planModel Model
if req.Plan.Raw.IsNull() {
return
}
var planModel sqlserverflexalpha2.InstanceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() {
return
@ -175,257 +117,303 @@ func (r *instanceResource) ModifyPlan(
return
}
var identityModel InstanceResourceIdentityModel
identityModel.ProjectID = planModel.ProjectId
identityModel.Region = planModel.Region
if !planModel.InstanceId.IsNull() && !planModel.InstanceId.IsUnknown() {
identityModel.InstanceID = planModel.InstanceId
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identityModel)...)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
}
}
//go:embed planModifiers.yaml
var modifiersFileByte []byte
// Schema defines the schema for the resource.
func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{
"main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the SQLServer Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"access_scope": "The access scope of the instance. (SNA | PUBLIC)",
"flavor_id": "The flavor ID of the instance.",
"acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
"backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")`,
"region": "The resource region. If not defined, the provider region is used.",
"encryption": "The encryption block.",
"replicas": "The number of replicas of the SQLServer Flex instance.",
"network": "The network block.",
"keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.",
"key_id": "STACKIT KMS - Key ID of the encryption key to use.",
"key_version": "STACKIT KMS - Key version to use in the encryption key.",
"service:account": "STACKIT KMS - service account to use in the encryption key.",
"instance_address": "The returned instance IP address of the SQLServer Flex instance.",
"router_address": "The returned router IP address of the SQLServer Flex instance.",
func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
//descriptions := map[string]string{
// "main": "SQLServer Flex ALPHA instance resource schema. Must have a `region` specified in the provider configuration.",
// "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
// "instance_id": "ID of the SQLServer Flex instance.",
// "project_id": "STACKIT project ID to which the instance is associated.",
// "name": "Instance name.",
// "access_scope": "The access scope of the instance. (SNA | PUBLIC)",
// "flavor_id": "The flavor ID of the instance.",
// "acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
// "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")`,
// "region": "The resource region. If not defined, the provider region is used.",
// "encryption": "The encryption block.",
// "replicas": "The number of replicas of the SQLServer Flex instance.",
// "network": "The network block.",
// "keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.",
// "key_id": "STACKIT KMS - Key ID of the encryption key to use.",
// "key_version": "STACKIT KMS - Key version to use in the encryption key.",
// "service:account": "STACKIT KMS - service account to use in the encryption key.",
// "instance_address": "The returned instance IP address of the SQLServer Flex instance.",
// "router_address": "The returned router IP address of the SQLServer Flex instance.",
//}
schema := sqlserverflexalpha2.InstanceResourceSchema(ctx)
fields, err := postgresflexUtils.ReadModifiersConfig(modifiersFileByte)
if err != nil {
resp.Diagnostics.AddError("error during read modifiers config file", err.Error())
return
}
resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
err = postgresflexUtils.AddPlanModifiersToResourceSchema(fields, &schema)
if err != nil {
resp.Diagnostics.AddError("error adding plan modifiers", err.Error())
return
}
resp.Schema = schema
//resp.Schema = schema.Schema{
// Description: descriptions["main"],
// Attributes: map[string]schema.Attribute{
// "id": schema.StringAttribute{
// Description: descriptions["id"],
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "instance_id": schema.StringAttribute{
// Description: descriptions["instance_id"],
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// Validators: []validator.String{
// validate.UUID(),
// validate.NoSeparator(),
// },
// },
// "project_id": schema.StringAttribute{
// Description: descriptions["project_id"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.UUID(),
// validate.NoSeparator(),
// },
// },
// "name": schema.StringAttribute{
// Description: descriptions["name"],
// Required: true,
// Validators: []validator.String{
// stringvalidator.LengthAtLeast(1),
// stringvalidator.RegexMatches(
// regexp.MustCompile("^[a-z]([-a-z0-9]*[a-z0-9])?$"),
// "must start with a letter, must have lower case letters, numbers or hyphens, and no hyphen at the end",
// ),
// },
// },
// "backup_schedule": schema.StringAttribute{
// Description: descriptions["backup_schedule"],
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "is_deletable": schema.BoolAttribute{
// Description: descriptions["is_deletable"],
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.Bool{
// boolplanmodifier.UseStateForUnknown(),
// },
// },
// "flavor_id": schema.StringAttribute{
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// Required: true,
// },
// "replicas": schema.Int64Attribute{
// Computed: true,
// PlanModifiers: []planmodifier.Int64{
// int64planmodifier.UseStateForUnknown(),
// },
// },
// "storage": schema.SingleNestedAttribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.Object{
// objectplanmodifier.UseStateForUnknown(),
// },
// Attributes: map[string]schema.Attribute{
// "class": schema.StringAttribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "size": schema.Int64Attribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.Int64{
// int64planmodifier.UseStateForUnknown(),
// },
// },
// },
// },
// "version": schema.StringAttribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "edition": schema.StringAttribute{
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "retention_days": schema.Int64Attribute{
// Optional: true,
// Computed: true,
// PlanModifiers: []planmodifier.Int64{
// int64planmodifier.UseStateForUnknown(),
// },
// },
// "region": schema.StringAttribute{
// Optional: true,
// // must be computed to allow for storing the override value from the provider
// Computed: true,
// Description: descriptions["region"],
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// },
// "status": schema.StringAttribute{
// Optional: true,
// // must be computed to allow for storing the override value from the provider
// Computed: true,
// Description: descriptions["status"],
// },
// "encryption": schema.SingleNestedAttribute{
// Optional: true,
// PlanModifiers: []planmodifier.Object{
// objectplanmodifier.RequiresReplace(),
// objectplanmodifier.UseStateForUnknown(),
// },
// Attributes: map[string]schema.Attribute{
// "key_id": schema.StringAttribute{
// Description: descriptions["key_id"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// "key_version": schema.StringAttribute{
// Description: descriptions["key_version"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// "keyring_id": schema.StringAttribute{
// Description: descriptions["keyring_id"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// "service_account": schema.StringAttribute{
// Description: descriptions["service_account"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// },
// Description: descriptions["encryption"],
// },
// "network": schema.SingleNestedAttribute{
// Required: true,
// Attributes: map[string]schema.Attribute{
// "access_scope": schema.StringAttribute{
// Description: descriptions["access_scope"],
// Required: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
// stringplanmodifier.UseStateForUnknown(),
// },
// Validators: []validator.String{
// validate.NoSeparator(),
// },
// },
// "acl": schema.ListAttribute{
// Description: descriptions["acl"],
// ElementType: types.StringType,
// Required: true,
// PlanModifiers: []planmodifier.List{
// listplanmodifier.UseStateForUnknown(),
// },
// },
// "instance_address": schema.StringAttribute{
// Description: descriptions["instance_address"],
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// "router_address": schema.StringAttribute{
// Description: descriptions["router_address"],
// Computed: true,
// PlanModifiers: []planmodifier.String{
// stringplanmodifier.UseStateForUnknown(),
// },
// },
// },
// Description: descriptions["network"],
// },
// },
//}
}
func (r *instanceResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
resp.IdentitySchema = identityschema.Schema{
Attributes: map[string]identityschema.Attribute{
"project_id": identityschema.StringAttribute{
RequiredForImport: true, // must be set during import by the practitioner
},
"instance_id": schema.StringAttribute{
Description: descriptions["instance_id"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
"region": identityschema.StringAttribute{
RequiredForImport: true, // can be defaulted by the provider configuration
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.RegexMatches(
regexp.MustCompile("^[a-z]([-a-z0-9]*[a-z0-9])?$"),
"must start with a letter, must have lower case letters, numbers or hyphens, and no hyphen at the end",
),
},
},
"backup_schedule": schema.StringAttribute{
Description: descriptions["backup_schedule"],
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"is_deletable": schema.BoolAttribute{
Description: descriptions["is_deletable"],
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
},
"flavor_id": schema.StringAttribute{
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Required: true,
},
"replicas": schema.Int64Attribute{
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"storage": schema.SingleNestedAttribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Object{
objectplanmodifier.UseStateForUnknown(),
},
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
},
"size": schema.Int64Attribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
},
},
"version": schema.StringAttribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
},
"edition": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
},
"retention_days": schema.Int64Attribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: descriptions["region"],
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"status": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: descriptions["status"],
},
"encryption": schema.SingleNestedAttribute{
Optional: true,
PlanModifiers: []planmodifier.Object{
objectplanmodifier.RequiresReplace(),
objectplanmodifier.UseStateForUnknown(),
},
Attributes: map[string]schema.Attribute{
"key_id": schema.StringAttribute{
Description: descriptions["key_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"key_version": schema.StringAttribute{
Description: descriptions["key_version"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"keyring_id": schema.StringAttribute{
Description: descriptions["keyring_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"service_account": schema.StringAttribute{
Description: descriptions["service_account"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
},
Description: descriptions["encryption"],
},
"network": schema.SingleNestedAttribute{
Required: true,
Attributes: map[string]schema.Attribute{
"access_scope": schema.StringAttribute{
Description: descriptions["access_scope"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"acl": schema.ListAttribute{
Description: descriptions["acl"],
ElementType: types.StringType,
Required: true,
PlanModifiers: []planmodifier.List{
listplanmodifier.UseStateForUnknown(),
},
},
"instance_address": schema.StringAttribute{
Description: descriptions["instance_address"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"router_address": schema.StringAttribute{
Description: descriptions["router_address"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
Description: descriptions["network"],
"instance_id": identityschema.StringAttribute{
RequiredForImport: true, // can be defaulted by the provider configuration
},
},
}
@ -437,49 +425,29 @@ func (r *instanceResource) Create(
req resource.CreateRequest,
resp *resource.CreateResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read identity data
var identityData InstanceResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
region := model.Region.ValueString()
projectId := identityData.ProjectID.ValueString()
region := identityData.Region.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
var storage = &storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var encryption = &encryptionModel{}
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var network = &networkModel{}
if !model.Network.IsNull() && !model.Network.IsUnknown() {
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Generate API request body from model
payload, err := toCreatePayload(&model, storage, encryption, network)
payload, err := toCreatePayload(ctx, &model)
if err != nil {
core.LogAndAddError(
ctx,
@ -503,6 +471,18 @@ func (r *instanceResource) Create(
ctx = core.LogResponse(ctx)
instanceId := *createResp.Id
// Set data returned by API in identity
identity := InstanceResourceIdentityModel{
ProjectID: types.StringValue(projectId),
Region: types.StringValue(region),
InstanceID: types.StringValue(instanceId),
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
if resp.Diagnostics.HasError() {
return
}
utils.SetAndLogStateFields(
ctx, &resp.Diagnostics, &resp.State, map[string]any{
"id": utils.BuildInternalTerraformId(projectId, region, instanceId),
@ -521,7 +501,11 @@ func (r *instanceResource) Create(
projectId,
instanceId,
region,
).SetSleepBeforeWait(30 * time.Second).WaitWithContext(ctx)
).SetSleepBeforeWait(
30 * time.Second,
).SetTimeout(
90 * time.Minute,
).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(
ctx,
@ -543,7 +527,8 @@ func (r *instanceResource) Create(
}
// Map response body to schema
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
// err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
err = mapResponseToModel(ctx, waitResp, &model, resp.Diagnostics)
if err != nil {
core.LogAndAddError(
ctx,
@ -560,11 +545,6 @@ func (r *instanceResource) Create(
return
}
// After the instance creation, database might not be ready to accept connections immediately.
// That is why we add a sleep
// TODO - can get removed?
time.Sleep(120 * time.Second)
tflog.Info(ctx, "SQLServer Flex instance created")
}
@ -574,13 +554,20 @@ func (r *instanceResource) Read(
req resource.ReadRequest,
resp *resource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read identity data
var identityData InstanceResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@ -591,33 +578,6 @@ func (r *instanceResource) Read(
ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region)
var storage = &storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var encryption = &encryptionModel{}
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var network = &networkModel{}
if !model.Network.IsNull() && !model.Network.IsUnknown() {
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
instanceResp, err := r.client.GetInstanceRequest(ctx, projectId, region, instanceId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
@ -632,7 +592,8 @@ func (r *instanceResource) Read(
ctx = core.LogResponse(ctx)
// Map response body to schema
err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
// err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
err = mapResponseToModel(ctx, instanceResp, &model, resp.Diagnostics)
if err != nil {
core.LogAndAddError(
ctx,
@ -648,6 +609,17 @@ func (r *instanceResource) Read(
if resp.Diagnostics.HasError() {
return
}
// Set data returned by API in identity
identity := InstanceResourceIdentityModel{
ProjectID: types.StringValue(projectId),
Region: types.StringValue(region),
InstanceID: types.StringValue(instanceId),
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "SQLServer Flex instance read")
}
@ -658,7 +630,7 @@ func (r *instanceResource) Update(
resp *resource.UpdateResponse,
) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@ -675,35 +647,8 @@ func (r *instanceResource) Update(
ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region)
var storage = &storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var encryption = &encryptionModel{}
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var network = &networkModel{}
if !model.Network.IsNull() && !model.Network.IsUnknown() {
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Generate API request body from model
payload, err := toUpdatePayload(&model, storage, network)
payload, err := toUpdatePayload(ctx, &model, resp)
if err != nil {
core.LogAndAddError(
ctx,
@ -739,7 +684,8 @@ func (r *instanceResource) Update(
}
// Map response body to schema
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
err = mapResponseToModel(ctx, waitResp, &model, resp.Diagnostics)
// err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
if err != nil {
core.LogAndAddError(
ctx,
@ -764,7 +710,7 @@ func (r *instanceResource) Delete(
resp *resource.DeleteResponse,
) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from state
var model Model
var model sqlserverflexalpha2.InstanceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {

View file

@ -1,280 +0,0 @@
package sqlserverflex
import (
"context"
"reflect"
"testing"
"github.com/hashicorp/terraform-plugin-framework/resource"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
sqlserverflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha"
)
func TestNewInstanceResource(t *testing.T) {
tests := []struct {
name string
want resource.Resource
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewInstanceResource(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewInstanceResource() = %v, want %v", got, tt.want)
}
})
}
}
func Test_instanceResource_Configure(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.ConfigureRequest
resp *resource.ConfigureResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Configure(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Create(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.CreateRequest
resp *resource.CreateResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Create(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Delete(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.DeleteRequest
resp *resource.DeleteResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Delete(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_ImportState(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.ImportStateRequest
resp *resource.ImportStateResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.ImportState(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Metadata(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
in0 context.Context
req resource.MetadataRequest
resp *resource.MetadataResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Metadata(tt.args.in0, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_ModifyPlan(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.ModifyPlanRequest
resp *resource.ModifyPlanResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.ModifyPlan(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Read(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.ReadRequest
resp *resource.ReadResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Read(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}
func Test_instanceResource_Schema(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
in0 context.Context
in1 resource.SchemaRequest
resp *resource.SchemaResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Schema(tt.args.in0, tt.args.in1, tt.args.resp)
})
}
}
func Test_instanceResource_Update(t *testing.T) {
type fields struct {
client *sqlserverflex.APIClient
providerData core.ProviderData
}
type args struct {
ctx context.Context
req resource.UpdateRequest
resp *resource.UpdateResponse
}
tests := []struct {
name string
fields fields
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &instanceResource{
client: tt.fields.client,
providerData: tt.fields.providerData,
}
r.Update(tt.args.ctx, tt.args.req, tt.args.resp)
})
}
}

View file

@ -26,6 +26,11 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
Description: "The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.",
MarkdownDescription: "The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.",
},
"edition": schema.StringAttribute{
Computed: true,
Description: "Edition of the MSSQL server instance",
MarkdownDescription: "Edition of the MSSQL server instance",
},
"encryption": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"kek_key_id": schema.StringAttribute{
@ -73,6 +78,11 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
Description: "The ID of the instance.",
MarkdownDescription: "The ID of the instance.",
},
"is_deletable": schema.BoolAttribute{
Computed: true,
Description: "Whether the instance can be deleted or not.",
MarkdownDescription: "Whether the instance can be deleted or not.",
},
"name": schema.StringAttribute{
Required: true,
Description: "The name of the instance.",
@ -99,6 +109,12 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
Description: "List of IPV4 cidr.",
MarkdownDescription: "List of IPV4 cidr.",
},
"instance_address": schema.StringAttribute{
Computed: true,
},
"router_address": schema.StringAttribute{
Computed: true,
},
},
CustomType: NetworkType{
ObjectType: types.ObjectType{
@ -126,11 +142,19 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
),
},
},
"replicas": schema.Int64Attribute{
Computed: true,
Description: "How many replicas the instance should have.",
MarkdownDescription: "How many replicas the instance should have.",
},
"retention_days": schema.Int64Attribute{
Required: true,
Description: "The days for how long the backup files should be stored before cleaned up. 30 to 365",
MarkdownDescription: "The days for how long the backup files should be stored before cleaned up. 30 to 365",
},
"status": schema.StringAttribute{
Computed: true,
},
"storage": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
@ -169,15 +193,19 @@ func InstanceResourceSchema(ctx context.Context) schema.Schema {
type InstanceModel struct {
BackupSchedule types.String `tfsdk:"backup_schedule"`
Edition types.String `tfsdk:"edition"`
Encryption EncryptionValue `tfsdk:"encryption"`
FlavorId types.String `tfsdk:"flavor_id"`
Id types.String `tfsdk:"id"`
InstanceId types.String `tfsdk:"instance_id"`
IsDeletable types.Bool `tfsdk:"is_deletable"`
Name types.String `tfsdk:"name"`
Network NetworkValue `tfsdk:"network"`
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
Replicas types.Int64 `tfsdk:"replicas"`
RetentionDays types.Int64 `tfsdk:"retention_days"`
Status types.String `tfsdk:"status"`
Storage StorageValue `tfsdk:"storage"`
Version types.String `tfsdk:"version"`
}
@ -732,14 +760,52 @@ func (t NetworkType) ValueFromObject(ctx context.Context, in basetypes.ObjectVal
fmt.Sprintf(`acl expected to be basetypes.ListValue, was: %T`, aclAttribute))
}
instanceAddressAttribute, ok := attributes["instance_address"]
if !ok {
diags.AddError(
"Attribute Missing",
`instance_address is missing from object`)
return nil, diags
}
instanceAddressVal, ok := instanceAddressAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`instance_address expected to be basetypes.StringValue, was: %T`, instanceAddressAttribute))
}
routerAddressAttribute, ok := attributes["router_address"]
if !ok {
diags.AddError(
"Attribute Missing",
`router_address is missing from object`)
return nil, diags
}
routerAddressVal, ok := routerAddressAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`router_address expected to be basetypes.StringValue, was: %T`, routerAddressAttribute))
}
if diags.HasError() {
return nil, diags
}
return NetworkValue{
AccessScope: accessScopeVal,
Acl: aclVal,
state: attr.ValueStateKnown,
AccessScope: accessScopeVal,
Acl: aclVal,
InstanceAddress: instanceAddressVal,
RouterAddress: routerAddressVal,
state: attr.ValueStateKnown,
}, diags
}
@ -842,14 +908,52 @@ func NewNetworkValue(attributeTypes map[string]attr.Type, attributes map[string]
fmt.Sprintf(`acl expected to be basetypes.ListValue, was: %T`, aclAttribute))
}
instanceAddressAttribute, ok := attributes["instance_address"]
if !ok {
diags.AddError(
"Attribute Missing",
`instance_address is missing from object`)
return NewNetworkValueUnknown(), diags
}
instanceAddressVal, ok := instanceAddressAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`instance_address expected to be basetypes.StringValue, was: %T`, instanceAddressAttribute))
}
routerAddressAttribute, ok := attributes["router_address"]
if !ok {
diags.AddError(
"Attribute Missing",
`router_address is missing from object`)
return NewNetworkValueUnknown(), diags
}
routerAddressVal, ok := routerAddressAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`router_address expected to be basetypes.StringValue, was: %T`, routerAddressAttribute))
}
if diags.HasError() {
return NewNetworkValueUnknown(), diags
}
return NetworkValue{
AccessScope: accessScopeVal,
Acl: aclVal,
state: attr.ValueStateKnown,
AccessScope: accessScopeVal,
Acl: aclVal,
InstanceAddress: instanceAddressVal,
RouterAddress: routerAddressVal,
state: attr.ValueStateKnown,
}, diags
}
@ -921,13 +1025,15 @@ func (t NetworkType) ValueType(ctx context.Context) attr.Value {
var _ basetypes.ObjectValuable = NetworkValue{}
type NetworkValue struct {
AccessScope basetypes.StringValue `tfsdk:"access_scope"`
Acl basetypes.ListValue `tfsdk:"acl"`
state attr.ValueState
AccessScope basetypes.StringValue `tfsdk:"access_scope"`
Acl basetypes.ListValue `tfsdk:"acl"`
InstanceAddress basetypes.StringValue `tfsdk:"instance_address"`
RouterAddress basetypes.StringValue `tfsdk:"router_address"`
state attr.ValueState
}
func (v NetworkValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 2)
attrTypes := make(map[string]tftypes.Type, 4)
var val tftypes.Value
var err error
@ -936,12 +1042,14 @@ func (v NetworkValue) ToTerraformValue(ctx context.Context) (tftypes.Value, erro
attrTypes["acl"] = basetypes.ListType{
ElemType: types.StringType,
}.TerraformType(ctx)
attrTypes["instance_address"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["router_address"] = basetypes.StringType{}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 2)
vals := make(map[string]tftypes.Value, 4)
val, err = v.AccessScope.ToTerraformValue(ctx)
@ -959,6 +1067,22 @@ func (v NetworkValue) ToTerraformValue(ctx context.Context) (tftypes.Value, erro
vals["acl"] = val
val, err = v.InstanceAddress.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["instance_address"] = val
val, err = v.RouterAddress.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["router_address"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
@ -1006,6 +1130,8 @@ func (v NetworkValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue,
"acl": basetypes.ListType{
ElemType: types.StringType,
},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}), diags
}
@ -1014,6 +1140,8 @@ func (v NetworkValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue,
"acl": basetypes.ListType{
ElemType: types.StringType,
},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}
if v.IsNull() {
@ -1027,8 +1155,10 @@ func (v NetworkValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue,
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"access_scope": v.AccessScope,
"acl": aclVal,
"access_scope": v.AccessScope,
"acl": aclVal,
"instance_address": v.InstanceAddress,
"router_address": v.RouterAddress,
})
return objVal, diags
@ -1057,6 +1187,14 @@ func (v NetworkValue) Equal(o attr.Value) bool {
return false
}
if !v.InstanceAddress.Equal(other.InstanceAddress) {
return false
}
if !v.RouterAddress.Equal(other.RouterAddress) {
return false
}
return true
}
@ -1074,6 +1212,8 @@ func (v NetworkValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
"acl": basetypes.ListType{
ElemType: types.StringType,
},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}
}

View file

@ -44,17 +44,24 @@ func CreateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface
}
switch strings.ToLower(string(*s.Status)) {
case strings.ToLower(InstanceStateSuccess):
if s.Network.InstanceAddress == nil {
tflog.Info(ctx, "Waiting for instance_address")
return false, nil, nil
}
if s.Network.RouterAddress == nil {
tflog.Info(ctx, "Waiting for router_address")
return false, nil, nil
if *s.Network.AccessScope == "SNA" {
if s.Network.InstanceAddress == nil {
tflog.Info(ctx, "Waiting for instance_address")
return false, nil, nil
}
if s.Network.RouterAddress == nil {
tflog.Info(ctx, "Waiting for router_address")
return false, nil, nil
}
}
return true, s, nil
case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed):
return true, s, fmt.Errorf("create failed for instance with id %s", instanceId)
case strings.ToLower(InstanceStatePending), strings.ToLower(InstanceStateProcessing):
tflog.Info(ctx, "request is being handled", map[string]interface{}{
"status": *s.Status,
})
return false, nil, nil
default:
tflog.Info(ctx, "Wait (create) received unknown status", map[string]interface{}{
"instanceId": instanceId,
@ -63,8 +70,6 @@ func CreateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface
return false, s, nil
}
})
handler.SetTimeout(45 * time.Minute)
handler.SetSleepBeforeWait(15 * time.Second)
return handler
}
@ -83,6 +88,11 @@ func UpdateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface
return true, s, nil
case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed):
return true, s, fmt.Errorf("update failed for instance with id %s", instanceId)
case strings.ToLower(InstanceStatePending), strings.ToLower(InstanceStateProcessing):
tflog.Info(ctx, "request is being handled", map[string]interface{}{
"status": *s.Status,
})
return false, nil, nil
default:
tflog.Info(ctx, "Wait (update) received unknown status", map[string]interface{}{
"instanceId": instanceId,