From f6dacf57cabe54904d832a04f0aa634e48fc8e33 Mon Sep 17 00:00:00 2001 From: GokceGK Date: Tue, 7 Jul 2026 20:52:06 +0200 Subject: [PATCH] feat(redis): region adjustments relates to STACKITTPR-695 --- docs/data-sources/redis_credential.md | 6 +- docs/data-sources/redis_instance.md | 6 +- docs/resources/redis_credential.md | 3 +- docs/resources/redis_instance.md | 3 +- go.mod | 2 +- go.sum | 4 +- .../services/redis/credential/datasource.go | 31 +++++--- .../redis/credential/datasource_test.go | 18 +++-- .../services/redis/credential/resource.go | 62 ++++++++++----- .../redis/credential/resource_test.go | 18 +++-- .../services/redis/instance/datasource.go | 27 +++++-- .../services/redis/instance/resource.go | 78 ++++++++++++------- .../services/redis/instance/resource_test.go | 12 ++- .../internal/services/redis/redis_acc_test.go | 24 ++++-- stackit/internal/services/redis/utils/util.go | 4 +- .../services/redis/utils/util_test.go | 3 +- 16 files changed, 202 insertions(+), 99 deletions(-) diff --git a/docs/data-sources/redis_credential.md b/docs/data-sources/redis_credential.md index bab579b71..ae103bb00 100644 --- a/docs/data-sources/redis_credential.md +++ b/docs/data-sources/redis_credential.md @@ -29,11 +29,15 @@ data "stackit_redis_credential" "example" { - `instance_id` (String) ID of the Redis instance. - `project_id` (String) STACKIT project ID to which the instance is associated. +### Optional + +- `region` (String) The resource region. If not defined, the provider region is used. + ### Read-Only - `host` (String) - `hosts` (List of String) -- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`,`credential_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`region`,`instance_id`,`credential_id`". - `load_balanced_host` (String) - `password` (String, Sensitive) - `port` (Number) diff --git a/docs/data-sources/redis_instance.md b/docs/data-sources/redis_instance.md index 32ead2699..6d830a99d 100644 --- a/docs/data-sources/redis_instance.md +++ b/docs/data-sources/redis_instance.md @@ -27,13 +27,17 @@ data "stackit_redis_instance" "example" { - `instance_id` (String) ID of the Redis instance. - `project_id` (String) STACKIT Project ID to which the instance is associated. +### Optional + +- `region` (String) The resource region. If not defined, the provider region is used. + ### Read-Only - `cf_guid` (String) - `cf_organization_guid` (String) - `cf_space_guid` (String) - `dashboard_url` (String) -- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`region`,`instance_id`". - `image_url` (String) - `name` (String) Instance name. - `parameters` (Attributes) (see [below for nested schema](#nestedatt--parameters)) diff --git a/docs/resources/redis_credential.md b/docs/resources/redis_credential.md index 2c289812b..b85e08dd5 100644 --- a/docs/resources/redis_credential.md +++ b/docs/resources/redis_credential.md @@ -48,6 +48,7 @@ import { ### Optional +- `region` (String) The resource region. If not defined, the provider region is used. - `rotate_when_changed` (Map of String) A map of arbitrary key/value pairs that will force recreation of the resource when they change, enabling resource rotation based on external conditions such as a rotating timestamp. Changing this forces a new resource to be created. ### Read-Only @@ -55,7 +56,7 @@ import { - `credential_id` (String) The credential's ID. - `host` (String) - `hosts` (List of String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`,`credential_id`". +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`,`credential_id`". - `load_balanced_host` (String) - `password` (String, Sensitive) - `port` (Number) diff --git a/docs/resources/redis_instance.md b/docs/resources/redis_instance.md index 40f63b813..7c2d03617 100644 --- a/docs/resources/redis_instance.md +++ b/docs/resources/redis_instance.md @@ -46,6 +46,7 @@ import { ### Optional - `parameters` (Attributes) Configuration parameters. Please note that removing a previously configured field from your Terraform configuration won't replace its value in the API. To update a previously configured field, explicitly set a new value for it. (see [below for nested schema](#nestedatt--parameters)) +- `region` (String) The resource region. If not defined, the provider region is used. ### Read-Only @@ -53,7 +54,7 @@ import { - `cf_organization_guid` (String) - `cf_space_guid` (String) - `dashboard_url` (String) -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`instance_id`". - `image_url` (String) - `instance_id` (String) ID of the Redis instance. - `plan_id` (String) The selected plan ID. diff --git a/go.mod b/go.mod index 2e905b5a9..f25089fce 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/opensearch v1.0.0 github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.10.0 github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v1.1.0 - github.com/stackitcloud/stackit-sdk-go/services/redis v1.0.0 + github.com/stackitcloud/stackit-sdk-go/services/redis v1.1.0 github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.24.0 github.com/stackitcloud/stackit-sdk-go/services/scf v0.9.0 github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.18.1 diff --git a/go.sum b/go.sum index 8841bcbe8..1f54e5dc8 100644 --- a/go.sum +++ b/go.sum @@ -714,8 +714,8 @@ github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.10.0 h1:Ia8FWqG1 github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.10.0/go.mod h1:yzlakB+f8ur4yAHR6lyCABO+HcEtZG3G2Faj6m5/uW8= github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v1.1.0 h1:HRJwodJX4aOn/487zaqJIKw13yIj4T6dn7/kEHLxeTg= github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v1.1.0/go.mod h1:TwfVVynB/+AKbccSOLk2qZpPL1tdK43BBAiACP6EtSg= -github.com/stackitcloud/stackit-sdk-go/services/redis v1.0.0 h1:JsHchCTVpCVUB0KQyjzlFEwJEdorql6pBpXtqQDjtqU= -github.com/stackitcloud/stackit-sdk-go/services/redis v1.0.0/go.mod h1:yjej6QfYoYdRIyKXlmbVz8fZYxbuUdl+QBkvLDPgA4k= +github.com/stackitcloud/stackit-sdk-go/services/redis v1.1.0 h1:ckYMRXAGE2/vaxPeNGEuun9AGcFn/8xuu0ytfsiqfWU= +github.com/stackitcloud/stackit-sdk-go/services/redis v1.1.0/go.mod h1:yjej6QfYoYdRIyKXlmbVz8fZYxbuUdl+QBkvLDPgA4k= github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.24.0 h1:JPP6a0ME1tZXr4iB69d/LtJsCAr58ENBadFaK9f48/c= github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.24.0/go.mod h1:NEz3f+GV5G++BE9/MmZCsXJyCih7jtg0pZuSyG2sLEs= github.com/stackitcloud/stackit-sdk-go/services/scf v0.9.0 h1:pwcGFeU2b6DLOGQGNbbw91zN0qIlBan4d7DGtGGZEEY= diff --git a/stackit/internal/services/redis/credential/datasource.go b/stackit/internal/services/redis/credential/datasource.go index 45f8fd054..420a25ffc 100644 --- a/stackit/internal/services/redis/credential/datasource.go +++ b/stackit/internal/services/redis/credential/datasource.go @@ -18,7 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" ) // Ensure the implementation satisfies the expected interfaces. @@ -31,6 +31,7 @@ type DataSourceModel struct { CredentialId types.String `tfsdk:"credential_id"` InstanceId types.String `tfsdk:"instance_id"` ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` Host types.String `tfsdk:"host"` Hosts types.List `tfsdk:"hosts"` LoadBalancedHost types.String `tfsdk:"load_balanced_host"` @@ -47,7 +48,8 @@ func NewCredentialDataSource() datasource.DataSource { // credentialDataSource is the data source implementation. type credentialDataSource struct { - client *redis.APIClient + client *redis.APIClient + providerData core.ProviderData } // Metadata returns the data source type name. @@ -57,12 +59,13 @@ func (r *credentialDataSource) Metadata(_ context.Context, req datasource.Metada // Configure adds the provider configured client to the data source. func (r *credentialDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + var ok bool + r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) if !ok { return } - apiClient := redisUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + apiClient := redisUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -74,11 +77,12 @@ func (r *credentialDataSource) Configure(ctx context.Context, req datasource.Con func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { descriptions := map[string]string{ //nolint:gosec // description for credential id "main": "Redis credential data source schema. Must have a `region` specified in the provider configuration.", - "id": "Terraform's internal data source. identifier. It is structured as \"`project_id`,`instance_id`,`credential_id`\".", + "id": "Terraform's internal data source. identifier. It is structured as \"`project_id`,`region`,`instance_id`,`credential_id`\".", "credential_id": "The credential's ID.", "instance_id": "ID of the Redis instance.", "project_id": "STACKIT project ID to which the instance is associated.", "uri": "Connection URI.", + "region": "The resource region. If not defined, the provider region is used.", } resp.Schema = schema.Schema{ @@ -137,6 +141,12 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ "username": schema.StringAttribute{ Computed: true, }, + "region": schema.StringAttribute{ + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, + Description: descriptions["region"], + }, }, } } @@ -153,13 +163,15 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) instanceId := model.InstanceId.ValueString() credentialId := model.CredentialId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "credential_id", credentialId) - recordSetResp, err := r.client.DefaultAPI.GetCredentials(ctx, projectId, instanceId, credentialId).Execute() + recordSetResp, err := r.client.DefaultAPI.GetCredentials(ctx, projectId, region, instanceId, credentialId).Execute() if err != nil { utils.LogError( ctx, @@ -178,7 +190,7 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ ctx = core.LogResponse(ctx) // Map response body to schema - err = mapDataSourceFields(ctx, recordSetResp, &model) + err = mapDataSourceFields(ctx, recordSetResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Processing API payload: %v", err)) return @@ -193,7 +205,7 @@ func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequ tflog.Info(ctx, "Redis credential read") } -func mapDataSourceFields(ctx context.Context, credentialsResp *redis.CredentialsResponse, model *DataSourceModel) error { +func mapDataSourceFields(ctx context.Context, credentialsResp *redis.CredentialsResponse, model *DataSourceModel, region string) error { if credentialsResp == nil { return fmt.Errorf("response input is nil") } @@ -214,7 +226,8 @@ func mapDataSourceFields(ctx context.Context, credentialsResp *redis.Credentials return fmt.Errorf("credentials id not present") } - model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.InstanceId.ValueString(), credentialId) + model.Region = types.StringValue(region) + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.Region.ValueString(), model.InstanceId.ValueString(), credentialId) modelHosts, err := utils.ListValueToStringSlice(model.Hosts) if err != nil { diff --git a/stackit/internal/services/redis/credential/datasource_test.go b/stackit/internal/services/redis/credential/datasource_test.go index 057435812..3a60aa745 100644 --- a/stackit/internal/services/redis/credential/datasource_test.go +++ b/stackit/internal/services/redis/credential/datasource_test.go @@ -2,15 +2,17 @@ package redis import ( "context" + "fmt" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" ) func TestMapDataSourceFields(t *testing.T) { + const testRegion = "eu01" tests := []struct { description string state DataSourceModel @@ -29,10 +31,11 @@ func TestMapDataSourceFields(t *testing.T) { Raw: &redis.RawCredentials{}, }, DataSourceModel{ - Id: types.StringValue("pid,iid,cid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid,cid", testRegion)), CredentialId: types.StringValue("cid"), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), Host: types.StringValue(""), Hosts: types.ListNull(types.StringType), LoadBalancedHost: types.StringNull(), @@ -67,10 +70,11 @@ func TestMapDataSourceFields(t *testing.T) { }, }, DataSourceModel{ - Id: types.StringValue("pid,iid,cid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid,cid", testRegion)), CredentialId: types.StringValue("cid"), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), Host: types.StringValue("host"), Hosts: types.ListValueMust(types.StringType, []attr.Value{ types.StringValue("host_1"), @@ -114,10 +118,11 @@ func TestMapDataSourceFields(t *testing.T) { }, }, DataSourceModel{ - Id: types.StringValue("pid,iid,cid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid,cid", testRegion)), CredentialId: types.StringValue("cid"), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), Host: types.StringValue("host"), Hosts: types.ListValueMust(types.StringType, []attr.Value{ types.StringValue("host_2"), @@ -153,10 +158,11 @@ func TestMapDataSourceFields(t *testing.T) { }, }, DataSourceModel{ - Id: types.StringValue("pid,iid,cid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid,cid", testRegion)), CredentialId: types.StringValue("cid"), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), Host: types.StringValue(""), Hosts: types.ListValueMust(types.StringType, []attr.Value{}), LoadBalancedHost: types.StringNull(), @@ -202,7 +208,7 @@ func TestMapDataSourceFields(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - err := mapDataSourceFields(context.Background(), tt.input, &tt.state) + err := mapDataSourceFields(context.Background(), tt.input, &tt.state, testRegion) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } diff --git a/stackit/internal/services/redis/credential/resource.go b/stackit/internal/services/redis/credential/resource.go index 64aaf8fb9..d94b57591 100644 --- a/stackit/internal/services/redis/credential/resource.go +++ b/stackit/internal/services/redis/credential/resource.go @@ -25,8 +25,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" - "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api/wait" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" + "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api/wait" ) // Ensure the implementation satisfies the expected interfaces. @@ -41,6 +41,7 @@ type Model struct { CredentialId types.String `tfsdk:"credential_id"` InstanceId types.String `tfsdk:"instance_id"` ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` Host types.String `tfsdk:"host"` Hosts types.List `tfsdk:"hosts"` LoadBalancedHost types.String `tfsdk:"load_balanced_host"` @@ -62,7 +63,8 @@ func NewCredentialResource() resource.Resource { // credentialResource is the resource implementation. type credentialResource struct { - client *redis.APIClient + client *redis.APIClient + providerData core.ProviderData } // Metadata returns the resource type name. @@ -72,12 +74,13 @@ func (r *credentialResource) Metadata(_ context.Context, req resource.MetadataRe // Configure adds the provider configured client to the resource. func (r *credentialResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + var ok bool + r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) if !ok { return } - apiClient := redisUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + apiClient := redisUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -89,11 +92,12 @@ func (r *credentialResource) Configure(ctx context.Context, req resource.Configu func (r *credentialResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { descriptions := map[string]string{ //nolint:gosec // description for credential id "main": "Redis credential resource schema. Must have a `region` specified in the provider configuration.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`instance_id`,`credential_id`\".", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`credential_id`\".", "credential_id": "The credential's ID.", "instance_id": "ID of the Redis instance.", "project_id": "STACKIT Project ID to which the instance is associated.", "uri": "Connection URI.", + "region": "The resource region. If not defined, the provider region is used.", } resp.Schema = schema.Schema{ @@ -178,6 +182,15 @@ func (r *credentialResource) Schema(_ context.Context, _ resource.SchemaRequest, mapplanmodifier.RequiresReplace(), }, }, + "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(), + }, + }, }, } } @@ -195,11 +208,13 @@ func (r *credentialResource) Create(ctx context.Context, req resource.CreateRequ projectId := model.ProjectId.ValueString() instanceId := model.InstanceId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) // Create new recordset - credentialsResp, err := r.client.DefaultAPI.CreateCredentials(ctx, projectId, instanceId).Execute() + credentialsResp, err := r.client.DefaultAPI.CreateCredentials(ctx, projectId, region, instanceId).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Calling API: %v", err)) return @@ -215,6 +230,7 @@ func (r *credentialResource) Create(ctx context.Context, req resource.CreateRequ // Write id attributes to state before polling via the wait handler - just in case anything goes wrong during the wait handler ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ "project_id": projectId, + "region": region, "instance_id": instanceId, "credential_id": credentialId, }) @@ -222,14 +238,14 @@ func (r *credentialResource) Create(ctx context.Context, req resource.CreateRequ return } - waitResp, err := wait.CreateCredentialsWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId, credentialId).WaitWithContext(ctx) + waitResp, err := wait.CreateCredentialsWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId, credentialId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Instance creation waiting: %v", err)) return } // Map response body to schema - err = mapFields(ctx, waitResp, &model) + err = mapFields(ctx, waitResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Processing API payload: %v", err)) return @@ -254,6 +270,7 @@ func (r *credentialResource) Read(ctx context.Context, req resource.ReadRequest, ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) instanceId := model.InstanceId.ValueString() credentialId := model.CredentialId.ValueString() if credentialId == "" { @@ -262,10 +279,11 @@ func (r *credentialResource) Read(ctx context.Context, req resource.ReadRequest, return } ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "credential_id", credentialId) - recordSetResp, err := r.client.DefaultAPI.GetCredentials(ctx, projectId, instanceId, credentialId).Execute() + recordSetResp, err := r.client.DefaultAPI.GetCredentials(ctx, projectId, region, instanceId, credentialId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { @@ -279,7 +297,7 @@ func (r *credentialResource) Read(ctx context.Context, req resource.ReadRequest, ctx = core.LogResponse(ctx) // Map response body to schema - err = mapFields(ctx, recordSetResp, &model) + err = mapFields(ctx, recordSetResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Processing API payload: %v", err)) return @@ -312,14 +330,16 @@ func (r *credentialResource) Delete(ctx context.Context, req resource.DeleteRequ ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) instanceId := model.InstanceId.ValueString() credentialId := model.CredentialId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "credential_id", credentialId) // Delete existing record set - err := r.client.DefaultAPI.DeleteCredentials(ctx, projectId, instanceId, credentialId).Execute() + err := r.client.DefaultAPI.DeleteCredentials(ctx, projectId, region, instanceId, credentialId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { @@ -331,7 +351,7 @@ func (r *credentialResource) Delete(ctx context.Context, req resource.DeleteRequ ctx = core.LogResponse(ctx) - _, err = wait.DeleteCredentialsWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId, credentialId).WaitWithContext(ctx) + _, err = wait.DeleteCredentialsWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId, credentialId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting credential", fmt.Sprintf("Instance deletion waiting: %v", err)) return @@ -340,26 +360,27 @@ func (r *credentialResource) Delete(ctx context.Context, req resource.DeleteRequ } // ImportState imports a resource into the Terraform state on success. -// The expected format of the resource import identifier is: project_id,instance_id,credential_id +// The expected format of the resource import identifier is: project_id,region,instance_id,credential_id func (r *credentialResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { idParts := strings.Split(req.ID, core.Separator) - if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" { core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing credential", - fmt.Sprintf("Expected import identifier with format [project_id],[instance_id],[credential_id], got %q", req.ID), + fmt.Sprintf("Expected import identifier with format [project_id],[region],[instance_id],[credential_id], got %q", req.ID), ) return } ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ "project_id": idParts[0], - "instance_id": idParts[1], - "credential_id": idParts[2], + "region": idParts[1], + "instance_id": idParts[2], + "credential_id": idParts[3], }) tflog.Info(ctx, "Redis credential state imported") } -func mapFields(ctx context.Context, credentialsResp *redis.CredentialsResponse, model *Model) error { +func mapFields(ctx context.Context, credentialsResp *redis.CredentialsResponse, model *Model, region string) error { if credentialsResp == nil { return fmt.Errorf("response input is nil") } @@ -380,7 +401,8 @@ func mapFields(ctx context.Context, credentialsResp *redis.CredentialsResponse, return fmt.Errorf("credentials id not present") } - model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.InstanceId.ValueString(), credentialId) + model.Region = types.StringValue(region) + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.Region.ValueString(), model.InstanceId.ValueString(), credentialId) modelHosts, err := utils.ListValueToStringSlice(model.Hosts) if err != nil { diff --git a/stackit/internal/services/redis/credential/resource_test.go b/stackit/internal/services/redis/credential/resource_test.go index 4b847cf8c..05a7d8d27 100644 --- a/stackit/internal/services/redis/credential/resource_test.go +++ b/stackit/internal/services/redis/credential/resource_test.go @@ -2,15 +2,17 @@ package redis import ( "context" + "fmt" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" ) func TestMapFields(t *testing.T) { + const testRegion = "eu01" tests := []struct { description string state Model @@ -30,10 +32,11 @@ func TestMapFields(t *testing.T) { Raw: &redis.RawCredentials{}, }, Model{ - Id: types.StringValue("pid,iid,cid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid,cid", testRegion)), CredentialId: types.StringValue("cid"), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), Host: types.StringValue(""), Hosts: types.ListNull(types.StringType), LoadBalancedHost: types.StringNull(), @@ -70,10 +73,11 @@ func TestMapFields(t *testing.T) { }, }, Model{ - Id: types.StringValue("pid,iid,cid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid,cid", testRegion)), CredentialId: types.StringValue("cid"), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), Host: types.StringValue("host"), Hosts: types.ListValueMust(types.StringType, []attr.Value{ types.StringValue("host_1"), @@ -119,10 +123,11 @@ func TestMapFields(t *testing.T) { }, }, Model{ - Id: types.StringValue("pid,iid,cid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid,cid", testRegion)), CredentialId: types.StringValue("cid"), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), Host: types.StringValue("host"), Hosts: types.ListValueMust(types.StringType, []attr.Value{ types.StringValue("host_2"), @@ -160,10 +165,11 @@ func TestMapFields(t *testing.T) { }, }, Model{ - Id: types.StringValue("pid,iid,cid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid,cid", testRegion)), CredentialId: types.StringValue("cid"), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), Host: types.StringValue(""), Hosts: types.ListValueMust(types.StringType, []attr.Value{}), LoadBalancedHost: types.StringNull(), @@ -213,7 +219,7 @@ func TestMapFields(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - err := mapFields(context.Background(), tt.input, &tt.state) + err := mapFields(context.Background(), tt.input, &tt.state, testRegion) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } diff --git a/stackit/internal/services/redis/instance/datasource.go b/stackit/internal/services/redis/instance/datasource.go index 98cf680e4..473255973 100644 --- a/stackit/internal/services/redis/instance/datasource.go +++ b/stackit/internal/services/redis/instance/datasource.go @@ -18,7 +18,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" ) // Ensure the implementation satisfies the expected interfaces. @@ -33,7 +33,8 @@ func NewInstanceDataSource() datasource.DataSource { // instanceDataSource is the data source implementation. type instanceDataSource struct { - client *redis.APIClient + client *redis.APIClient + providerData core.ProviderData } // Metadata returns the data source type name. @@ -43,12 +44,13 @@ func (r *instanceDataSource) Metadata(_ context.Context, req datasource.Metadata // Configure adds the provider configured client to the data source. func (r *instanceDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + var ok bool + r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) if !ok { return } - apiClient := redisUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + apiClient := redisUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -60,13 +62,14 @@ func (r *instanceDataSource) Configure(ctx context.Context, req datasource.Confi func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { descriptions := map[string]string{ "main": "Redis instance data source schema. Must have a `region` specified in the provider configuration.", - "id": "Terraform's internal data source. identifier. It is structured as \"`project_id`,`instance_id`\".", + "id": "Terraform's internal data source. identifier. It is structured as \"`project_id`,`region`,`instance_id`\".", "instance_id": "ID of the Redis instance.", "project_id": "STACKIT Project ID to which the instance is associated.", "name": "Instance name.", "version": "The service version.", "plan_name": "The selected plan name.", "plan_id": "The selected plan ID.", + "region": "The resource region. If not defined, the provider region is used.", } parametersDescriptions := map[string]string{ @@ -248,6 +251,12 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques Description: descriptions["cf_organization_guid"], Computed: true, }, + "region": schema.StringAttribute{ + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, + Description: descriptions["region"], + }, }, } } @@ -265,10 +274,12 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques projectId := model.ProjectId.ValueString() instanceId := model.InstanceId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) - instanceResp, err := r.client.DefaultAPI.GetInstance(ctx, projectId, instanceId).Execute() + instanceResp, err := r.client.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute() if err != nil { utils.LogError( ctx, @@ -287,14 +298,14 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques ctx = core.LogResponse(ctx) - err = mapFields(instanceResp, &model) + err = mapFields(instanceResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) return } // Compute and store values not present in the API response - err = loadPlanNameAndVersion(ctx, r.client, &model) + err = loadPlanNameAndVersion(ctx, r.client, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Loading service plan details: %v", err)) return diff --git a/stackit/internal/services/redis/instance/resource.go b/stackit/internal/services/redis/instance/resource.go index 6b0a4d281..4982d8d1f 100644 --- a/stackit/internal/services/redis/instance/resource.go +++ b/stackit/internal/services/redis/instance/resource.go @@ -28,8 +28,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" - "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api/wait" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" + "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api/wait" ) // Ensure the implementation satisfies the expected interfaces. @@ -43,6 +43,7 @@ type Model struct { Id types.String `tfsdk:"id"` // needed by TF InstanceId types.String `tfsdk:"instance_id"` ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` CfGuid types.String `tfsdk:"cf_guid"` CfSpaceGuid types.String `tfsdk:"cf_space_guid"` DashboardUrl types.String `tfsdk:"dashboard_url"` @@ -114,7 +115,8 @@ func NewInstanceResource() resource.Resource { // instanceResource is the resource implementation. type instanceResource struct { - client *redis.APIClient + client *redis.APIClient + providerData core.ProviderData } // Metadata returns the resource type name. @@ -124,12 +126,13 @@ func (r *instanceResource) Metadata(_ context.Context, req resource.MetadataRequ // Configure adds the provider configured client to the resource. func (r *instanceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + var ok bool + r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) if !ok { return } - apiClient := redisUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + apiClient := redisUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -141,7 +144,7 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { descriptions := map[string]string{ "main": "Redis instance resource schema. Must have a `region` specified in the provider configuration.", - "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".", "instance_id": "ID of the Redis instance.", "project_id": "STACKIT project ID to which the instance is associated.", "name": "Instance name.", @@ -149,6 +152,7 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "plan_name": "The selected plan name.", "plan_id": "The selected plan ID.", "parameters": "Configuration parameters. Please note that removing a previously configured field from your Terraform configuration won't replace its value in the API. To update a previously configured field, explicitly set a new value for it.", + "region": "The resource region. If not defined, the provider region is used.", } parametersDescriptions := map[string]string{ @@ -385,6 +389,15 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r stringplanmodifier.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(), + }, + }, }, } } @@ -401,7 +414,9 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) var parameters *parametersModel if !(model.Parameters.IsNull() || model.Parameters.IsUnknown()) { @@ -426,7 +441,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques return } // Create new instance - createResp, err := r.client.DefaultAPI.CreateInstance(ctx, projectId).CreateInstancePayload(*payload).Execute() + createResp, err := r.client.DefaultAPI.CreateInstance(ctx, projectId, region).CreateInstancePayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err)) return @@ -443,18 +458,19 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ "project_id": projectId, "instance_id": instanceId, + "region": region, }) if resp.Diagnostics.HasError() { return } - waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId).WaitWithContext(ctx) + waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Instance creation waiting: %v", err)) return } // Map response body to schema - err = mapFields(waitResp, &model) + err = mapFields(waitResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -487,10 +503,13 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r resp.State.RemoveResource(ctx) return } + region := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) - instanceResp, err := r.client.DefaultAPI.GetInstance(ctx, projectId, instanceId).Execute() + instanceResp, err := r.client.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) && (oapiErr.StatusCode == http.StatusNotFound || oapiErr.StatusCode == http.StatusGone) { @@ -504,14 +523,14 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r ctx = core.LogResponse(ctx) // Map response body to schema - err = mapFields(instanceResp, &model) + err = mapFields(instanceResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) return } // Compute and store values not present in the API response - err = loadPlanNameAndVersion(ctx, r.client, &model) + err = loadPlanNameAndVersion(ctx, r.client, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Loading service plan details: %v", err)) return @@ -539,8 +558,10 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques projectId := model.ProjectId.ValueString() instanceId := model.InstanceId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) var parameters *parametersModel if !(model.Parameters.IsNull() || model.Parameters.IsUnknown()) { @@ -565,7 +586,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques return } // Update existing instance - err = r.client.DefaultAPI.PartialUpdateInstance(ctx, projectId, instanceId).PartialUpdateInstancePayload(*payload).Execute() + err = r.client.DefaultAPI.PartialUpdateInstance(ctx, projectId, instanceId, region).PartialUpdateInstancePayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Calling API: %v", err)) return @@ -573,14 +594,14 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques ctx = core.LogResponse(ctx) - waitResp, err := wait.PartialUpdateInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId).WaitWithContext(ctx) + waitResp, err := wait.PartialUpdateInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Instance update waiting: %v", err)) return } // Map response body to schema - err = mapFields(waitResp, &model) + err = mapFields(waitResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -608,11 +629,13 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques projectId := model.ProjectId.ValueString() instanceId := model.InstanceId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) // Delete existing instance - err := r.client.DefaultAPI.DeleteInstance(ctx, projectId, instanceId).Execute() + err := r.client.DefaultAPI.DeleteInstance(ctx, projectId, region, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { @@ -625,7 +648,7 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques ctx = core.LogResponse(ctx) - _, err = wait.DeleteInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId).WaitWithContext(ctx) + _, err = wait.DeleteInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Instance deletion waiting: %v", err)) return @@ -634,26 +657,27 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques } // ImportState imports a resource into the Terraform state on success. -// The expected format of the resource import identifier is: project_id,instance_id +// The expected format of the resource import identifier is: project_id,region,instance_id func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { idParts := strings.Split(req.ID, core.Separator) - if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing instance", - fmt.Sprintf("Expected import identifier with format: [project_id],[instance_id] Got: %q", req.ID), + fmt.Sprintf("Expected import identifier with format: [project_id],[region],[instance_id] Got: %q", req.ID), ) return } ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ "project_id": idParts[0], - "instance_id": idParts[1], + "region": idParts[1], + "instance_id": idParts[2], }) tflog.Info(ctx, "Redis instance state imported") } -func mapFields(instance *redis.Instance, model *Model) error { +func mapFields(instance *redis.Instance, model *Model, region string) error { if instance == nil { return fmt.Errorf("response input is nil") } @@ -670,7 +694,8 @@ func mapFields(instance *redis.Instance, model *Model) error { return fmt.Errorf("instance id not present") } - model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), instanceId) + model.Region = types.StringValue(region) + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId) model.InstanceId = types.StringValue(instanceId) model.PlanId = types.StringValue(instance.PlanId) model.CfGuid = types.StringValue(instance.CfGuid) @@ -883,7 +908,8 @@ func toInstanceParams(parameters *parametersModel) (*redis.InstanceParameters, e func (r *instanceResource) loadPlanId(ctx context.Context, model *Model) error { projectId := model.ProjectId.ValueString() - res, err := r.client.DefaultAPI.ListOfferings(ctx, projectId).Execute() + region := r.providerData.GetRegionWithOverride(model.Region) + res, err := r.client.DefaultAPI.ListOfferings(ctx, projectId, region).Execute() if err != nil { return fmt.Errorf("getting Redis offerings: %w", err) } @@ -918,10 +944,10 @@ func (r *instanceResource) loadPlanId(ctx context.Context, model *Model) error { return fmt.Errorf("couldn't find plan_name '%s' for version %s, available names are: %s", planName, version, availablePlanNames) } -func loadPlanNameAndVersion(ctx context.Context, client *redis.APIClient, model *Model) error { +func loadPlanNameAndVersion(ctx context.Context, client *redis.APIClient, model *Model, region string) error { projectId := model.ProjectId.ValueString() planId := model.PlanId.ValueString() - res, err := client.DefaultAPI.ListOfferings(ctx, projectId).Execute() + res, err := client.DefaultAPI.ListOfferings(ctx, projectId, region).Execute() if err != nil { return fmt.Errorf("getting Redis offerings: %w", err) } diff --git a/stackit/internal/services/redis/instance/resource_test.go b/stackit/internal/services/redis/instance/resource_test.go index eb6338762..5e7df77ea 100644 --- a/stackit/internal/services/redis/instance/resource_test.go +++ b/stackit/internal/services/redis/instance/resource_test.go @@ -2,13 +2,14 @@ package redis import ( "context" + "fmt" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" ) var fixtureModelParameters = types.ObjectValueMust(parametersTypes, map[string]attr.Value{ @@ -68,6 +69,7 @@ var fixtureInstanceParameters = redis.InstanceParameters{ } func TestMapFields(t *testing.T) { + const testRegion = "eu01" tests := []struct { description string input *redis.Instance @@ -78,9 +80,10 @@ func TestMapFields(t *testing.T) { "default_values", &redis.Instance{}, Model{ - Id: types.StringValue("pid,iid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid", testRegion)), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), PlanId: types.StringValue(""), Name: types.StringValue(""), CfGuid: types.StringValue(""), @@ -129,9 +132,10 @@ func TestMapFields(t *testing.T) { }, }, Model{ - Id: types.StringValue("pid,iid"), + Id: types.StringValue(fmt.Sprintf("pid,%s,iid", testRegion)), InstanceId: types.StringValue("iid"), ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), PlanId: types.StringValue("plan"), Name: types.StringValue("name"), CfGuid: types.StringValue("cf"), @@ -182,7 +186,7 @@ func TestMapFields(t *testing.T) { ProjectId: tt.expected.ProjectId, InstanceId: tt.expected.InstanceId, } - err := mapFields(tt.input, state) + err := mapFields(tt.input, state, testRegion) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } diff --git a/stackit/internal/services/redis/redis_acc_test.go b/stackit/internal/services/redis/redis_acc_test.go index 1c55e2569..9e58f9b66 100644 --- a/stackit/internal/services/redis/redis_acc_test.go +++ b/stackit/internal/services/redis/redis_acc_test.go @@ -10,8 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stackitcloud/stackit-sdk-go/core/utils" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" - "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api/wait" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" + "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" @@ -209,11 +209,15 @@ func TestAccRedisResource(t *testing.T) { if !ok { return "", fmt.Errorf("couldn't find resource stackit_redis_instance.instance") } + region, ok := r.Primary.Attributes["region"] + if !ok { + return "", fmt.Errorf("couldn't find attribute region") + } instanceId, ok := r.Primary.Attributes["instance_id"] if !ok { return "", fmt.Errorf("couldn't find attribute instance_id") } - return fmt.Sprintf("%s,%s", testutil.ProjectId, instanceId), nil + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, region, instanceId), nil }, ImportState: true, ImportStateVerify: true, @@ -225,6 +229,10 @@ func TestAccRedisResource(t *testing.T) { if !ok { return "", fmt.Errorf("couldn't find resource stackit_redis_credential.credential") } + region, ok := r.Primary.Attributes["region"] + if !ok { + return "", fmt.Errorf("couldn't find attribute region") + } instanceId, ok := r.Primary.Attributes["instance_id"] if !ok { return "", fmt.Errorf("couldn't find attribute instance_id") @@ -233,7 +241,7 @@ func TestAccRedisResource(t *testing.T) { if !ok { return "", fmt.Errorf("couldn't find attribute credential_id") } - return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, instanceId, credentialId), nil + return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, region, instanceId, credentialId), nil }, ImportState: true, ImportStateVerify: true, @@ -274,7 +282,7 @@ func checkInstanceDeleteSuccess(i *redis.Instance) bool { func testAccCheckRedisDestroy(s *terraform.State) error { ctx := context.Background() - client, err := redis.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.RedisCustomEndpoint, true)...) + client, err := redis.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.RedisCustomEndpoint, false)...) if err != nil { return fmt.Errorf("creating client: %w", err) } @@ -289,7 +297,7 @@ func testAccCheckRedisDestroy(s *terraform.State) error { instancesToDestroy = append(instancesToDestroy, instanceId) } - instancesResp, err := client.DefaultAPI.ListInstances(ctx, testutil.ProjectId).Execute() + instancesResp, err := client.DefaultAPI.ListInstances(ctx, testutil.ProjectId, "eu01").Execute() if err != nil { return fmt.Errorf("getting instancesResp: %w", err) } @@ -301,11 +309,11 @@ func testAccCheckRedisDestroy(s *terraform.State) error { } if utils.Contains(instancesToDestroy, *instances[i].InstanceId) { if !checkInstanceDeleteSuccess(&instances[i]) { - err := client.DefaultAPI.DeleteInstance(ctx, testutil.ProjectId, *instances[i].InstanceId).Execute() + err := client.DefaultAPI.DeleteInstance(ctx, testutil.ProjectId, "eu01", *instances[i].InstanceId).Execute() if err != nil { return fmt.Errorf("destroying instance %s during CheckDestroy: %w", *instances[i].InstanceId, err) } - _, err = wait.DeleteInstanceWaitHandler(ctx, client.DefaultAPI, testutil.ProjectId, *instances[i].InstanceId).WaitWithContext(ctx) + _, err = wait.DeleteInstanceWaitHandler(ctx, client.DefaultAPI, testutil.ProjectId, "eu01", *instances[i].InstanceId).WaitWithContext(ctx) if err != nil { return fmt.Errorf("destroying instance %s during CheckDestroy: waiting for deletion %w", *instances[i].InstanceId, err) } diff --git a/stackit/internal/services/redis/utils/util.go b/stackit/internal/services/redis/utils/util.go index b52e685a3..7e6f301ec 100644 --- a/stackit/internal/services/redis/utils/util.go +++ b/stackit/internal/services/redis/utils/util.go @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/stackitcloud/stackit-sdk-go/core/config" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" @@ -19,8 +19,6 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags } if providerData.RedisCustomEndpoint != "" { apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.RedisCustomEndpoint)) - } else { - apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion())) } apiClient, err := redis.NewAPIClient(apiClientConfigOptions...) if err != nil { diff --git a/stackit/internal/services/redis/utils/util_test.go b/stackit/internal/services/redis/utils/util_test.go index e422a525a..391ab9a20 100644 --- a/stackit/internal/services/redis/utils/util_test.go +++ b/stackit/internal/services/redis/utils/util_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" "github.com/stackitcloud/stackit-sdk-go/core/config" - redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api" + redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" @@ -46,7 +46,6 @@ func TestConfigureClient(t *testing.T) { }, expected: func() *redis.APIClient { apiClient, err := redis.NewAPIClient( - config.WithRegion("eu01"), utils.UserAgentConfigOption(testVersion), ) if err != nil {