From 440ee784e9aae2fb40de27ea7b5b14366390cafd Mon Sep 17 00:00:00 2001 From: Jonas Schlecht Date: Mon, 20 Jul 2026 10:53:11 +0200 Subject: [PATCH 1/5] feat(sqlserverflex): add encyption support Relates to STACKITTPR-756 --- .../sqlserverflex/instance/datasource.go | 28 ++++ .../sqlserverflex/instance/resource.go | 121 +++++++++++++++++- .../sqlserverflex/instance/resource_test.go | 45 ++++--- .../sqlserverflex/sqlserverflex_acc_test.go | 63 ++++++--- .../sqlserverflex/testdata/resource-max.tf | 34 ++++- 5 files changed, 256 insertions(+), 35 deletions(-) diff --git a/stackit/internal/services/sqlserverflex/instance/datasource.go b/stackit/internal/services/sqlserverflex/instance/datasource.go index e3807d012..dec174abb 100644 --- a/stackit/internal/services/sqlserverflex/instance/datasource.go +++ b/stackit/internal/services/sqlserverflex/instance/datasource.go @@ -69,6 +69,11 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques "name": "Instance name.", "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 * * *").`, + "encryption": "Parameter to define which key to use for storage encryption.", + "kek_key_id": "UUID of the key within the STACKIT-KMS to use for the encryption.", + "kek_keyring_id": "UUID of the keyring where the key is located within the STACKTI-KMS.", + "kek_key_version": "Version of the key within the STACKIT-KMS to use for the encryption.", + "service_account": "Service-Account linked to the Key within the STACKIT-KMS.", "options": "Custom parameters for the SQLServer Flex instance.", "flavor_id": "The flavor ID of the SQLServer Flex instance.", "network": "The network configuration of the instance.", @@ -115,6 +120,29 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques Description: descriptions["backup_schedule"], Computed: true, }, + "encryption": schema.SingleNestedAttribute{ + Description: descriptions["encryption"], + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "kek_key_id": schema.StringAttribute{ + Description: descriptions["kek_key_id"], + Computed: true, + }, + "kek_keyring_id": schema.StringAttribute{ + Description: descriptions["kek_keyring_id"], + Computed: true, + }, + "kek_key_version": schema.StringAttribute{ + Description: descriptions["kek_key_version"], + Computed: true, + }, + "service_account": schema.StringAttribute{ + Description: descriptions["service_account"], + Computed: true, + }, + }, + }, "flavor": schema.SingleNestedAttribute{ Computed: true, Attributes: map[string]schema.Attribute{ diff --git a/stackit/internal/services/sqlserverflex/instance/resource.go b/stackit/internal/services/sqlserverflex/instance/resource.go index 71a9a5ce0..8ca1a51ed 100644 --- a/stackit/internal/services/sqlserverflex/instance/resource.go +++ b/stackit/internal/services/sqlserverflex/instance/resource.go @@ -62,6 +62,7 @@ type Model struct { // Deprecated: ACL is deprecated and will be removed after January 2027 ACL types.List `tfsdk:"acl"` BackupSchedule types.String `tfsdk:"backup_schedule"` + Encryption types.Object `tfsdk:"encryption"` Flavor types.Object `tfsdk:"flavor"` FlavorId types.String `tfsdk:"flavor_id"` Storage types.Object `tfsdk:"storage"` @@ -75,6 +76,22 @@ type Model struct { Region types.String `tfsdk:"region"` } +// Struct corresponding to Model.Encryption +type encryptionModel struct { + KekKeyId types.String `tfsdk:"kek_key_id"` + KekKeyRingId types.String `tfsdk:"kek_keyring_id"` + KekKeyVersion types.String `tfsdk:"kek_key_version"` + ServiceAccount types.String `tfsdk:"service_account"` +} + +// types corresponding to encryptionModel +var encryptionTypes = map[string]attr.Type{ + "kek_key_id": basetypes.StringType{}, + "kek_keyring_id": basetypes.StringType{}, + "kek_key_version": basetypes.StringType{}, + "service_account": basetypes.StringType{}, +} + // Struct corresponding to Model.Network type networkModel struct { AccessScope types.String `tfsdk:"access_scope"` @@ -300,6 +317,11 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "name": "Instance name.", "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 * * *")` + willBeRequired, + "encryption": "Parameter to define which key to use for storage encryption.", + "kek_key_id": "UUID of the key within the STACKIT-KMS to use for the encryption.", + "kek_keyring_id": "UUID of the keyring where the key is located within the STACKTI-KMS.", + "kek_key_version": "Version of the key within the STACKIT-KMS to use for the encryption.", + "service_account": "Service-Account linked to the Key within the STACKIT-KMS.", "options": "Custom parameters for the SQLServer Flex instance.", "flavor_id": "The flavor ID of the SQLServer Flex instance.", "network": "The network configuration of the instance." + willBeRequired, @@ -381,6 +403,52 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r stringplanmodifier.UseStateForUnknown(), }, }, + "encryption": schema.SingleNestedAttribute{ + Description: descriptions["encryption"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplace(), + }, + Attributes: map[string]schema.Attribute{ + "kek_key_id": schema.StringAttribute{ + Description: descriptions["kek_key_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "kek_keyring_id": schema.StringAttribute{ + Description: descriptions["kek_keyring_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "kek_key_version": schema.StringAttribute{ + Description: descriptions["kek_key_version"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "service_account": schema.StringAttribute{ + Description: descriptions["service_account"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + }, "flavor": schema.SingleNestedAttribute{ Computed: true, Optional: true, @@ -587,6 +655,17 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques return } } + + var encryption *encryptionModel + if !(model.Encryption.IsNull() || model.Encryption.IsUnknown()) { + encryption = &encryptionModel{} + diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + var flavor = &flavorModel{} if !(model.Flavor.IsNull() || model.Flavor.IsUnknown()) { diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{}) @@ -628,7 +707,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques } // Generate API request body from model - payload, err := toCreatePayload(&model, acl, flavor, storage, options, network) + payload, err := toCreatePayload(&model, acl, encryption, flavor, storage, options, network) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err)) return @@ -1001,6 +1080,23 @@ func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, mod return fmt.Errorf("creating options: %w", core.DiagsToError(diags)) } + var encryptionValues map[string]attr.Value + var encryptionObject types.Object + if resp.Encryption != nil { + encryptionValues = map[string]attr.Value{ + "kek_key_id": types.StringValue(resp.Encryption.KekKeyId), + "kek_keyring_id": types.StringValue(resp.Encryption.KekKeyRingId), + "kek_key_version": types.StringValue(resp.Encryption.KekKeyVersion), + "service_account": types.StringValue(resp.Encryption.ServiceAccount), + } + encryptionObject, diags = types.ObjectValue(encryptionTypes, encryptionValues) + if diags.HasError() { + return fmt.Errorf("creating encryption: %w", core.DiagsToError(diags)) + } + } else { + encryptionObject = types.ObjectNull(encryptionTypes) + } + // If the API returned "0 0 * * *" but user defined "00 00 * * *" in its config, // we keep the user's "00 00 * * *" in the state to satisfy Terraform. backupScheduleApiResp := types.StringValue(resp.BackupSchedule) @@ -1023,14 +1119,33 @@ func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, mod model.RetentionDays = types.Int32Value(resp.RetentionDays) model.Edition = types.StringValue(string(resp.Edition)) model.Network = networkObject + model.Encryption = encryptionObject return nil } -func toCreatePayload(model *Model, acl []string, flavor *flavorModel, storage *storageModel, options *optionsModel, network *networkModel) (*sqlserverflex.CreateInstancePayload, error) { +func toCreatePayload(model *Model, acl []string, encryption *encryptionModel, flavor *flavorModel, storage *storageModel, options *optionsModel, network *networkModel) (*sqlserverflex.CreateInstancePayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } + // Encryption + var encryptionPayload *sqlserverflex.InstanceEncryption + if encryption != nil { + encryptionPayload = &sqlserverflex.InstanceEncryption{} + if !encryption.KekKeyId.IsNull() || !encryption.KekKeyId.IsUnknown() { + encryptionPayload.KekKeyId = encryption.KekKeyId.ValueString() + } + if !encryption.KekKeyRingId.IsNull() || !encryption.KekKeyRingId.IsUnknown() { + encryptionPayload.KekKeyRingId = encryption.KekKeyRingId.ValueString() + } + if !encryption.KekKeyVersion.IsNull() || !encryption.KekKeyVersion.IsUnknown() { + encryptionPayload.KekKeyVersion = encryption.KekKeyVersion.ValueString() + } + if !encryption.ServiceAccount.IsNull() || !encryption.ServiceAccount.IsUnknown() { + encryptionPayload.ServiceAccount = encryption.ServiceAccount.ValueString() + } + } + // Network networkPayload := sqlserverflex.CreateInstancePayloadNetwork{} if acl != nil { @@ -1077,7 +1192,7 @@ func toCreatePayload(model *Model, acl []string, flavor *flavorModel, storage *s return &sqlserverflex.CreateInstancePayload{ BackupSchedule: model.BackupSchedule.ValueString(), - Encryption: nil, + Encryption: encryptionPayload, FlavorId: flavorId, Labels: nil, Name: model.Name.ValueString(), diff --git a/stackit/internal/services/sqlserverflex/instance/resource_test.go b/stackit/internal/services/sqlserverflex/instance/resource_test.go index d8d5051b0..80ef655bb 100644 --- a/stackit/internal/services/sqlserverflex/instance/resource_test.go +++ b/stackit/internal/services/sqlserverflex/instance/resource_test.go @@ -344,15 +344,16 @@ func TestMapFields(t *testing.T) { func TestToCreatePayload(t *testing.T) { tests := []struct { - description string - input *Model - inputAcl []string - inputFlavor *flavorModel - inputStorage *storageModel - inputOptions *optionsModel - inputNetwork *networkModel - expected *sqlserverflex.CreateInstancePayload - isValid bool + description string + input *Model + inputAcl []string + inputEncryption *encryptionModel + inputFlavor *flavorModel + inputStorage *storageModel + inputOptions *optionsModel + inputNetwork *networkModel + expected *sqlserverflex.CreateInstancePayload + isValid bool }{ { description: "default_values", @@ -360,11 +361,12 @@ func TestToCreatePayload(t *testing.T) { FlavorId: types.StringValue("fid"), RetentionDays: types.Int32Value(1), }, - inputAcl: []string{}, - inputFlavor: &flavorModel{}, - inputStorage: &storageModel{}, - inputOptions: &optionsModel{}, - inputNetwork: &networkModel{}, + inputAcl: []string{}, + inputEncryption: &encryptionModel{}, + inputFlavor: &flavorModel{}, + inputStorage: &storageModel{}, + inputOptions: &optionsModel{}, + inputNetwork: &networkModel{}, expected: &sqlserverflex.CreateInstancePayload{ FlavorId: "fid", RetentionDays: 1, @@ -375,6 +377,7 @@ func TestToCreatePayload(t *testing.T) { Class: "", Size: 0, }, + Encryption: &sqlserverflex.InstanceEncryption{}, }, isValid: true, }, @@ -401,6 +404,12 @@ func TestToCreatePayload(t *testing.T) { RetentionDays: types.Int32Value(1), }, inputNetwork: &networkModel{}, + inputEncryption: &encryptionModel{ + KekKeyId: types.StringValue("id"), + KekKeyRingId: types.StringValue("keyRingId"), + KekKeyVersion: types.StringValue("keyVersion"), + ServiceAccount: types.StringValue("some_service_account"), + }, expected: &sqlserverflex.CreateInstancePayload{ Network: sqlserverflex.CreateInstancePayloadNetwork{ Acl: []string{"ip_1", "ip_2"}, @@ -414,6 +423,12 @@ func TestToCreatePayload(t *testing.T) { }, RetentionDays: 1, Version: "version", + Encryption: &sqlserverflex.InstanceEncryption{ + KekKeyId: "id", + KekKeyRingId: "keyRingId", + KekKeyVersion: "keyVersion", + ServiceAccount: "some_service_account", + }, }, isValid: true, }, @@ -522,7 +537,7 @@ func TestToCreatePayload(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - output, err := toCreatePayload(tt.input, tt.inputAcl, tt.inputFlavor, tt.inputStorage, tt.inputOptions, tt.inputNetwork) + output, err := toCreatePayload(tt.input, tt.inputAcl, tt.inputEncryption, tt.inputFlavor, tt.inputStorage, tt.inputOptions, tt.inputNetwork) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } diff --git a/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go b/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go index bda442e1a..2a3e0191c 100644 --- a/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go +++ b/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stackitcloud/stackit-sdk-go/core/utils" + kms "github.com/stackitcloud/stackit-sdk-go/services/kms/v1api" sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api/wait" @@ -38,20 +39,28 @@ var testConfigVarsMin = config.Variables{ } var testConfigVarsMax = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), - "acl1": config.StringVariable("192.168.0.0/16"), - "storage_class": config.StringVariable("premium-perf2-stackit"), - "storage_size": config.IntegerVariable(40), - "server_version": config.StringVariable("2022"), - "replicas": config.IntegerVariable(1), - "access_scope": config.StringVariable(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), - "retention_days": config.IntegerVariable(32), - "flavor_id": config.StringVariable("4.16-Single"), - "backup_schedule": config.StringVariable("0 6 * * *"), - "username": config.StringVariable(fmt.Sprintf("tf-acc-user-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlpha))), - "role": config.StringVariable("##STACKIT_LoginManager##"), - "region": config.StringVariable(testutil.Region), + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "acl1": config.StringVariable("192.168.0.0/16"), + "storage_class": config.StringVariable("premium-perf2-stackit"), + "storage_size": config.IntegerVariable(40), + "server_version": config.StringVariable("2022"), + "replicas": config.IntegerVariable(1), + "access_scope": config.StringVariable(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "retention_days": config.IntegerVariable(32), + "flavor_id": config.StringVariable("4.16-Single"), + "backup_schedule": config.StringVariable("0 6 * * *"), + "username": config.StringVariable(fmt.Sprintf("tf-acc-user-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlpha))), + "role": config.StringVariable("##STACKIT_LoginManager##"), + "region": config.StringVariable(testutil.Region), + "kek_key_version": config.StringVariable("1"), + "service_account_email": config.StringVariable(testutil.TestProjectServiceAccountEmail), + + "keyring_display_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), + "display_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), + "algorithm": config.StringVariable(string(kms.ALGORITHM_AES_256_GCM)), + "protection": config.StringVariable("software"), + "purpose": config.StringVariable(string(kms.PURPOSE_SYMMETRIC_ENCRYPT_DECRYPT)), } func configVarsMinUpdated() config.Variables { @@ -147,7 +156,7 @@ func TestAccSQLServerFlexMinResource(t *testing.T) { resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_user.user", "user_id"), resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "username", testutil.ConvertConfigVariable(testConfigVarsMin["username"])), resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "roles.#", "1"), - resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "roles.0", testutil.ConvertConfigVariable(testConfigVarsMax["role"])), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "roles.0", testutil.ConvertConfigVariable(testConfigVarsMin["role"])), ), }, // Import @@ -250,6 +259,15 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) { }, ConfigVariables: testConfigVarsMax, Check: resource.ComposeAggregateTestCheckFunc( + // Key and Keyring + resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region), + resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "display_name", testutil.ConvertConfigVariable(testConfigVarsMax["display_name"])), + resource.TestCheckResourceAttrSet("stackit_kms_keyring.keyring", "keyring_id"), + resource.TestCheckNoResourceAttr("stackit_kms_keyring.keyring", "description"), + ), + // Instance resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "instance_id"), @@ -268,6 +286,11 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) { resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "version", testutil.ConvertConfigVariable(testConfigVarsMax["server_version"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["retention_days"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMax["backup_schedule"])), + + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMax["kek_key_version"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMax["service_account_email"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "region", testutil.Region), // User resource.TestCheckResourceAttrPair( @@ -314,6 +337,11 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["retention_days"])), resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMax["backup_schedule"])), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMax["kek_key_version"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMax["service_account_email"])), + // User data resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_user.user", "user_id"), @@ -411,6 +439,11 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) { resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "version", testutil.ConvertConfigVariable(configVarsMaxUpdated()["server_version"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(configVarsMaxUpdated()["retention_days"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(configVarsMaxUpdated()["backup_schedule"])), + + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMax["kek_key_version"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMax["service_account_email"])), ), }, // Deletion is done by the framework implicitly diff --git a/stackit/internal/services/sqlserverflex/testdata/resource-max.tf b/stackit/internal/services/sqlserverflex/testdata/resource-max.tf index 9797377e5..2873f5972 100644 --- a/stackit/internal/services/sqlserverflex/testdata/resource-max.tf +++ b/stackit/internal/services/sqlserverflex/testdata/resource-max.tf @@ -12,6 +12,30 @@ variable "role" {} variable "server_version" {} variable "region" {} +variable "kek_key_version" {} +variable "service_account_email" {} + +variable "keyring_display_name" {} +variable "display_name" {} +variable "protection" {} +variable "algorithm" {} +variable "purpose" {} + + +resource "stackit_kms_keyring" "keyring" { + project_id = var.project_id + display_name = var.keyring_display_name +} + +resource "stackit_kms_key" "key" { + project_id = var.project_id + keyring_id = stackit_kms_keyring.keyring.keyring_id + protection = var.protection + algorithm = var.algorithm + display_name = var.display_name + purpose = var.purpose +} + resource "stackit_sqlserverflex_instance" "instance" { project_id = var.project_id name = var.name @@ -24,8 +48,14 @@ resource "stackit_sqlserverflex_instance" "instance" { acl = [var.acl1] access_scope = var.access_scope } - retention_days = var.retention_days - version = var.server_version + retention_days = var.retention_days + version = var.server_version + encryption = { + kek_key_id = stackit_kms_key.key.key_id + kek_keyring_id = stackit_kms_keyring.keyring.keyring_id + kek_key_version = var.kek_key_version + service_account = var.service_account_email + } backup_schedule = var.backup_schedule region = var.region } From d7cafe2b82963de93370d5454e8bfc951bc249eb Mon Sep 17 00:00:00 2001 From: Jonas Schlecht Date: Wed, 22 Jul 2026 14:59:33 +0200 Subject: [PATCH 2/5] feat(sqlserverflex): add instance_address and router_address Relates to STACKITTPR-756 --- docs/data-sources/sqlserverflex_instance.md | 14 +++ docs/resources/sqlserverflex_instance.md | 17 +++ .../sqlserverflex/instance/datasource.go | 12 ++ .../sqlserverflex/instance/resource.go | 30 ++++- .../sqlserverflex/instance/resource_test.go | 114 ++++++++++++------ 5 files changed, 143 insertions(+), 44 deletions(-) diff --git a/docs/data-sources/sqlserverflex_instance.md b/docs/data-sources/sqlserverflex_instance.md index 4fa559558..ba34f4816 100644 --- a/docs/data-sources/sqlserverflex_instance.md +++ b/docs/data-sources/sqlserverflex_instance.md @@ -29,6 +29,7 @@ data "stackit_sqlserverflex_instance" "example" { ### Optional +- `encryption` (Attributes) Parameter to define which key to use for storage encryption. (see [below for nested schema](#nestedatt--encryption)) - `network` (Attributes) The network configuration of the instance. (see [below for nested schema](#nestedatt--network)) - `region` (String) The resource region. If not defined, the provider region is used. @@ -47,6 +48,17 @@ data "stackit_sqlserverflex_instance" "example" { - `storage` (Attributes) (see [below for nested schema](#nestedatt--storage)) - `version` (String) + +### Nested Schema for `encryption` + +Read-Only: + +- `kek_key_id` (String) UUID of the key within the STACKIT-KMS to use for the encryption. +- `kek_key_version` (String) Version of the key within the STACKIT-KMS to use for the encryption. +- `kek_keyring_id` (String) UUID of the keyring where the key is located within the STACKTI-KMS. +- `service_account` (String) Service-Account linked to the Key within the STACKIT-KMS. + + ### Nested Schema for `network` @@ -57,6 +69,8 @@ Optional: Read-Only: - `acl` (List of String) List of IPV4 cidr. +- `instance_address` (List of String) Address of this instance. +- `router_address` (List of String) Address of the router. diff --git a/docs/resources/sqlserverflex_instance.md b/docs/resources/sqlserverflex_instance.md index b520e4a3b..7e8df229e 100644 --- a/docs/resources/sqlserverflex_instance.md +++ b/docs/resources/sqlserverflex_instance.md @@ -39,6 +39,7 @@ resource "stackit_sqlserverflex_instance" "example" { - `acl` (List of String, Deprecated) The Access Control List (ACL) for the SQLServer Flex instance. - `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *") Will be required in the future. Set a value to prevent breaking changes. +- `encryption` (Attributes) Parameter to define which key to use for storage encryption. (see [below for nested schema](#nestedatt--encryption)) - `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor)) - `flavor_id` (String) The flavor ID of the SQLServer Flex instance. - `network` (Attributes) The network configuration of the instance. Will be required in the future. Set a value to prevent breaking changes. (see [below for nested schema](#nestedatt--network)) @@ -55,6 +56,17 @@ resource "stackit_sqlserverflex_instance" "example" { - `instance_id` (String) ID of the SQLServer Flex instance. - `replicas` (Number) + +### Nested Schema for `encryption` + +Required: + +- `kek_key_id` (String) UUID of the key within the STACKIT-KMS to use for the encryption. +- `kek_key_version` (String) Version of the key within the STACKIT-KMS to use for the encryption. +- `kek_keyring_id` (String) UUID of the keyring where the key is located within the STACKTI-KMS. +- `service_account` (String) Service-Account linked to the Key within the STACKIT-KMS. + + ### Nested Schema for `flavor` @@ -77,6 +89,11 @@ Optional: - `access_scope` (String) The network access scope of the instance. This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected. Possible values are: `PUBLIC`, `SNA`. - `acl` (List of String) List of IPV4 cidr. +Read-Only: + +- `instance_address` (List of String) Address of this instance. +- `router_address` (List of String) Address of the router. + ### Nested Schema for `options` diff --git a/stackit/internal/services/sqlserverflex/instance/datasource.go b/stackit/internal/services/sqlserverflex/instance/datasource.go index dec174abb..679bbe6ca 100644 --- a/stackit/internal/services/sqlserverflex/instance/datasource.go +++ b/stackit/internal/services/sqlserverflex/instance/datasource.go @@ -79,6 +79,8 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques "network": "The network configuration of the instance.", "network.access_scope": "The network access scope of the instance. This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected.", "network.acl": "List of IPV4 cidr.", + "instance_address": "Address of this instance.", + "router_address": "Address of the router.", "retention_days": "The days (30 to 90) for how long the backup files should be stored before cleaned up.", "edition": "Edition of the MSSQL server instance.", "region": "The resource region. If not defined, the provider region is used.", @@ -177,6 +179,16 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques ElementType: types.StringType, Computed: true, }, + "instance_address": schema.ListAttribute{ + Description: descriptions["instance_address"], + ElementType: types.StringType, + Computed: true, + }, + "router_address": schema.ListAttribute{ + Description: descriptions["router_address"], + ElementType: types.StringType, + Computed: true, + }, }, }, "replicas": schema.Int32Attribute{ diff --git a/stackit/internal/services/sqlserverflex/instance/resource.go b/stackit/internal/services/sqlserverflex/instance/resource.go index 8ca1a51ed..042c3085d 100644 --- a/stackit/internal/services/sqlserverflex/instance/resource.go +++ b/stackit/internal/services/sqlserverflex/instance/resource.go @@ -94,14 +94,18 @@ var encryptionTypes = map[string]attr.Type{ // Struct corresponding to Model.Network type networkModel struct { - AccessScope types.String `tfsdk:"access_scope"` - Acl types.List `tfsdk:"acl"` + AccessScope types.String `tfsdk:"access_scope"` + Acl types.List `tfsdk:"acl"` + InstanceAddress types.String `tfsdk:"instance_address"` + RouterAddress types.String `tfsdk:"router_address"` } // types corresponding to Network var networkTypes = map[string]attr.Type{ - "access_scope": basetypes.StringType{}, - "acl": basetypes.ListType{ElemType: types.StringType}, + "access_scope": basetypes.StringType{}, + "acl": basetypes.ListType{ElemType: types.StringType}, + "instance_address": basetypes.StringType{}, + "router_address": basetypes.StringType{}, } // Struct corresponding to Model.Flavor @@ -327,6 +331,8 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "network": "The network configuration of the instance." + willBeRequired, "network.access_scope": "The network access scope of the instance. This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected.", "network.acl": "List of IPV4 cidr." + willBeRequired, + "instance_address": "Address of this instance.", + "router_address": "Address of the router.", "retention_days": "The days (30 to 90) for how long the backup files should be stored before cleaned up." + willBeRequired, "edition": "Edition of the MSSQL server instance.", "region": "The resource region. If not defined, the provider region is used.", @@ -522,6 +528,16 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r listvalidator.SizeAtLeast(1), }, }, + "instance_address": schema.ListAttribute{ + Description: descriptions["instance_address"], + ElementType: types.StringType, + Computed: true, + }, + "router_address": schema.ListAttribute{ + Description: descriptions["router_address"], + ElementType: types.StringType, + Computed: true, + }, }, }, "replicas": schema.Int32Attribute{ @@ -1043,8 +1059,10 @@ func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, mod } networkValues := map[string]attr.Value{ - "acl": aclList, - "access_scope": types.StringPointerValue((*string)(resp.Network.AccessScope)), + "acl": aclList, + "access_scope": types.StringPointerValue((*string)(resp.Network.AccessScope)), + "instance_address": types.StringPointerValue(resp.Network.InstanceAddress), + "router_address": types.StringPointerValue(resp.Network.RouterAddress), } networkObject, diags := types.ObjectValue(networkTypes, networkValues) if diags.HasError() { diff --git a/stackit/internal/services/sqlserverflex/instance/resource_test.go b/stackit/internal/services/sqlserverflex/instance/resource_test.go index 80ef655bb..7478f9d95 100644 --- a/stackit/internal/services/sqlserverflex/instance/resource_test.go +++ b/stackit/internal/services/sqlserverflex/instance/resource_test.go @@ -74,8 +74,10 @@ func TestMapFields(t *testing.T) { "retention_days": types.Int32Value(0), }), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListNull(types.StringType), - "access_scope": types.StringNull(), + "acl": types.ListNull(types.StringType), + "access_scope": types.StringNull(), + "instance_address": types.StringNull(), + "router_address": types.StringNull(), }), RetentionDays: types.Int32Value(0), Edition: types.StringValue(""), @@ -135,8 +137,10 @@ func TestMapFields(t *testing.T) { Edition: types.StringValue("edition"), RetentionDays: types.Int32Value(1), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("ip1"), types.StringValue("ip2"), types.StringValue("")}), - "access_scope": types.StringNull(), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("ip1"), types.StringValue("ip2"), types.StringValue("")}), + "access_scope": types.StringNull(), + "instance_address": types.StringNull(), + "router_address": types.StringNull(), }), Replicas: types.Int32Value(56), Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{ @@ -207,8 +211,10 @@ func TestMapFields(t *testing.T) { RetentionDays: types.Int32Value(1), Replicas: types.Int32Value(56), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("ip1"), types.StringValue("ip2"), types.StringValue("")}), - "access_scope": types.StringNull(), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("ip1"), types.StringValue("ip2"), types.StringValue("")}), + "access_scope": types.StringNull(), + "instance_address": types.StringNull(), + "router_address": types.StringNull(), }), Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{ "class": types.StringValue("class"), @@ -283,8 +289,10 @@ func TestMapFields(t *testing.T) { RetentionDays: types.Int32Value(1), Replicas: types.Int32Value(56), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("ip2"), types.StringValue(""), types.StringValue("ip1")}), - "access_scope": types.StringNull(), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("ip2"), types.StringValue(""), types.StringValue("ip1")}), + "access_scope": types.StringNull(), + "instance_address": types.StringNull(), + "router_address": types.StringNull(), }), Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{ "class": types.StringValue("class"), @@ -903,8 +911,10 @@ func TestHandleV3Migration(t *testing.T) { }), Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(45), }, @@ -917,8 +927,10 @@ func TestHandleV3Migration(t *testing.T) { }), Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(45), }, @@ -931,8 +943,10 @@ func TestHandleV3Migration(t *testing.T) { }), Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(45), }, @@ -983,8 +997,10 @@ func TestHandleV3Migration(t *testing.T) { Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)), ACL: types.ListNull(types.StringType), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(1), Options: types.ObjectNull(optionsTypes), @@ -998,8 +1014,10 @@ func TestHandleV3Migration(t *testing.T) { Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)), ACL: types.ListNull(types.StringType), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(1), Options: types.ObjectNull(optionsTypes), @@ -1013,8 +1031,10 @@ func TestHandleV3Migration(t *testing.T) { Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)), ACL: types.ListNull(types.StringType), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(1), Options: types.ObjectNull(optionsTypes), @@ -1032,8 +1052,10 @@ func TestHandleV3Migration(t *testing.T) { Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)), ACL: types.ListNull(types.StringType), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(1), Options: types.ObjectNull(optionsTypes), @@ -1047,8 +1069,10 @@ func TestHandleV3Migration(t *testing.T) { Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)), ACL: types.ListNull(types.StringType), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(1), Options: types.ObjectNull(optionsTypes), @@ -1062,8 +1086,10 @@ func TestHandleV3Migration(t *testing.T) { Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)), ACL: types.ListNull(types.StringType), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(1), Options: types.ObjectNull(optionsTypes), @@ -1081,8 +1107,10 @@ func TestHandleV3Migration(t *testing.T) { Version: types.StringNull(), ACL: types.ListNull(types.StringType), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(1), Options: types.ObjectNull(optionsTypes), @@ -1096,8 +1124,10 @@ func TestHandleV3Migration(t *testing.T) { Version: types.StringNull(), ACL: types.ListNull(types.StringType), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(1), Options: types.ObjectNull(optionsTypes), @@ -1111,8 +1141,10 @@ func TestHandleV3Migration(t *testing.T) { Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)), ACL: types.ListNull(types.StringType), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Value(1), Options: types.ObjectNull(optionsTypes), @@ -1130,8 +1162,10 @@ func TestHandleV3Migration(t *testing.T) { }), Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("193.148.160.0/19")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("193.148.160.0/19")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Null(), Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ @@ -1148,8 +1182,10 @@ func TestHandleV3Migration(t *testing.T) { }), Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("193.148.160.0/19")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("193.148.160.0/19")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Null(), Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ @@ -1166,8 +1202,10 @@ func TestHandleV3Migration(t *testing.T) { }), Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("193.148.160.0/19")}), - "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("193.148.160.0/19")}), + "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "instance_address": types.StringValue("instance_address"), + "router_address": types.StringValue("router_address"), }), RetentionDays: types.Int32Null(), Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ From 9d7c1b473aaf3c2b295cabb3f383967f423930e9 Mon Sep 17 00:00:00 2001 From: Jonas Schlecht Date: Thu, 23 Jul 2026 08:48:33 +0200 Subject: [PATCH 3/5] fix(sqlserverflex): fix various issues found during review Relates to STACKITTPR-756 --- docs/data-sources/sqlserverflex_instance.md | 28 +++++++++---------- docs/resources/sqlserverflex_instance.md | 4 +-- .../sqlserverflex/instance/datasource.go | 7 ++--- .../sqlserverflex/instance/resource.go | 28 ++++++------------- .../sqlserverflex/sqlserverflex_acc_test.go | 8 +++--- 5 files changed, 30 insertions(+), 45 deletions(-) diff --git a/docs/data-sources/sqlserverflex_instance.md b/docs/data-sources/sqlserverflex_instance.md index ba34f4816..6547050c3 100644 --- a/docs/data-sources/sqlserverflex_instance.md +++ b/docs/data-sources/sqlserverflex_instance.md @@ -29,7 +29,6 @@ data "stackit_sqlserverflex_instance" "example" { ### Optional -- `encryption` (Attributes) Parameter to define which key to use for storage encryption. (see [below for nested schema](#nestedatt--encryption)) - `network` (Attributes) The network configuration of the instance. (see [below for nested schema](#nestedatt--network)) - `region` (String) The resource region. If not defined, the provider region is used. @@ -38,6 +37,7 @@ data "stackit_sqlserverflex_instance" "example" { - `acl` (List of String) The Access Control List (ACL) for the SQLServer Flex instance. - `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *"). - `edition` (String) Edition of the MSSQL server instance. +- `encryption` (Attributes) Parameter to define which key to use for storage encryption. (see [below for nested schema](#nestedatt--encryption)) - `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor)) - `flavor_id` (String) The flavor ID of the SQLServer Flex instance. - `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`instance_id`". @@ -48,17 +48,6 @@ data "stackit_sqlserverflex_instance" "example" { - `storage` (Attributes) (see [below for nested schema](#nestedatt--storage)) - `version` (String) - -### Nested Schema for `encryption` - -Read-Only: - -- `kek_key_id` (String) UUID of the key within the STACKIT-KMS to use for the encryption. -- `kek_key_version` (String) Version of the key within the STACKIT-KMS to use for the encryption. -- `kek_keyring_id` (String) UUID of the keyring where the key is located within the STACKTI-KMS. -- `service_account` (String) Service-Account linked to the Key within the STACKIT-KMS. - - ### Nested Schema for `network` @@ -69,8 +58,19 @@ Optional: Read-Only: - `acl` (List of String) List of IPV4 cidr. -- `instance_address` (List of String) Address of this instance. -- `router_address` (List of String) Address of the router. +- `instance_address` (String) Address of this instance. +- `router_address` (String) Address of the router. + + + +### Nested Schema for `encryption` + +Read-Only: + +- `kek_key_id` (String) UUID of the key within the STACKIT-KMS to use for the encryption. +- `kek_key_version` (String) Version of the key within the STACKIT-KMS to use for the encryption. +- `kek_keyring_id` (String) UUID of the keyring where the key is located within the STACKTI-KMS. +- `service_account` (String) Service-Account linked to the Key within the STACKIT-KMS. diff --git a/docs/resources/sqlserverflex_instance.md b/docs/resources/sqlserverflex_instance.md index 7e8df229e..ddc2aa802 100644 --- a/docs/resources/sqlserverflex_instance.md +++ b/docs/resources/sqlserverflex_instance.md @@ -91,8 +91,8 @@ Optional: Read-Only: -- `instance_address` (List of String) Address of this instance. -- `router_address` (List of String) Address of the router. +- `instance_address` (String) Address of this instance. +- `router_address` (String) Address of the router. diff --git a/stackit/internal/services/sqlserverflex/instance/datasource.go b/stackit/internal/services/sqlserverflex/instance/datasource.go index 679bbe6ca..45dd5bb6c 100644 --- a/stackit/internal/services/sqlserverflex/instance/datasource.go +++ b/stackit/internal/services/sqlserverflex/instance/datasource.go @@ -124,7 +124,6 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques }, "encryption": schema.SingleNestedAttribute{ Description: descriptions["encryption"], - Optional: true, Computed: true, Attributes: map[string]schema.Attribute{ "kek_key_id": schema.StringAttribute{ @@ -179,14 +178,12 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques ElementType: types.StringType, Computed: true, }, - "instance_address": schema.ListAttribute{ + "instance_address": schema.StringAttribute{ Description: descriptions["instance_address"], - ElementType: types.StringType, Computed: true, }, - "router_address": schema.ListAttribute{ + "router_address": schema.StringAttribute{ Description: descriptions["router_address"], - ElementType: types.StringType, Computed: true, }, }, diff --git a/stackit/internal/services/sqlserverflex/instance/resource.go b/stackit/internal/services/sqlserverflex/instance/resource.go index 042c3085d..6f82c44eb 100644 --- a/stackit/internal/services/sqlserverflex/instance/resource.go +++ b/stackit/internal/services/sqlserverflex/instance/resource.go @@ -412,7 +412,6 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "encryption": schema.SingleNestedAttribute{ Description: descriptions["encryption"], Optional: true, - Computed: true, PlanModifiers: []planmodifier.Object{ objectplanmodifier.RequiresReplace(), }, @@ -528,14 +527,12 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r listvalidator.SizeAtLeast(1), }, }, - "instance_address": schema.ListAttribute{ + "instance_address": schema.StringAttribute{ Description: descriptions["instance_address"], - ElementType: types.StringType, Computed: true, }, - "router_address": schema.ListAttribute{ + "router_address": schema.StringAttribute{ Description: descriptions["router_address"], - ElementType: types.StringType, Computed: true, }, }, @@ -1099,7 +1096,7 @@ func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, mod } var encryptionValues map[string]attr.Value - var encryptionObject types.Object + encryptionObject := types.ObjectNull(encryptionTypes) if resp.Encryption != nil { encryptionValues = map[string]attr.Value{ "kek_key_id": types.StringValue(resp.Encryption.KekKeyId), @@ -1111,8 +1108,6 @@ func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, mod if diags.HasError() { return fmt.Errorf("creating encryption: %w", core.DiagsToError(diags)) } - } else { - encryptionObject = types.ObjectNull(encryptionTypes) } // If the API returned "0 0 * * *" but user defined "00 00 * * *" in its config, @@ -1149,18 +1144,11 @@ func toCreatePayload(model *Model, acl []string, encryption *encryptionModel, fl // Encryption var encryptionPayload *sqlserverflex.InstanceEncryption if encryption != nil { - encryptionPayload = &sqlserverflex.InstanceEncryption{} - if !encryption.KekKeyId.IsNull() || !encryption.KekKeyId.IsUnknown() { - encryptionPayload.KekKeyId = encryption.KekKeyId.ValueString() - } - if !encryption.KekKeyRingId.IsNull() || !encryption.KekKeyRingId.IsUnknown() { - encryptionPayload.KekKeyRingId = encryption.KekKeyRingId.ValueString() - } - if !encryption.KekKeyVersion.IsNull() || !encryption.KekKeyVersion.IsUnknown() { - encryptionPayload.KekKeyVersion = encryption.KekKeyVersion.ValueString() - } - if !encryption.ServiceAccount.IsNull() || !encryption.ServiceAccount.IsUnknown() { - encryptionPayload.ServiceAccount = encryption.ServiceAccount.ValueString() + encryptionPayload = &sqlserverflex.InstanceEncryption{ + KekKeyId: encryption.KekKeyId.ValueString(), + KekKeyRingId: encryption.KekKeyRingId.ValueString(), + KekKeyVersion: encryption.KekKeyVersion.ValueString(), + ServiceAccount: encryption.ServiceAccount.ValueString(), } } diff --git a/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go b/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go index 2a3e0191c..b70f91dc0 100644 --- a/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go +++ b/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go @@ -337,10 +337,10 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["retention_days"])), resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMax["backup_schedule"])), - resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), - resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), - resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMax["kek_key_version"])), - resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMax["service_account_email"])), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMax["kek_key_version"])), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMax["service_account_email"])), // User data resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), From bbf96f216f0e706128d76d9a446c546c52db5c18 Mon Sep 17 00:00:00 2001 From: Jonas Schlecht Date: Thu, 23 Jul 2026 09:04:47 +0200 Subject: [PATCH 4/5] chore(sqlserverflex): split private preview tests and GA tests Relates to STACKITTPR-756 --- .../sqlserverflex/sqlserverflex_acc_test.go | 61 +--- .../sqlserverflex_private_preview_acc_test.go | 265 ++++++++++++++++++ .../testdata/resource-max-private-preview.tf | 79 ++++++ .../sqlserverflex/testdata/resource-max.tf | 34 +-- 4 files changed, 360 insertions(+), 79 deletions(-) create mode 100644 stackit/internal/services/sqlserverflex/sqlserverflex_private_preview_acc_test.go create mode 100644 stackit/internal/services/sqlserverflex/testdata/resource-max-private-preview.tf diff --git a/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go b/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go index b70f91dc0..ad7f44ccd 100644 --- a/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go +++ b/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stackitcloud/stackit-sdk-go/core/utils" - kms "github.com/stackitcloud/stackit-sdk-go/services/kms/v1api" sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api/wait" @@ -39,28 +38,20 @@ var testConfigVarsMin = config.Variables{ } var testConfigVarsMax = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), - "acl1": config.StringVariable("192.168.0.0/16"), - "storage_class": config.StringVariable("premium-perf2-stackit"), - "storage_size": config.IntegerVariable(40), - "server_version": config.StringVariable("2022"), - "replicas": config.IntegerVariable(1), - "access_scope": config.StringVariable(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), - "retention_days": config.IntegerVariable(32), - "flavor_id": config.StringVariable("4.16-Single"), - "backup_schedule": config.StringVariable("0 6 * * *"), - "username": config.StringVariable(fmt.Sprintf("tf-acc-user-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlpha))), - "role": config.StringVariable("##STACKIT_LoginManager##"), - "region": config.StringVariable(testutil.Region), - "kek_key_version": config.StringVariable("1"), - "service_account_email": config.StringVariable(testutil.TestProjectServiceAccountEmail), - - "keyring_display_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), - "display_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), - "algorithm": config.StringVariable(string(kms.ALGORITHM_AES_256_GCM)), - "protection": config.StringVariable("software"), - "purpose": config.StringVariable(string(kms.PURPOSE_SYMMETRIC_ENCRYPT_DECRYPT)), + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "acl1": config.StringVariable("192.168.0.0/16"), + "storage_class": config.StringVariable("premium-perf2-stackit"), + "storage_size": config.IntegerVariable(40), + "server_version": config.StringVariable("2022"), + "replicas": config.IntegerVariable(1), + "access_scope": config.StringVariable(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "retention_days": config.IntegerVariable(32), + "flavor_id": config.StringVariable("4.16-Single"), + "backup_schedule": config.StringVariable("0 6 * * *"), + "username": config.StringVariable(fmt.Sprintf("tf-acc-user-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlpha))), + "role": config.StringVariable("##STACKIT_LoginManager##"), + "region": config.StringVariable(testutil.Region), } func configVarsMinUpdated() config.Variables { @@ -259,15 +250,6 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) { }, ConfigVariables: testConfigVarsMax, Check: resource.ComposeAggregateTestCheckFunc( - // Key and Keyring - resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId), - resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region), - resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "display_name", testutil.ConvertConfigVariable(testConfigVarsMax["display_name"])), - resource.TestCheckResourceAttrSet("stackit_kms_keyring.keyring", "keyring_id"), - resource.TestCheckNoResourceAttr("stackit_kms_keyring.keyring", "description"), - ), - // Instance resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "instance_id"), @@ -286,11 +268,6 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) { resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "version", testutil.ConvertConfigVariable(testConfigVarsMax["server_version"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["retention_days"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMax["backup_schedule"])), - - resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), - resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), - resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMax["kek_key_version"])), - resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMax["service_account_email"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "region", testutil.Region), // User resource.TestCheckResourceAttrPair( @@ -337,11 +314,6 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["retention_days"])), resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMax["backup_schedule"])), - resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), - resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), - resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMax["kek_key_version"])), - resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMax["service_account_email"])), - // User data resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_user.user", "user_id"), @@ -439,11 +411,6 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) { resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "version", testutil.ConvertConfigVariable(configVarsMaxUpdated()["server_version"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(configVarsMaxUpdated()["retention_days"])), resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(configVarsMaxUpdated()["backup_schedule"])), - - resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), - resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), - resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMax["kek_key_version"])), - resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMax["service_account_email"])), ), }, // Deletion is done by the framework implicitly diff --git a/stackit/internal/services/sqlserverflex/sqlserverflex_private_preview_acc_test.go b/stackit/internal/services/sqlserverflex/sqlserverflex_private_preview_acc_test.go new file mode 100644 index 000000000..e076bd699 --- /dev/null +++ b/stackit/internal/services/sqlserverflex/sqlserverflex_private_preview_acc_test.go @@ -0,0 +1,265 @@ +package sqlserverflex_test + +import ( + _ "embed" + "fmt" + "maps" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + kms "github.com/stackitcloud/stackit-sdk-go/services/kms/v1api" + sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +var ( + //go:embed testdata/resource-max-private-preview.tf + resourcePrivatePreviewConfig string +) + +var testConfigVarsMaxPrivatePreview = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "acl1": config.StringVariable("192.168.0.0/16"), + "storage_class": config.StringVariable("premium-perf2-stackit"), + "storage_size": config.IntegerVariable(40), + "server_version": config.StringVariable("2022"), + "replicas": config.IntegerVariable(1), + "access_scope": config.StringVariable(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)), + "retention_days": config.IntegerVariable(32), + "flavor_id": config.StringVariable("4.16-Single"), + "backup_schedule": config.StringVariable("0 6 * * *"), + "username": config.StringVariable(fmt.Sprintf("tf-acc-user-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlpha))), + "role": config.StringVariable("##STACKIT_LoginManager##"), + "region": config.StringVariable(testutil.Region), + "kek_key_version": config.StringVariable("1"), + "service_account_email": config.StringVariable(testutil.TestProjectServiceAccountEmail), + + "keyring_display_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), + "display_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), + "algorithm": config.StringVariable(string(kms.ALGORITHM_AES_256_GCM)), + "protection": config.StringVariable("software"), + "purpose": config.StringVariable(string(kms.PURPOSE_SYMMETRIC_ENCRYPT_DECRYPT)), +} + +func configVarsMaxPrivatePreviewUpdated() config.Variables { + temp := maps.Clone(testConfigVarsMaxPrivatePreview) + temp["backup_schedule"] = config.StringVariable("0 12 * * *") + temp["acl1"] = config.StringVariable("192.168.2.0/16") + temp["retention_days"] = config.IntegerVariable(40) + return temp +} + +func TestAccSQLServerFlexMaxPrivatePreviewResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccChecksqlserverflexDestroy, + Steps: []resource.TestStep{ + // Creation + { + Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourcePrivatePreviewConfig, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("stackit_sqlserverflex_instance.instance", plancheck.ResourceActionCreate), + plancheck.ExpectResourceAction("stackit_sqlserverflex_user.user", plancheck.ResourceActionCreate), + }, + }, + ConfigVariables: testConfigVarsMaxPrivatePreview, + Check: resource.ComposeAggregateTestCheckFunc( + // Key and Keyring + resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region), + resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "display_name", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["display_name"])), + resource.TestCheckResourceAttrSet("stackit_kms_keyring.keyring", "keyring_id"), + resource.TestCheckNoResourceAttr("stackit_kms_keyring.keyring", "description"), + ), + + // Instance + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["project_id"])), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "instance_id"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "name", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["name"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "network.acl.#", "1"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "network.acl.0", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["acl1"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "network.access_scope", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["access_scope"])), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor_id"), + resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.id"), + resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "replicas", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["replicas"])), + resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu"), + resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.class", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["storage_class"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.size", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["storage_size"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "version", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["server_version"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["retention_days"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["backup_schedule"])), + + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["kek_key_version"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["service_account_email"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "region", testutil.Region), + // User + resource.TestCheckResourceAttrPair( + "stackit_sqlserverflex_user.user", "project_id", + "stackit_sqlserverflex_instance.instance", "project_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_sqlserverflex_user.user", "instance_id", + "stackit_sqlserverflex_instance.instance", "instance_id", + ), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_user.user", "user_id"), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_user.user", "password"), + ), + }, + // data source + { + Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourcePrivatePreviewConfig, + ConfigVariables: testConfigVarsMaxPrivatePreview, + Check: resource.ComposeAggregateTestCheckFunc( + // Instance data + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["project_id"])), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "name", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["name"])), + resource.TestCheckResourceAttrPair( + "data.stackit_sqlserverflex_instance.instance", "project_id", + "stackit_sqlserverflex_instance.instance", "project_id", + ), + resource.TestCheckResourceAttrPair( + "data.stackit_sqlserverflex_instance.instance", "instance_id", + "stackit_sqlserverflex_instance.instance", "instance_id", + ), + resource.TestCheckResourceAttrPair( + "data.stackit_sqlserverflex_user.user", "instance_id", + "stackit_sqlserverflex_user.user", "instance_id", + ), + + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "acl.#", "1"), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "acl.0", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["acl1"])), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor_id"), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.id"), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.description"), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.cpu"), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.ram"), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "replicas", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["replicas"])), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["retention_days"])), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["backup_schedule"])), + + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["kek_key_version"])), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["service_account_email"])), + + // User data + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "project_id", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["project_id"])), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_user.user", "user_id"), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "username", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["username"])), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "roles.#", "1"), + resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "roles.0", testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["role"])), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_user.user", "host"), + resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_user.user", "port"), + ), + }, + // Import + { + ConfigVariables: testConfigVarsMaxPrivatePreview, + ResourceName: "stackit_sqlserverflex_instance.instance", + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources["stackit_sqlserverflex_instance.instance"] + if !ok { + return "", fmt.Errorf("couldn't find resource stackit_sqlserverflex_instance.instance") + } + instanceId, ok := r.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute instance_id") + } + + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId), nil + }, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "backup_schedule", + // Will be added during the import, because it's unknown which attribute defined the flavor + "flavor.cpu", + "flavor.description", + "flavor.id", + "flavor.ram", + }, + ImportStateCheck: func(s []*terraform.InstanceState) error { + if len(s) != 1 { + return fmt.Errorf("expected 1 state, got %d", len(s)) + } + if s[0].Attributes["backup_schedule"] != testutil.ConvertConfigVariable(testConfigVarsMaxPrivatePreview["backup_schedule"]) { + return fmt.Errorf("expected backup_schedule %s, got %s", testConfigVarsMaxPrivatePreview["backup_schedule"], s[0].Attributes["backup_schedule"]) + } + return nil + }, + }, + { + ResourceName: "stackit_sqlserverflex_user.user", + ConfigVariables: testConfigVarsMaxPrivatePreview, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources["stackit_sqlserverflex_user.user"] + if !ok { + return "", fmt.Errorf("couldn't find resource stackit_sqlserverflex_user.user") + } + instanceId, ok := r.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute instance_id") + } + userId, ok := r.Primary.Attributes["user_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute user_id") + } + + return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId, userId), nil + }, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password"}, + }, + // Update + { + Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourcePrivatePreviewConfig, + ConfigVariables: configVarsMaxPrivatePreviewUpdated(), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("stackit_sqlserverflex_instance.instance", plancheck.ResourceActionUpdate), + plancheck.ExpectResourceAction("stackit_sqlserverflex_user.user", plancheck.ResourceActionNoop), + }, + }, + Check: resource.ComposeAggregateTestCheckFunc( + // Instance data + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["project_id"])), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "instance_id"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "name", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["name"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.#", "1"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.0", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["acl1"])), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor_id"), + resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.id"), + resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description"), + resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu"), + resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "replicas", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["replicas"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.class", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["storage_class"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.size", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["storage_size"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "version", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["server_version"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["retention_days"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["backup_schedule"])), + + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_key_id"), + resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "encryption.kek_keyring_id"), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.kek_key_version", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["kek_key_version"])), + resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "encryption.service_account", testutil.ConvertConfigVariable(configVarsMaxPrivatePreviewUpdated()["service_account_email"])), + ), + }, + // Deletion is done by the framework implicitly + }, + }) +} diff --git a/stackit/internal/services/sqlserverflex/testdata/resource-max-private-preview.tf b/stackit/internal/services/sqlserverflex/testdata/resource-max-private-preview.tf new file mode 100644 index 000000000..2873f5972 --- /dev/null +++ b/stackit/internal/services/sqlserverflex/testdata/resource-max-private-preview.tf @@ -0,0 +1,79 @@ +variable "project_id" {} +variable "name" {} +variable "acl1" {} +variable "flavor_id" {} +variable "storage_class" {} +variable "storage_size" {} +variable "access_scope" {} +variable "retention_days" {} +variable "backup_schedule" {} +variable "username" {} +variable "role" {} +variable "server_version" {} +variable "region" {} + +variable "kek_key_version" {} +variable "service_account_email" {} + +variable "keyring_display_name" {} +variable "display_name" {} +variable "protection" {} +variable "algorithm" {} +variable "purpose" {} + + +resource "stackit_kms_keyring" "keyring" { + project_id = var.project_id + display_name = var.keyring_display_name +} + +resource "stackit_kms_key" "key" { + project_id = var.project_id + keyring_id = stackit_kms_keyring.keyring.keyring_id + protection = var.protection + algorithm = var.algorithm + display_name = var.display_name + purpose = var.purpose +} + +resource "stackit_sqlserverflex_instance" "instance" { + project_id = var.project_id + name = var.name + flavor_id = var.flavor_id + storage = { + class = var.storage_class + size = var.storage_size + } + network = { + acl = [var.acl1] + access_scope = var.access_scope + } + retention_days = var.retention_days + version = var.server_version + encryption = { + kek_key_id = stackit_kms_key.key.key_id + kek_keyring_id = stackit_kms_keyring.keyring.keyring_id + kek_key_version = var.kek_key_version + service_account = var.service_account_email + } + backup_schedule = var.backup_schedule + region = var.region +} + +resource "stackit_sqlserverflex_user" "user" { + project_id = stackit_sqlserverflex_instance.instance.project_id + instance_id = stackit_sqlserverflex_instance.instance.instance_id + username = var.username + roles = [var.role] +} + +data "stackit_sqlserverflex_instance" "instance" { + project_id = var.project_id + instance_id = stackit_sqlserverflex_instance.instance.instance_id +} + +data "stackit_sqlserverflex_user" "user" { + project_id = var.project_id + instance_id = stackit_sqlserverflex_instance.instance.instance_id + user_id = stackit_sqlserverflex_user.user.user_id +} diff --git a/stackit/internal/services/sqlserverflex/testdata/resource-max.tf b/stackit/internal/services/sqlserverflex/testdata/resource-max.tf index 2873f5972..9797377e5 100644 --- a/stackit/internal/services/sqlserverflex/testdata/resource-max.tf +++ b/stackit/internal/services/sqlserverflex/testdata/resource-max.tf @@ -12,30 +12,6 @@ variable "role" {} variable "server_version" {} variable "region" {} -variable "kek_key_version" {} -variable "service_account_email" {} - -variable "keyring_display_name" {} -variable "display_name" {} -variable "protection" {} -variable "algorithm" {} -variable "purpose" {} - - -resource "stackit_kms_keyring" "keyring" { - project_id = var.project_id - display_name = var.keyring_display_name -} - -resource "stackit_kms_key" "key" { - project_id = var.project_id - keyring_id = stackit_kms_keyring.keyring.keyring_id - protection = var.protection - algorithm = var.algorithm - display_name = var.display_name - purpose = var.purpose -} - resource "stackit_sqlserverflex_instance" "instance" { project_id = var.project_id name = var.name @@ -48,14 +24,8 @@ resource "stackit_sqlserverflex_instance" "instance" { acl = [var.acl1] access_scope = var.access_scope } - retention_days = var.retention_days - version = var.server_version - encryption = { - kek_key_id = stackit_kms_key.key.key_id - kek_keyring_id = stackit_kms_keyring.keyring.keyring_id - kek_key_version = var.kek_key_version - service_account = var.service_account_email - } + retention_days = var.retention_days + version = var.server_version backup_schedule = var.backup_schedule region = var.region } From c90ed554cb491b8d626eee6c885e9ae87e674f2f Mon Sep 17 00:00:00 2001 From: Jonas Schlecht Date: Fri, 24 Jul 2026 09:26:27 +0200 Subject: [PATCH 5/5] docs(sqlserverflex): update example Relates to STACKITTPR-756 --- docs/resources/sqlserverflex_instance.md | 9 ++++++--- .../resources/stackit_sqlserverflex_instance/resource.tf | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/resources/sqlserverflex_instance.md b/docs/resources/sqlserverflex_instance.md index ddc2aa802..06bfdac1b 100644 --- a/docs/resources/sqlserverflex_instance.md +++ b/docs/resources/sqlserverflex_instance.md @@ -16,14 +16,17 @@ SQLServer Flex instance resource schema. Must have a `region` specified in the p resource "stackit_sqlserverflex_instance" "example" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" name = "example-instance" - acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"] - backup_schedule = "00 00 * * *" flavor_id = "4.16-Single" + backup_schedule = "0 0 * * *" + network = { + acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"] + } storage = { class = "premium-perf2-stackit" size = 5 } - version = 2022 + retention_days = 32 + version = "2022" } ``` diff --git a/examples/resources/stackit_sqlserverflex_instance/resource.tf b/examples/resources/stackit_sqlserverflex_instance/resource.tf index 45bb93b54..6cdbf71b5 100644 --- a/examples/resources/stackit_sqlserverflex_instance/resource.tf +++ b/examples/resources/stackit_sqlserverflex_instance/resource.tf @@ -1,12 +1,15 @@ resource "stackit_sqlserverflex_instance" "example" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" name = "example-instance" - acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"] - backup_schedule = "00 00 * * *" flavor_id = "4.16-Single" + backup_schedule = "0 0 * * *" + network = { + acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"] + } storage = { class = "premium-perf2-stackit" size = 5 } - version = 2022 + retention_days = 32 + version = "2022" }