feat(mongodbflex): region adjustment (#914)

relates to STACKITTPR-282
This commit is contained in:
Ruben Hönle 2025-07-21 14:37:47 +02:00 committed by GitHub
parent 6555a99a6d
commit 189fa1ece7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 388 additions and 175 deletions

View file

@ -27,12 +27,16 @@ data "stackit_mongodbflex_instance" "example" {
- `instance_id` (String) ID of the MongoDB Flex instance. - `instance_id` (String) ID of the MongoDB Flex instance.
- `project_id` (String) STACKIT project ID to which the instance is associated. - `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 ### Read-Only
- `acl` (List of String) The Access Control List (ACL) for the MongoDB Flex instance. - `acl` (List of String) The Access Control List (ACL) for the MongoDB Flex instance.
- `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *"). - `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *").
- `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor)) - `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor))
- `id` (String) Terraform's internal data source ID. It is structured as "`project_id`,`instance_id`". - `id` (String) Terraform's internal data source ID. It is structured as "`project_id`,`region`,`instance_id`".
- `name` (String) Instance name. - `name` (String) Instance name.
- `options` (Attributes) Custom parameters for the MongoDB Flex instance. (see [below for nested schema](#nestedatt--options)) - `options` (Attributes) Custom parameters for the MongoDB Flex instance. (see [below for nested schema](#nestedatt--options))
- `replicas` (Number) - `replicas` (Number)

View file

@ -29,11 +29,15 @@ data "stackit_mongodbflex_user" "example" {
- `project_id` (String) STACKIT project ID to which the instance is associated. - `project_id` (String) STACKIT project ID to which the instance is associated.
- `user_id` (String) User ID. - `user_id` (String) User ID.
### Optional
- `region` (String) The resource region. If not defined, the provider region is used.
### Read-Only ### Read-Only
- `database` (String) - `database` (String)
- `host` (String) - `host` (String)
- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`instance_id`,`user_id`". - `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`instance_id`,`user_id`".
- `port` (Number) - `port` (Number)
- `roles` (Set of String) - `roles` (Set of String)
- `username` (String) - `username` (String)

View file

@ -51,9 +51,13 @@ resource "stackit_mongodbflex_instance" "example" {
- `storage` (Attributes) (see [below for nested schema](#nestedatt--storage)) - `storage` (Attributes) (see [below for nested schema](#nestedatt--storage))
- `version` (String) - `version` (String)
### Optional
- `region` (String) The resource region. If not defined, the provider region is used.
### Read-Only ### Read-Only
- `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`".
- `instance_id` (String) ID of the MongoDB Flex instance. - `instance_id` (String) ID of the MongoDB Flex instance.
<a id="nestedatt--flavor"></a> <a id="nestedatt--flavor"></a>

View file

@ -34,6 +34,7 @@ resource "stackit_mongodbflex_user" "example" {
### Optional ### Optional
- `region` (String) The resource region. If not defined, the provider region is used.
- `username` (String) - `username` (String)
### Read-Only ### Read-Only

2
go.mod
View file

@ -21,7 +21,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0 github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0 github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0
github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.0 github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.0
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.2.1 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.4.0
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.0 github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.0
github.com/stackitcloud/stackit-sdk-go/services/observability v0.8.0 github.com/stackitcloud/stackit-sdk-go/services/observability v0.8.0
github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.0 github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.0

4
go.sum
View file

@ -172,8 +172,8 @@ github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0 h1:vxk6ztgzUIPMk
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0/go.mod h1:Pb8IEV5/jP8k75dVcN5cn3kP7PHTy/4KXXKpG76oj4U= github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0/go.mod h1:Pb8IEV5/jP8k75dVcN5cn3kP7PHTy/4KXXKpG76oj4U=
github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.0 h1:WMnCRB+zEWK5qq3d+MT5+nysuyXs+694/kSxgVYaXfk= github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.0 h1:WMnCRB+zEWK5qq3d+MT5+nysuyXs+694/kSxgVYaXfk=
github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.0/go.mod h1:3p68DR66MNYgc2YcMF7B23MmySVShQ6g7f+7EkZfpPY= github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.0/go.mod h1:3p68DR66MNYgc2YcMF7B23MmySVShQ6g7f+7EkZfpPY=
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.2.1 h1:TWz7qJ4Mg5pquDXODSZ1dzhS95ZYn3w1aKjuRU2VqCg= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.4.0 h1:il4vBOejtX0//CWuY6CDpFfPpDIvin5V9QIaKvyXV/M=
github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.2.1/go.mod h1:U1Zf/S9IuDvRJq1tRKFT/bsJd4qxYzwtukqX3TL++Mw= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.4.0/go.mod h1:U1Zf/S9IuDvRJq1tRKFT/bsJd4qxYzwtukqX3TL++Mw=
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.0 h1:bK5FDpSGfUKDXNkqOoiqOU9hua2YfcdYsGS4zQQ9wg0= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.0 h1:bK5FDpSGfUKDXNkqOoiqOU9hua2YfcdYsGS4zQQ9wg0=
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.0/go.mod h1:Q2SJXlZTksvNAb1QnpGNfDSw/OMPN9uopaKuptUGhO8= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.0/go.mod h1:Q2SJXlZTksvNAb1QnpGNfDSw/OMPN9uopaKuptUGhO8=
github.com/stackitcloud/stackit-sdk-go/services/observability v0.8.0 h1:8v9Iupkc3meXcVPMpteXDz07YzY2M5jud50vEtAvbs8= github.com/stackitcloud/stackit-sdk-go/services/observability v0.8.0 h1:8v9Iupkc3meXcVPMpteXDz07YzY2M5jud50vEtAvbs8=

View file

@ -33,34 +33,36 @@ func NewInstanceDataSource() datasource.DataSource {
// instanceDataSource is the data source implementation. // instanceDataSource is the data source implementation.
type instanceDataSource struct { type instanceDataSource struct {
client *mongodbflex.APIClient client *mongodbflex.APIClient
providerData core.ProviderData
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.
func (r *instanceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { func (d *instanceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_mongodbflex_instance" resp.TypeName = req.ProviderTypeName + "_mongodbflex_instance"
} }
// Configure adds the provider configured client to the data source. // Configure adds the provider configured client to the data source.
func (r *instanceDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { func (d *instanceDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) var ok bool
d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok { if !ok {
return return
} }
apiClient := mongodbflexUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) apiClient := mongodbflexUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
r.client = apiClient d.client = apiClient
tflog.Info(ctx, "MongoDB Flex instance client configured") tflog.Info(ctx, "MongoDB Flex instance client configured")
} }
// Schema defines the schema for the data source. // Schema defines the schema for the data source.
func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{ descriptions := map[string]string{
"main": "MongoDB Flex instance data source schema. Must have a `region` specified in the provider configuration.", "main": "MongoDB Flex instance data source schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal data source ID. It is structured as \"`project_id`,`instance_id`\".", "id": "Terraform's internal data source ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the MongoDB Flex instance.", "instance_id": "ID of the MongoDB Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.", "project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.", "name": "Instance name.",
@ -73,6 +75,7 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
"weekly_snapshot_retention_weeks": "The number of weeks that weekly backups will be retained.", "weekly_snapshot_retention_weeks": "The number of weeks that weekly backups will be retained.",
"monthly_snapshot_retention_months": "The number of months that monthly backups will be retained.", "monthly_snapshot_retention_months": "The number of months that monthly backups will be retained.",
"point_in_time_window_hours": "The number of hours back in time the point-in-time recovery feature will be able to recover.", "point_in_time_window_hours": "The number of hours back in time the point-in-time recovery feature will be able to recover.",
"region": "The resource region. If not defined, the provider region is used.",
} }
resp.Schema = schema.Schema{ resp.Schema = schema.Schema{
@ -175,12 +178,18 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
}, },
}, },
}, },
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: descriptions["region"],
},
}, },
} }
} }
// Read refreshes the Terraform state with the latest data. // Read refreshes the Terraform state with the latest data.
func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model var model Model
diags := req.Config.Get(ctx, &model) diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
@ -189,10 +198,12 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := d.providerData.GetRegionWithOverride(model.Region)
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
instanceResp, err := r.client.GetInstance(ctx, projectId, instanceId).Execute() instanceResp, err := d.client.GetInstance(ctx, projectId, instanceId, region).Execute()
if err != nil { if err != nil {
utils.LogError( utils.LogError(
ctx, ctx,
@ -233,7 +244,7 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
} }
} }
err = mapFields(ctx, instanceResp, &model, flavor, storage, options) err = mapFields(ctx, instanceResp, &model, flavor, storage, options, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return return

View file

@ -39,6 +39,7 @@ var (
_ resource.Resource = &instanceResource{} _ resource.Resource = &instanceResource{}
_ resource.ResourceWithConfigure = &instanceResource{} _ resource.ResourceWithConfigure = &instanceResource{}
_ resource.ResourceWithImportState = &instanceResource{} _ resource.ResourceWithImportState = &instanceResource{}
_ resource.ResourceWithModifyPlan = &instanceResource{}
) )
type Model struct { type Model struct {
@ -53,6 +54,7 @@ type Model struct {
Storage types.Object `tfsdk:"storage"` Storage types.Object `tfsdk:"storage"`
Version types.String `tfsdk:"version"` Version types.String `tfsdk:"version"`
Options types.Object `tfsdk:"options"` Options types.Object `tfsdk:"options"`
Region types.String `tfsdk:"region"`
} }
// Struct corresponding to Model.Flavor // Struct corresponding to Model.Flavor
@ -110,7 +112,8 @@ func NewInstanceResource() resource.Resource {
// instanceResource is the resource implementation. // instanceResource is the resource implementation.
type instanceResource struct { type instanceResource struct {
client *mongodbflex.APIClient client *mongodbflex.APIClient
providerData core.ProviderData
} }
// Metadata returns the resource type name. // Metadata returns the resource type name.
@ -120,12 +123,13 @@ func (r *instanceResource) Metadata(_ context.Context, req resource.MetadataRequ
// Configure adds the provider configured client to the resource. // Configure adds the provider configured client to the resource.
func (r *instanceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { 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 { if !ok {
return return
} }
apiClient := mongodbflexUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) apiClient := mongodbflexUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
@ -133,13 +137,43 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure
tflog.Info(ctx, "MongoDB Flex instance client configured") tflog.Info(ctx, "MongoDB Flex instance client configured")
} }
// ModifyPlan implements resource.ResourceWithModifyPlan.
// Use the modifier to set the effective region in the current plan.
func (r *instanceResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
var configModel Model
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
}
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
if resp.Diagnostics.HasError() {
return
}
var planModel Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() {
return
}
utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
}
}
// Schema defines the schema for the resource. // Schema defines the schema for the resource.
func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
typeOptions := []string{"Replica", "Sharded", "Single"} typeOptions := []string{"Replica", "Sharded", "Single"}
descriptions := map[string]string{ descriptions := map[string]string{
"main": "MongoDB Flex instance resource schema. Must have a `region` specified in the provider configuration.", "main": "MongoDB Flex 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 MongoDB Flex instance.", "instance_id": "ID of the MongoDB Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.", "project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.", "name": "Instance name.",
@ -152,6 +186,7 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
"weekly_snapshot_retention_weeks": "The number of weeks that weekly backups will be retained.", "weekly_snapshot_retention_weeks": "The number of weeks that weekly backups will be retained.",
"monthly_snapshot_retention_months": "The number of months that monthly backups will be retained.", "monthly_snapshot_retention_months": "The number of months that monthly backups will be retained.",
"point_in_time_window_hours": "The number of hours back in time the point-in-time recovery feature will be able to recover.", "point_in_time_window_hours": "The number of hours back in time the point-in-time recovery feature will be able to recover.",
"region": "The resource region. If not defined, the provider region is used.",
} }
resp.Schema = schema.Schema{ resp.Schema = schema.Schema{
@ -298,6 +333,15 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
}, },
}, },
}, },
"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(),
},
},
}, },
} }
} }
@ -312,7 +356,9 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
return return
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
var acl []string var acl []string
if !(model.ACL.IsNull() || model.ACL.IsUnknown()) { if !(model.ACL.IsNull() || model.ACL.IsUnknown()) {
@ -329,7 +375,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
err := loadFlavorId(ctx, r.client, &model, flavor) err := loadFlavorId(ctx, r.client, &model, flavor, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Loading flavor ID: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Loading flavor ID: %v", err))
return return
@ -360,7 +406,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
return return
} }
// Create new instance // Create new instance
createResp, err := r.client.CreateInstance(ctx, projectId).CreateInstancePayload(*payload).Execute() createResp, err := r.client.CreateInstance(ctx, projectId, region).CreateInstancePayload(*payload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err))
return return
@ -385,14 +431,14 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, instanceId).WaitWithContext(ctx) waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, instanceId, region).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Instance creation waiting: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Instance creation waiting: %v", err))
return return
} }
// Map response body to schema // Map response body to schema
err = mapFields(ctx, waitResp, &model, flavor, storage, options) err = mapFields(ctx, waitResp, &model, flavor, storage, options, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
return return
@ -409,7 +455,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err))
return return
} }
backupScheduleOptions, err := r.client.UpdateBackupSchedule(ctx, projectId, instanceId).UpdateBackupSchedulePayload(*backupScheduleOptionsPayload).Execute() backupScheduleOptions, err := r.client.UpdateBackupSchedule(ctx, projectId, instanceId, region).UpdateBackupSchedulePayload(*backupScheduleOptionsPayload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Updating options: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Updating options: %v", err))
return return
@ -439,8 +485,10 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
return return
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
var flavor = &flavorModel{} var flavor = &flavorModel{}
@ -469,7 +517,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
} }
} }
instanceResp, err := r.client.GetInstance(ctx, projectId, instanceId).Execute() instanceResp, err := r.client.GetInstance(ctx, projectId, instanceId, region).Execute()
if err != nil { if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if ok && oapiErr.StatusCode == http.StatusNotFound { if ok && oapiErr.StatusCode == http.StatusNotFound {
@ -481,7 +529,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
} }
// Map response body to schema // Map response body to schema
err = mapFields(ctx, instanceResp, &model, flavor, storage, options) err = mapFields(ctx, instanceResp, &model, flavor, storage, options, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return return
@ -505,8 +553,10 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
return return
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
var acl []string var acl []string
@ -524,7 +574,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
err := loadFlavorId(ctx, r.client, &model, flavor) err := loadFlavorId(ctx, r.client, &model, flavor, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Loading flavor ID: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Loading flavor ID: %v", err))
return return
@ -555,19 +605,19 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
return return
} }
// Update existing instance // Update existing instance
_, err = r.client.PartialUpdateInstance(ctx, projectId, instanceId).PartialUpdateInstancePayload(*payload).Execute() _, err = r.client.PartialUpdateInstance(ctx, projectId, instanceId, region).PartialUpdateInstancePayload(*payload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", err.Error()) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", err.Error())
return return
} }
waitResp, err := wait.UpdateInstanceWaitHandler(ctx, r.client, projectId, instanceId).WaitWithContext(ctx) waitResp, err := wait.UpdateInstanceWaitHandler(ctx, r.client, projectId, instanceId, region).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Instance update waiting: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Instance update waiting: %v", err))
return return
} }
// Map response body to schema // Map response body to schema
err = mapFields(ctx, waitResp, &model, flavor, storage, options) err = mapFields(ctx, waitResp, &model, flavor, storage, options, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
return return
@ -578,7 +628,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err))
return return
} }
backupScheduleOptions, err := r.client.UpdateBackupSchedule(ctx, projectId, instanceId).UpdateBackupSchedulePayload(*backupScheduleOptionsPayload).Execute() backupScheduleOptions, err := r.client.UpdateBackupSchedule(ctx, projectId, instanceId, region).UpdateBackupSchedulePayload(*backupScheduleOptionsPayload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Updating options: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Updating options: %v", err))
return return
@ -608,17 +658,19 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques
return return
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
// Delete existing instance // Delete existing instance
err := r.client.DeleteInstance(ctx, projectId, instanceId).Execute() err := r.client.DeleteInstance(ctx, projectId, instanceId, region).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Calling API: %v", err))
return return
} }
_, err = wait.DeleteInstanceWaitHandler(ctx, r.client, projectId, instanceId).WaitWithContext(ctx) _, err = wait.DeleteInstanceWaitHandler(ctx, r.client, projectId, instanceId, region).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Instance deletion waiting: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Instance deletion waiting: %v", err))
return return
@ -636,20 +688,21 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques
func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator) 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, core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing instance", "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 return
} }
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[1])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
tflog.Info(ctx, "MongoDB Flex instance state imported") tflog.Info(ctx, "MongoDB Flex instance state imported")
} }
func mapFields(ctx context.Context, resp *mongodbflex.GetInstanceResponse, model *Model, flavor *flavorModel, storage *storageModel, options *optionsModel) error { func mapFields(ctx context.Context, resp *mongodbflex.GetInstanceResponse, model *Model, flavor *flavorModel, storage *storageModel, options *optionsModel, region string) error {
if resp == nil { if resp == nil {
return fmt.Errorf("response input is nil") return fmt.Errorf("response input is nil")
} }
@ -785,7 +838,8 @@ func mapFields(ctx context.Context, resp *mongodbflex.GetInstanceResponse, model
model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule) model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule)
} }
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), instanceId) model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
model.Region = types.StringValue(region)
model.InstanceId = types.StringValue(instanceId) model.InstanceId = types.StringValue(instanceId)
model.Name = types.StringPointerValue(instance.Name) model.Name = types.StringPointerValue(instance.Name)
model.ACL = aclList model.ACL = aclList
@ -849,7 +903,7 @@ func toCreatePayload(model *Model, acl []string, flavor *flavorModel, storage *s
} }
return &mongodbflex.CreateInstancePayload{ return &mongodbflex.CreateInstancePayload{
Acl: &mongodbflex.ACL{ Acl: &mongodbflex.CreateInstancePayloadAcl{
Items: &acl, Items: &acl,
}, },
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule), BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
@ -956,10 +1010,10 @@ func toUpdateBackupScheduleOptionsPayload(ctx context.Context, model *Model, con
} }
type mongoDBFlexClient interface { type mongoDBFlexClient interface {
ListFlavorsExecute(ctx context.Context, projectId string) (*mongodbflex.ListFlavorsResponse, error) ListFlavorsExecute(ctx context.Context, projectId, region string) (*mongodbflex.ListFlavorsResponse, error)
} }
func loadFlavorId(ctx context.Context, client mongoDBFlexClient, model *Model, flavor *flavorModel) error { func loadFlavorId(ctx context.Context, client mongoDBFlexClient, model *Model, flavor *flavorModel, region string) error {
if model == nil { if model == nil {
return fmt.Errorf("nil model") return fmt.Errorf("nil model")
} }
@ -976,7 +1030,7 @@ func loadFlavorId(ctx context.Context, client mongoDBFlexClient, model *Model, f
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
res, err := client.ListFlavorsExecute(ctx, projectId) res, err := client.ListFlavorsExecute(ctx, projectId, region)
if err != nil { if err != nil {
return fmt.Errorf("listing mongodbflex flavors: %w", err) return fmt.Errorf("listing mongodbflex flavors: %w", err)
} }

View file

@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/google/uuid"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/attr"
@ -13,12 +15,21 @@ import (
"github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex"
) )
const (
testRegion = "eu02"
)
var (
projectId = uuid.NewString()
instanceId = uuid.NewString()
)
type mongoDBFlexClientMocked struct { type mongoDBFlexClientMocked struct {
returnError bool returnError bool
listFlavorsResp *mongodbflex.ListFlavorsResponse listFlavorsResp *mongodbflex.ListFlavorsResponse
} }
func (c *mongoDBFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string) (*mongodbflex.ListFlavorsResponse, error) { func (c *mongoDBFlexClientMocked) ListFlavorsExecute(_ context.Context, _, _ string) (*mongodbflex.ListFlavorsResponse, error) {
if c.returnError { if c.returnError {
return nil, fmt.Errorf("get flavors failed") return nil, fmt.Errorf("get flavors failed")
} }
@ -34,14 +45,15 @@ func TestMapFields(t *testing.T) {
flavor *flavorModel flavor *flavorModel
storage *storageModel storage *storageModel
options *optionsModel options *optionsModel
region string
expected Model expected Model
isValid bool isValid bool
}{ }{
{ {
"default_values", "default_values",
Model{ Model{
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
}, },
&mongodbflex.GetInstanceResponse{ &mongodbflex.GetInstanceResponse{
Item: &mongodbflex.Instance{}, Item: &mongodbflex.Instance{},
@ -49,10 +61,11 @@ func TestMapFields(t *testing.T) {
&flavorModel{}, &flavorModel{},
&storageModel{}, &storageModel{},
&optionsModel{}, &optionsModel{},
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s", projectId, testRegion, instanceId)),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Name: types.StringNull(), Name: types.StringNull(),
ACL: types.ListNull(types.StringType), ACL: types.ListNull(types.StringType),
BackupSchedule: types.StringNull(), BackupSchedule: types.StringNull(),
@ -76,14 +89,15 @@ func TestMapFields(t *testing.T) {
"point_in_time_window_hours": types.Int64Null(), "point_in_time_window_hours": types.Int64Null(),
}), }),
Version: types.StringNull(), Version: types.StringNull(),
Region: types.StringValue(testRegion),
}, },
true, true,
}, },
{ {
"simple_values", "simple_values",
Model{ Model{
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
}, },
&mongodbflex.GetInstanceResponse{ &mongodbflex.GetInstanceResponse{
Item: &mongodbflex.Instance{ Item: &mongodbflex.Instance{
@ -101,7 +115,7 @@ func TestMapFields(t *testing.T) {
Id: utils.Ptr("flavor_id"), Id: utils.Ptr("flavor_id"),
Memory: utils.Ptr(int64(34)), Memory: utils.Ptr(int64(34)),
}, },
Id: utils.Ptr("iid"), Id: utils.Ptr(instanceId),
Name: utils.Ptr("name"), Name: utils.Ptr("name"),
Replicas: utils.Ptr(int64(56)), Replicas: utils.Ptr(int64(56)),
Status: mongodbflex.INSTANCESTATUS_READY.Ptr(), Status: mongodbflex.INSTANCESTATUS_READY.Ptr(),
@ -123,10 +137,11 @@ func TestMapFields(t *testing.T) {
&flavorModel{}, &flavorModel{},
&storageModel{}, &storageModel{},
&optionsModel{}, &optionsModel{},
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s", projectId, testRegion, instanceId)),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Name: types.StringValue("name"), Name: types.StringValue("name"),
ACL: types.ListValueMust(types.StringType, []attr.Value{ ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ip1"), types.StringValue("ip1"),
@ -153,6 +168,7 @@ func TestMapFields(t *testing.T) {
"monthly_snapshot_retention_months": types.Int64Value(8), "monthly_snapshot_retention_months": types.Int64Value(8),
"point_in_time_window_hours": types.Int64Value(9), "point_in_time_window_hours": types.Int64Value(9),
}), }),
Region: types.StringValue(testRegion),
Version: types.StringValue("version"), Version: types.StringValue("version"),
}, },
true, true,
@ -160,8 +176,8 @@ func TestMapFields(t *testing.T) {
{ {
"simple_values_no_flavor_and_storage", "simple_values_no_flavor_and_storage",
Model{ Model{
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
}, },
&mongodbflex.GetInstanceResponse{ &mongodbflex.GetInstanceResponse{
Item: &mongodbflex.Instance{ Item: &mongodbflex.Instance{
@ -174,7 +190,7 @@ func TestMapFields(t *testing.T) {
}, },
BackupSchedule: utils.Ptr("schedule"), BackupSchedule: utils.Ptr("schedule"),
Flavor: nil, Flavor: nil,
Id: utils.Ptr("iid"), Id: utils.Ptr(instanceId),
Name: utils.Ptr("name"), Name: utils.Ptr("name"),
Replicas: utils.Ptr(int64(56)), Replicas: utils.Ptr(int64(56)),
Status: mongodbflex.INSTANCESTATUS_READY.Ptr(), Status: mongodbflex.INSTANCESTATUS_READY.Ptr(),
@ -201,10 +217,11 @@ func TestMapFields(t *testing.T) {
&optionsModel{ &optionsModel{
Type: types.StringValue("type"), Type: types.StringValue("type"),
}, },
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s", projectId, testRegion, instanceId)),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Name: types.StringValue("name"), Name: types.StringValue("name"),
ACL: types.ListValueMust(types.StringType, []attr.Value{ ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ip1"), types.StringValue("ip1"),
@ -231,6 +248,7 @@ func TestMapFields(t *testing.T) {
"monthly_snapshot_retention_months": types.Int64Value(8), "monthly_snapshot_retention_months": types.Int64Value(8),
"point_in_time_window_hours": types.Int64Value(9), "point_in_time_window_hours": types.Int64Value(9),
}), }),
Region: types.StringValue(testRegion),
Version: types.StringValue("version"), Version: types.StringValue("version"),
}, },
true, true,
@ -238,8 +256,8 @@ func TestMapFields(t *testing.T) {
{ {
"acls_unordered", "acls_unordered",
Model{ Model{
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
ACL: types.ListValueMust(types.StringType, []attr.Value{ ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ip2"), types.StringValue("ip2"),
types.StringValue(""), types.StringValue(""),
@ -257,7 +275,7 @@ func TestMapFields(t *testing.T) {
}, },
BackupSchedule: utils.Ptr("schedule"), BackupSchedule: utils.Ptr("schedule"),
Flavor: nil, Flavor: nil,
Id: utils.Ptr("iid"), Id: utils.Ptr(instanceId),
Name: utils.Ptr("name"), Name: utils.Ptr("name"),
Replicas: utils.Ptr(int64(56)), Replicas: utils.Ptr(int64(56)),
Status: mongodbflex.INSTANCESTATUS_READY.Ptr(), Status: mongodbflex.INSTANCESTATUS_READY.Ptr(),
@ -284,10 +302,11 @@ func TestMapFields(t *testing.T) {
&optionsModel{ &optionsModel{
Type: types.StringValue("type"), Type: types.StringValue("type"),
}, },
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s", projectId, testRegion, instanceId)),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Name: types.StringValue("name"), Name: types.StringValue("name"),
ACL: types.ListValueMust(types.StringType, []attr.Value{ ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ip2"), types.StringValue("ip2"),
@ -314,6 +333,7 @@ func TestMapFields(t *testing.T) {
"monthly_snapshot_retention_months": types.Int64Value(8), "monthly_snapshot_retention_months": types.Int64Value(8),
"point_in_time_window_hours": types.Int64Value(9), "point_in_time_window_hours": types.Int64Value(9),
}), }),
Region: types.StringValue(testRegion),
Version: types.StringValue("version"), Version: types.StringValue("version"),
}, },
true, true,
@ -321,33 +341,35 @@ func TestMapFields(t *testing.T) {
{ {
"nil_response", "nil_response",
Model{ Model{
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
}, },
nil, nil,
&flavorModel{}, &flavorModel{},
&storageModel{}, &storageModel{},
&optionsModel{}, &optionsModel{},
testRegion,
Model{}, Model{},
false, false,
}, },
{ {
"no_resource_id", "no_resource_id",
Model{ Model{
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
}, },
&mongodbflex.GetInstanceResponse{}, &mongodbflex.GetInstanceResponse{},
&flavorModel{}, &flavorModel{},
&storageModel{}, &storageModel{},
&optionsModel{}, &optionsModel{},
testRegion,
Model{}, Model{},
false, false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) { t.Run(tt.description, func(t *testing.T) {
err := mapFields(context.Background(), tt.input, &tt.state, tt.flavor, tt.storage, tt.options) err := mapFields(context.Background(), tt.input, &tt.state, tt.flavor, tt.storage, tt.options, tt.region)
if !tt.isValid && err == nil { if !tt.isValid && err == nil {
t.Fatalf("Should have failed") t.Fatalf("Should have failed")
} }
@ -454,7 +476,7 @@ func TestToCreatePayload(t *testing.T) {
&storageModel{}, &storageModel{},
&optionsModel{}, &optionsModel{},
&mongodbflex.CreateInstancePayload{ &mongodbflex.CreateInstancePayload{
Acl: &mongodbflex.ACL{ Acl: &mongodbflex.CreateInstancePayloadAcl{
Items: &[]string{}, Items: &[]string{},
}, },
Storage: &mongodbflex.Storage{}, Storage: &mongodbflex.Storage{},
@ -485,7 +507,7 @@ func TestToCreatePayload(t *testing.T) {
Type: types.StringValue("type"), Type: types.StringValue("type"),
}, },
&mongodbflex.CreateInstancePayload{ &mongodbflex.CreateInstancePayload{
Acl: &mongodbflex.ACL{ Acl: &mongodbflex.CreateInstancePayloadAcl{
Items: &[]string{ Items: &[]string{
"ip_1", "ip_1",
"ip_2", "ip_2",
@ -526,7 +548,7 @@ func TestToCreatePayload(t *testing.T) {
Type: types.StringNull(), Type: types.StringNull(),
}, },
&mongodbflex.CreateInstancePayload{ &mongodbflex.CreateInstancePayload{
Acl: &mongodbflex.ACL{ Acl: &mongodbflex.CreateInstancePayloadAcl{
Items: &[]string{ Items: &[]string{
"", "",
}, },
@ -933,7 +955,7 @@ func TestLoadFlavorId(t *testing.T) {
RAM: types.Int64Value(8), RAM: types.Int64Value(8),
}, },
&mongodbflex.ListFlavorsResponse{ &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{ Flavors: &[]mongodbflex.InstanceFlavor{
{ {
Id: utils.Ptr("fid-1"), Id: utils.Ptr("fid-1"),
Cpu: utils.Ptr(int64(2)), Cpu: utils.Ptr(int64(2)),
@ -958,7 +980,7 @@ func TestLoadFlavorId(t *testing.T) {
RAM: types.Int64Value(8), RAM: types.Int64Value(8),
}, },
&mongodbflex.ListFlavorsResponse{ &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{ Flavors: &[]mongodbflex.InstanceFlavor{
{ {
Id: utils.Ptr("fid-1"), Id: utils.Ptr("fid-1"),
Cpu: utils.Ptr(int64(2)), Cpu: utils.Ptr(int64(2)),
@ -989,7 +1011,7 @@ func TestLoadFlavorId(t *testing.T) {
RAM: types.Int64Value(8), RAM: types.Int64Value(8),
}, },
&mongodbflex.ListFlavorsResponse{ &mongodbflex.ListFlavorsResponse{
Flavors: &[]mongodbflex.HandlersInfraFlavor{ Flavors: &[]mongodbflex.InstanceFlavor{
{ {
Id: utils.Ptr("fid-1"), Id: utils.Ptr("fid-1"),
Cpu: utils.Ptr(int64(1)), Cpu: utils.Ptr(int64(1)),
@ -1047,13 +1069,13 @@ func TestLoadFlavorId(t *testing.T) {
listFlavorsResp: tt.mockedResp, listFlavorsResp: tt.mockedResp,
} }
model := &Model{ model := &Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
} }
flavorModel := &flavorModel{ flavorModel := &flavorModel{
CPU: tt.inputFlavor.CPU, CPU: tt.inputFlavor.CPU,
RAM: tt.inputFlavor.RAM, RAM: tt.inputFlavor.RAM,
} }
err := loadFlavorId(context.Background(), client, model, flavorModel) err := loadFlavorId(context.Background(), client, model, flavorModel, testRegion)
if !tt.isValid && err == nil { if !tt.isValid && err == nil {
t.Fatalf("Should have failed") t.Fatalf("Should have failed")
} }

View file

@ -29,8 +29,8 @@ var instanceResource = map[string]string{
"replicas": "3", "replicas": "3",
"storage_class": "premium-perf2-mongodb", "storage_class": "premium-perf2-mongodb",
"storage_size": "10", "storage_size": "10",
"version": "6.0", "version": "7.0",
"version_updated": "7.0", "version_updated": "8.0",
"options_type": "Replica", "options_type": "Replica",
"flavor_id": "2.4", "flavor_id": "2.4",
"backup_schedule": "00 6 * * *", "backup_schedule": "00 6 * * *",
@ -217,12 +217,16 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) {
if !ok { if !ok {
return "", fmt.Errorf("couldn't find resource stackit_mongodbflex_instance.instance") return "", fmt.Errorf("couldn't find resource stackit_mongodbflex_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"] instanceId, ok := r.Primary.Attributes["instance_id"]
if !ok { if !ok {
return "", fmt.Errorf("couldn't find attribute instance_id") 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, ImportState: true,
ImportStateVerify: true, ImportStateVerify: true,
@ -244,6 +248,10 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) {
if !ok { if !ok {
return "", fmt.Errorf("couldn't find resource stackit_mongodbflex_user.user") return "", fmt.Errorf("couldn't find resource stackit_mongodbflex_user.user")
} }
region, ok := r.Primary.Attributes["region"]
if !ok {
return "", fmt.Errorf("couldn't find attribute region")
}
instanceId, ok := r.Primary.Attributes["instance_id"] instanceId, ok := r.Primary.Attributes["instance_id"]
if !ok { if !ok {
return "", fmt.Errorf("couldn't find attribute instance_id") return "", fmt.Errorf("couldn't find attribute instance_id")
@ -253,7 +261,7 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) {
return "", fmt.Errorf("couldn't find attribute user_id") return "", fmt.Errorf("couldn't find attribute user_id")
} }
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, instanceId, userId), nil return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, region, instanceId, userId), nil
}, },
ImportState: true, ImportState: true,
ImportStateVerify: true, ImportStateVerify: true,
@ -293,9 +301,7 @@ func testAccCheckMongoDBFlexDestroy(s *terraform.State) error {
var client *mongodbflex.APIClient var client *mongodbflex.APIClient
var err error var err error
if testutil.MongoDBFlexCustomEndpoint == "" { if testutil.MongoDBFlexCustomEndpoint == "" {
client, err = mongodbflex.NewAPIClient( client, err = mongodbflex.NewAPIClient()
config.WithRegion("eu01"),
)
} else { } else {
client, err = mongodbflex.NewAPIClient( client, err = mongodbflex.NewAPIClient(
config.WithEndpoint(testutil.MongoDBFlexCustomEndpoint), config.WithEndpoint(testutil.MongoDBFlexCustomEndpoint),
@ -310,12 +316,12 @@ func testAccCheckMongoDBFlexDestroy(s *terraform.State) error {
if rs.Type != "stackit_mongodbflex_instance" { if rs.Type != "stackit_mongodbflex_instance" {
continue continue
} }
// instance terraform ID: = "[project_id],[instance_id]" // instance terraform ID: = "[project_id],[region],[instance_id]"
instanceId := strings.Split(rs.Primary.ID, core.Separator)[1] instanceId := strings.Split(rs.Primary.ID, core.Separator)[2]
instancesToDestroy = append(instancesToDestroy, instanceId) instancesToDestroy = append(instancesToDestroy, instanceId)
} }
instancesResp, err := client.ListInstances(ctx, testutil.ProjectId).Tag("").Execute() instancesResp, err := client.ListInstances(ctx, testutil.ProjectId, testutil.Region).Tag("").Execute()
if err != nil { if err != nil {
return fmt.Errorf("getting instancesResp: %w", err) return fmt.Errorf("getting instancesResp: %w", err)
} }
@ -326,11 +332,11 @@ func testAccCheckMongoDBFlexDestroy(s *terraform.State) error {
continue continue
} }
if utils.Contains(instancesToDestroy, *items[i].Id) { if utils.Contains(instancesToDestroy, *items[i].Id) {
err := client.DeleteInstanceExecute(ctx, testutil.ProjectId, *items[i].Id) err := client.DeleteInstanceExecute(ctx, testutil.ProjectId, *items[i].Id, testutil.Region)
if err != nil { if err != nil {
return fmt.Errorf("destroying instance %s during CheckDestroy: %w", *items[i].Id, err) return fmt.Errorf("destroying instance %s during CheckDestroy: %w", *items[i].Id, err)
} }
_, err = wait.DeleteInstanceWaitHandler(ctx, client, testutil.ProjectId, *items[i].Id).WaitWithContext(ctx) _, err = wait.DeleteInstanceWaitHandler(ctx, client, testutil.ProjectId, *items[i].Id, testutil.Region).WaitWithContext(ctx)
if err != nil { if err != nil {
return fmt.Errorf("destroying instance %s during CheckDestroy: waiting for deletion %w", *items[i].Id, err) return fmt.Errorf("destroying instance %s during CheckDestroy: waiting for deletion %w", *items[i].Id, err)
} }

View file

@ -36,6 +36,7 @@ type DataSourceModel struct {
Roles types.Set `tfsdk:"roles"` Roles types.Set `tfsdk:"roles"`
Host types.String `tfsdk:"host"` Host types.String `tfsdk:"host"`
Port types.Int64 `tfsdk:"port"` Port types.Int64 `tfsdk:"port"`
Region types.String `tfsdk:"region"`
} }
// NewUserDataSource is a helper function to simplify the provider implementation. // NewUserDataSource is a helper function to simplify the provider implementation.
@ -45,37 +46,40 @@ func NewUserDataSource() datasource.DataSource {
// userDataSource is the data source implementation. // userDataSource is the data source implementation.
type userDataSource struct { type userDataSource struct {
client *mongodbflex.APIClient client *mongodbflex.APIClient
providerData core.ProviderData
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.
func (r *userDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { func (d *userDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_mongodbflex_user" resp.TypeName = req.ProviderTypeName + "_mongodbflex_user"
} }
// Configure adds the provider configured client to the data source. // Configure adds the provider configured client to the data source.
func (r *userDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { func (d *userDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) var ok bool
d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok { if !ok {
return return
} }
apiClient := mongodbflexUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) apiClient := mongodbflexUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
r.client = apiClient d.client = apiClient
tflog.Info(ctx, "MongoDB Flex user client configured") tflog.Info(ctx, "MongoDB Flex user client configured")
} }
// Schema defines the schema for the data source. // Schema defines the schema for the data source.
func (r *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{ descriptions := map[string]string{
"main": "MongoDB Flex user data source schema. Must have a `region` specified in the provider configuration.", "main": "MongoDB Flex user data source schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal data source. ID. It is structured as \"`project_id`,`instance_id`,`user_id`\".", "id": "Terraform's internal data source. ID. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".",
"user_id": "User ID.", "user_id": "User ID.",
"instance_id": "ID of the MongoDB Flex instance.", "instance_id": "ID of the MongoDB Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.", "project_id": "STACKIT project ID to which the instance is associated.",
"region": "The resource region. If not defined, the provider region is used.",
} }
resp.Schema = schema.Schema{ resp.Schema = schema.Schema{
@ -124,12 +128,18 @@ func (r *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r
"port": schema.Int64Attribute{ "port": schema.Int64Attribute{
Computed: true, 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"],
},
}, },
} }
} }
// Read refreshes the Terraform state with the latest data. // Read refreshes the Terraform state with the latest data.
func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform func (d *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model DataSourceModel var model DataSourceModel
diags := req.Config.Get(ctx, &model) diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
@ -137,13 +147,15 @@ func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
return return
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := d.providerData.GetRegionWithOverride(model.Region)
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
userId := model.UserId.ValueString() userId := model.UserId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "user_id", userId) ctx = tflog.SetField(ctx, "user_id", userId)
recordSetResp, err := r.client.GetUser(ctx, projectId, instanceId, userId).Execute() recordSetResp, err := d.client.GetUser(ctx, projectId, instanceId, userId, region).Execute()
if err != nil { if err != nil {
utils.LogError( utils.LogError(
ctx, ctx,
@ -160,7 +172,7 @@ func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
} }
// Map response body to schema and populate Computed attribute values // Map response body to schema and populate Computed attribute values
err = mapDataSourceFields(recordSetResp, &model) err = mapDataSourceFields(recordSetResp, &model, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Processing API payload: %v", err))
return return
@ -175,7 +187,7 @@ func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
tflog.Info(ctx, "MongoDB Flex user read") tflog.Info(ctx, "MongoDB Flex user read")
} }
func mapDataSourceFields(userResp *mongodbflex.GetUserResponse, model *DataSourceModel) error { func mapDataSourceFields(userResp *mongodbflex.GetUserResponse, model *DataSourceModel, region string) error {
if userResp == nil || userResp.Item == nil { if userResp == nil || userResp.Item == nil {
return fmt.Errorf("response is nil") return fmt.Errorf("response is nil")
} }
@ -192,7 +204,7 @@ func mapDataSourceFields(userResp *mongodbflex.GetUserResponse, model *DataSourc
} else { } else {
return fmt.Errorf("user id not present") return fmt.Errorf("user id not present")
} }
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.InstanceId.ValueString(), userId) model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), userId)
model.UserId = types.StringValue(userId) model.UserId = types.StringValue(userId)
model.Username = types.StringPointerValue(user.Username) model.Username = types.StringPointerValue(user.Username)
model.Database = types.StringPointerValue(user.Database) model.Database = types.StringPointerValue(user.Database)

View file

@ -1,6 +1,7 @@
package mongodbflex package mongodbflex
import ( import (
"fmt"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@ -14,6 +15,7 @@ func TestMapDataSourceFields(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
input *mongodbflex.GetUserResponse input *mongodbflex.GetUserResponse
region string
expected DataSourceModel expected DataSourceModel
isValid bool isValid bool
}{ }{
@ -22,11 +24,12 @@ func TestMapDataSourceFields(t *testing.T) {
&mongodbflex.GetUserResponse{ &mongodbflex.GetUserResponse{
Item: &mongodbflex.InstanceResponseUser{}, Item: &mongodbflex.InstanceResponseUser{},
}, },
testRegion,
DataSourceModel{ DataSourceModel{
Id: types.StringValue("pid,iid,uid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", projectId, testRegion, instanceId, userId)),
UserId: types.StringValue("uid"), UserId: types.StringValue(userId),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Username: types.StringNull(), Username: types.StringNull(),
Database: types.StringNull(), Database: types.StringNull(),
Roles: types.SetNull(types.StringType), Roles: types.SetNull(types.StringType),
@ -50,11 +53,12 @@ func TestMapDataSourceFields(t *testing.T) {
Port: utils.Ptr(int64(1234)), Port: utils.Ptr(int64(1234)),
}, },
}, },
testRegion,
DataSourceModel{ DataSourceModel{
Id: types.StringValue("pid,iid,uid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", projectId, testRegion, instanceId, userId)),
UserId: types.StringValue("uid"), UserId: types.StringValue(userId),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Username: types.StringValue("username"), Username: types.StringValue("username"),
Database: types.StringValue("database"), Database: types.StringValue("database"),
Roles: types.SetValueMust(types.StringType, []attr.Value{ Roles: types.SetValueMust(types.StringType, []attr.Value{
@ -71,7 +75,7 @@ func TestMapDataSourceFields(t *testing.T) {
"null_fields_and_int_conversions", "null_fields_and_int_conversions",
&mongodbflex.GetUserResponse{ &mongodbflex.GetUserResponse{
Item: &mongodbflex.InstanceResponseUser{ Item: &mongodbflex.InstanceResponseUser{
Id: utils.Ptr("uid"), Id: utils.Ptr(userId),
Roles: &[]string{}, Roles: &[]string{},
Username: nil, Username: nil,
Database: nil, Database: nil,
@ -79,11 +83,12 @@ func TestMapDataSourceFields(t *testing.T) {
Port: utils.Ptr(int64(2123456789)), Port: utils.Ptr(int64(2123456789)),
}, },
}, },
testRegion,
DataSourceModel{ DataSourceModel{
Id: types.StringValue("pid,iid,uid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", projectId, testRegion, instanceId, userId)),
UserId: types.StringValue("uid"), UserId: types.StringValue(userId),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Username: types.StringNull(), Username: types.StringNull(),
Database: types.StringNull(), Database: types.StringNull(),
Roles: types.SetValueMust(types.StringType, []attr.Value{}), Roles: types.SetValueMust(types.StringType, []attr.Value{}),
@ -95,12 +100,14 @@ func TestMapDataSourceFields(t *testing.T) {
{ {
"nil_response", "nil_response",
nil, nil,
testRegion,
DataSourceModel{}, DataSourceModel{},
false, false,
}, },
{ {
"nil_response_2", "nil_response_2",
&mongodbflex.GetUserResponse{}, &mongodbflex.GetUserResponse{},
testRegion,
DataSourceModel{}, DataSourceModel{},
false, false,
}, },
@ -109,6 +116,7 @@ func TestMapDataSourceFields(t *testing.T) {
&mongodbflex.GetUserResponse{ &mongodbflex.GetUserResponse{
Item: &mongodbflex.InstanceResponseUser{}, Item: &mongodbflex.InstanceResponseUser{},
}, },
testRegion,
DataSourceModel{}, DataSourceModel{},
false, false,
}, },
@ -120,7 +128,7 @@ func TestMapDataSourceFields(t *testing.T) {
InstanceId: tt.expected.InstanceId, InstanceId: tt.expected.InstanceId,
UserId: tt.expected.UserId, UserId: tt.expected.UserId,
} }
err := mapDataSourceFields(tt.input, state) err := mapDataSourceFields(tt.input, state, tt.region)
if !tt.isValid && err == nil { if !tt.isValid && err == nil {
t.Fatalf("Should have failed") t.Fatalf("Should have failed")
} }

View file

@ -32,6 +32,7 @@ var (
_ resource.Resource = &userResource{} _ resource.Resource = &userResource{}
_ resource.ResourceWithConfigure = &userResource{} _ resource.ResourceWithConfigure = &userResource{}
_ resource.ResourceWithImportState = &userResource{} _ resource.ResourceWithImportState = &userResource{}
_ resource.ResourceWithModifyPlan = &userResource{}
) )
type Model struct { type Model struct {
@ -46,6 +47,7 @@ type Model struct {
Host types.String `tfsdk:"host"` Host types.String `tfsdk:"host"`
Port types.Int64 `tfsdk:"port"` Port types.Int64 `tfsdk:"port"`
Uri types.String `tfsdk:"uri"` Uri types.String `tfsdk:"uri"`
Region types.String `tfsdk:"region"`
} }
// NewUserResource is a helper function to simplify the provider implementation. // NewUserResource is a helper function to simplify the provider implementation.
@ -55,7 +57,8 @@ func NewUserResource() resource.Resource {
// userResource is the resource implementation. // userResource is the resource implementation.
type userResource struct { type userResource struct {
client *mongodbflex.APIClient client *mongodbflex.APIClient
providerData core.ProviderData
} }
// Metadata returns the resource type name. // Metadata returns the resource type name.
@ -65,12 +68,13 @@ func (r *userResource) Metadata(_ context.Context, req resource.MetadataRequest,
// Configure adds the provider configured client to the resource. // Configure adds the provider configured client to the resource.
func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { func (r *userResource) 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 { if !ok {
return return
} }
apiClient := mongodbflexUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) apiClient := mongodbflexUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
@ -78,6 +82,36 @@ func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequ
tflog.Info(ctx, "MongoDB Flex user client configured") tflog.Info(ctx, "MongoDB Flex user client configured")
} }
// ModifyPlan implements resource.ResourceWithModifyPlan.
// Use the modifier to set the effective region in the current plan.
func (r *userResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
var configModel Model
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
}
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
if resp.Diagnostics.HasError() {
return
}
var planModel Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() {
return
}
utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
}
}
// Schema defines the schema for the resource. // Schema defines the schema for the resource.
func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{ descriptions := map[string]string{
@ -87,6 +121,7 @@ func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
"instance_id": "ID of the MongoDB Flex instance.", "instance_id": "ID of the MongoDB Flex instance.",
"project_id": "STACKIT project ID to which the instance is associated.", "project_id": "STACKIT project ID to which the instance is associated.",
"roles": "Database access levels for the user. Some of the possible values are: [`read`, `readWrite`, `readWriteAnyDatabase`]", "roles": "Database access levels for the user. Some of the possible values are: [`read`, `readWrite`, `readWriteAnyDatabase`]",
"region": "The resource region. If not defined, the provider region is used.",
} }
resp.Schema = schema.Schema{ resp.Schema = schema.Schema{
@ -166,6 +201,15 @@ func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
Computed: true, Computed: true,
Sensitive: true, Sensitive: true,
}, },
"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(),
},
},
}, },
} }
} }
@ -179,8 +223,10 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r
return return
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
var roles []string var roles []string
@ -199,7 +245,7 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r
return return
} }
// Create new user // Create new user
userResp, err := r.client.CreateUser(ctx, projectId, instanceId).CreateUserPayload(*payload).Execute() userResp, err := r.client.CreateUser(ctx, projectId, instanceId, region).CreateUserPayload(*payload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err))
return return
@ -212,7 +258,7 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r
ctx = tflog.SetField(ctx, "user_id", userId) ctx = tflog.SetField(ctx, "user_id", userId)
// Map response body to schema // Map response body to schema
err = mapFieldsCreate(userResp, &model) err = mapFieldsCreate(userResp, &model, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Processing API payload: %v", err))
return return
@ -235,13 +281,15 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp
return return
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
userId := model.UserId.ValueString() userId := model.UserId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "user_id", userId) ctx = tflog.SetField(ctx, "user_id", userId)
recordSetResp, err := r.client.GetUser(ctx, projectId, instanceId, userId).Execute() recordSetResp, err := r.client.GetUser(ctx, projectId, instanceId, userId, region).Execute()
if err != nil { if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if ok && oapiErr.StatusCode == http.StatusNotFound { if ok && oapiErr.StatusCode == http.StatusNotFound {
@ -253,7 +301,7 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp
} }
// Map response body to schema // Map response body to schema
err = mapFields(recordSetResp, &model) err = mapFields(recordSetResp, &model, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Processing API payload: %v", err))
return return
@ -278,9 +326,11 @@ func (r *userResource) Update(ctx context.Context, req resource.UpdateRequest, r
return return
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
userId := model.UserId.ValueString() userId := model.UserId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "user_id", userId) ctx = tflog.SetField(ctx, "user_id", userId)
@ -309,20 +359,20 @@ func (r *userResource) Update(ctx context.Context, req resource.UpdateRequest, r
} }
// Update existing instance // Update existing instance
err = r.client.UpdateUser(ctx, projectId, instanceId, userId).UpdateUserPayload(*payload).Execute() err = r.client.UpdateUser(ctx, projectId, instanceId, userId, region).UpdateUserPayload(*payload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", err.Error()) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", err.Error())
return return
} }
userResp, err := r.client.GetUser(ctx, projectId, instanceId, userId).Execute() userResp, err := r.client.GetUser(ctx, projectId, instanceId, userId, region).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Calling API: %v", err))
return return
} }
// Map response body to schema // Map response body to schema
err = mapFields(userResp, &stateModel) err = mapFields(userResp, &stateModel, region)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Processing API payload: %v", err))
return return
@ -348,14 +398,16 @@ func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, r
} }
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
userId := model.UserId.ValueString() userId := model.UserId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "user_id", userId) ctx = tflog.SetField(ctx, "user_id", userId)
// Delete user // Delete user
err := r.client.DeleteUser(ctx, projectId, instanceId, userId).Execute() err := r.client.DeleteUser(ctx, projectId, instanceId, userId, region).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Calling API: %v", err))
return return
@ -367,17 +419,18 @@ func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, r
// The expected format of the resource import identifier is: project_id,zone_id,record_set_id // The expected format of the resource import identifier is: project_id,zone_id,record_set_id
func (r *userResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { func (r *userResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator) 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, core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing user", "Error importing user",
fmt.Sprintf("Expected import identifier with format [project_id],[instance_id],[user_id], got %q", req.ID), fmt.Sprintf("Expected import identifier with format [project_id],[region],[instance_id],[user_id], got %q", req.ID),
) )
return return
} }
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[1])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), idParts[2])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), idParts[3])...)
core.LogAndAddWarning(ctx, &resp.Diagnostics, core.LogAndAddWarning(ctx, &resp.Diagnostics,
"MongoDB Flex user imported with empty password and empty uri", "MongoDB Flex user imported with empty password and empty uri",
"The user password and uri are not imported as they are only available upon creation of a new user. The password and uri fields will be empty.", "The user password and uri are not imported as they are only available upon creation of a new user. The password and uri fields will be empty.",
@ -385,7 +438,7 @@ func (r *userResource) ImportState(ctx context.Context, req resource.ImportState
tflog.Info(ctx, "MongoDB Flex user state imported") tflog.Info(ctx, "MongoDB Flex user state imported")
} }
func mapFieldsCreate(userResp *mongodbflex.CreateUserResponse, model *Model) error { func mapFieldsCreate(userResp *mongodbflex.CreateUserResponse, model *Model, region string) error {
if userResp == nil || userResp.Item == nil { if userResp == nil || userResp.Item == nil {
return fmt.Errorf("response is nil") return fmt.Errorf("response is nil")
} }
@ -398,7 +451,8 @@ func mapFieldsCreate(userResp *mongodbflex.CreateUserResponse, model *Model) err
return fmt.Errorf("user id not present") return fmt.Errorf("user id not present")
} }
userId := *user.Id userId := *user.Id
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.InstanceId.ValueString(), userId) model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), userId)
model.Region = types.StringValue(region)
model.UserId = types.StringValue(userId) model.UserId = types.StringValue(userId)
model.Username = types.StringPointerValue(user.Username) model.Username = types.StringPointerValue(user.Username)
model.Database = types.StringPointerValue(user.Database) model.Database = types.StringPointerValue(user.Database)
@ -427,7 +481,7 @@ func mapFieldsCreate(userResp *mongodbflex.CreateUserResponse, model *Model) err
return nil return nil
} }
func mapFields(userResp *mongodbflex.GetUserResponse, model *Model) error { func mapFields(userResp *mongodbflex.GetUserResponse, model *Model, region string) error {
if userResp == nil || userResp.Item == nil { if userResp == nil || userResp.Item == nil {
return fmt.Errorf("response is nil") return fmt.Errorf("response is nil")
} }
@ -444,7 +498,8 @@ func mapFields(userResp *mongodbflex.GetUserResponse, model *Model) error {
} else { } else {
return fmt.Errorf("user id not present") return fmt.Errorf("user id not present")
} }
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.InstanceId.ValueString(), userId) model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), userId)
model.Region = types.StringValue(region)
model.UserId = types.StringValue(userId) model.UserId = types.StringValue(userId)
model.Username = types.StringPointerValue(user.Username) model.Username = types.StringPointerValue(user.Username)
model.Database = types.StringPointerValue(user.Database) model.Database = types.StringPointerValue(user.Database)

View file

@ -1,8 +1,11 @@
package mongodbflex package mongodbflex
import ( import (
"fmt"
"testing" "testing"
"github.com/google/uuid"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
@ -10,10 +13,21 @@ import (
"github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex"
) )
const (
testRegion = "eu02"
)
var (
projectId = uuid.NewString()
instanceId = uuid.NewString()
userId = uuid.NewString()
)
func TestMapFieldsCreate(t *testing.T) { func TestMapFieldsCreate(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
input *mongodbflex.CreateUserResponse input *mongodbflex.CreateUserResponse
region string
expected Model expected Model
isValid bool isValid bool
}{ }{
@ -21,15 +35,16 @@ func TestMapFieldsCreate(t *testing.T) {
"default_values", "default_values",
&mongodbflex.CreateUserResponse{ &mongodbflex.CreateUserResponse{
Item: &mongodbflex.User{ Item: &mongodbflex.User{
Id: utils.Ptr("uid"), Id: utils.Ptr(userId),
Password: utils.Ptr(""), Password: utils.Ptr(""),
}, },
}, },
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid,uid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", projectId, testRegion, instanceId, userId)),
UserId: types.StringValue("uid"), UserId: types.StringValue(userId),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Username: types.StringNull(), Username: types.StringNull(),
Database: types.StringNull(), Database: types.StringNull(),
Roles: types.SetNull(types.StringType), Roles: types.SetNull(types.StringType),
@ -37,6 +52,7 @@ func TestMapFieldsCreate(t *testing.T) {
Host: types.StringNull(), Host: types.StringNull(),
Port: types.Int64Null(), Port: types.Int64Null(),
Uri: types.StringNull(), Uri: types.StringNull(),
Region: types.StringValue(testRegion),
}, },
true, true,
}, },
@ -44,7 +60,7 @@ func TestMapFieldsCreate(t *testing.T) {
"simple_values", "simple_values",
&mongodbflex.CreateUserResponse{ &mongodbflex.CreateUserResponse{
Item: &mongodbflex.User{ Item: &mongodbflex.User{
Id: utils.Ptr("uid"), Id: utils.Ptr(userId),
Roles: &[]string{ Roles: &[]string{
"role_1", "role_1",
"role_2", "role_2",
@ -58,11 +74,12 @@ func TestMapFieldsCreate(t *testing.T) {
Uri: utils.Ptr("uri"), Uri: utils.Ptr("uri"),
}, },
}, },
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid,uid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", projectId, testRegion, instanceId, userId)),
UserId: types.StringValue("uid"), UserId: types.StringValue(userId),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Username: types.StringValue("username"), Username: types.StringValue("username"),
Database: types.StringValue("database"), Database: types.StringValue("database"),
Roles: types.SetValueMust(types.StringType, []attr.Value{ Roles: types.SetValueMust(types.StringType, []attr.Value{
@ -74,6 +91,7 @@ func TestMapFieldsCreate(t *testing.T) {
Host: types.StringValue("host"), Host: types.StringValue("host"),
Port: types.Int64Value(1234), Port: types.Int64Value(1234),
Uri: types.StringValue("uri"), Uri: types.StringValue("uri"),
Region: types.StringValue(testRegion),
}, },
true, true,
}, },
@ -81,7 +99,7 @@ func TestMapFieldsCreate(t *testing.T) {
"null_fields_and_int_conversions", "null_fields_and_int_conversions",
&mongodbflex.CreateUserResponse{ &mongodbflex.CreateUserResponse{
Item: &mongodbflex.User{ Item: &mongodbflex.User{
Id: utils.Ptr("uid"), Id: utils.Ptr(userId),
Roles: &[]string{}, Roles: &[]string{},
Username: nil, Username: nil,
Database: nil, Database: nil,
@ -91,11 +109,12 @@ func TestMapFieldsCreate(t *testing.T) {
Uri: nil, Uri: nil,
}, },
}, },
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid,uid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", projectId, testRegion, instanceId, userId)),
UserId: types.StringValue("uid"), UserId: types.StringValue(userId),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Username: types.StringNull(), Username: types.StringNull(),
Database: types.StringNull(), Database: types.StringNull(),
Roles: types.SetValueMust(types.StringType, []attr.Value{}), Roles: types.SetValueMust(types.StringType, []attr.Value{}),
@ -103,18 +122,21 @@ func TestMapFieldsCreate(t *testing.T) {
Host: types.StringNull(), Host: types.StringNull(),
Port: types.Int64Value(2123456789), Port: types.Int64Value(2123456789),
Uri: types.StringNull(), Uri: types.StringNull(),
Region: types.StringValue(testRegion),
}, },
true, true,
}, },
{ {
"nil_response", "nil_response",
nil, nil,
testRegion,
Model{}, Model{},
false, false,
}, },
{ {
"nil_response_2", "nil_response_2",
&mongodbflex.CreateUserResponse{}, &mongodbflex.CreateUserResponse{},
testRegion,
Model{}, Model{},
false, false,
}, },
@ -123,6 +145,7 @@ func TestMapFieldsCreate(t *testing.T) {
&mongodbflex.CreateUserResponse{ &mongodbflex.CreateUserResponse{
Item: &mongodbflex.User{}, Item: &mongodbflex.User{},
}, },
testRegion,
Model{}, Model{},
false, false,
}, },
@ -130,9 +153,10 @@ func TestMapFieldsCreate(t *testing.T) {
"no_password", "no_password",
&mongodbflex.CreateUserResponse{ &mongodbflex.CreateUserResponse{
Item: &mongodbflex.User{ Item: &mongodbflex.User{
Id: utils.Ptr("uid"), Id: utils.Ptr(userId),
}, },
}, },
testRegion,
Model{}, Model{},
false, false,
}, },
@ -143,7 +167,7 @@ func TestMapFieldsCreate(t *testing.T) {
ProjectId: tt.expected.ProjectId, ProjectId: tt.expected.ProjectId,
InstanceId: tt.expected.InstanceId, InstanceId: tt.expected.InstanceId,
} }
err := mapFieldsCreate(tt.input, state) err := mapFieldsCreate(tt.input, state, tt.region)
if !tt.isValid && err == nil { if !tt.isValid && err == nil {
t.Fatalf("Should have failed") t.Fatalf("Should have failed")
} }
@ -164,6 +188,7 @@ func TestMapFields(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
input *mongodbflex.GetUserResponse input *mongodbflex.GetUserResponse
region string
expected Model expected Model
isValid bool isValid bool
}{ }{
@ -172,16 +197,18 @@ func TestMapFields(t *testing.T) {
&mongodbflex.GetUserResponse{ &mongodbflex.GetUserResponse{
Item: &mongodbflex.InstanceResponseUser{}, Item: &mongodbflex.InstanceResponseUser{},
}, },
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid,uid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", projectId, testRegion, instanceId, userId)),
UserId: types.StringValue("uid"), UserId: types.StringValue(userId),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Username: types.StringNull(), Username: types.StringNull(),
Database: types.StringNull(), Database: types.StringNull(),
Roles: types.SetNull(types.StringType), Roles: types.SetNull(types.StringType),
Host: types.StringNull(), Host: types.StringNull(),
Port: types.Int64Null(), Port: types.Int64Null(),
Region: types.StringValue(testRegion),
}, },
true, true,
}, },
@ -200,11 +227,12 @@ func TestMapFields(t *testing.T) {
Port: utils.Ptr(int64(1234)), Port: utils.Ptr(int64(1234)),
}, },
}, },
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid,uid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", projectId, testRegion, instanceId, userId)),
UserId: types.StringValue("uid"), UserId: types.StringValue(userId),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Username: types.StringValue("username"), Username: types.StringValue("username"),
Database: types.StringValue("database"), Database: types.StringValue("database"),
Roles: types.SetValueMust(types.StringType, []attr.Value{ Roles: types.SetValueMust(types.StringType, []attr.Value{
@ -212,8 +240,9 @@ func TestMapFields(t *testing.T) {
types.StringValue("role_2"), types.StringValue("role_2"),
types.StringValue(""), types.StringValue(""),
}), }),
Host: types.StringValue("host"), Host: types.StringValue("host"),
Port: types.Int64Value(1234), Port: types.Int64Value(1234),
Region: types.StringValue(testRegion),
}, },
true, true,
}, },
@ -221,7 +250,7 @@ func TestMapFields(t *testing.T) {
"null_fields_and_int_conversions", "null_fields_and_int_conversions",
&mongodbflex.GetUserResponse{ &mongodbflex.GetUserResponse{
Item: &mongodbflex.InstanceResponseUser{ Item: &mongodbflex.InstanceResponseUser{
Id: utils.Ptr("uid"), Id: utils.Ptr(userId),
Roles: &[]string{}, Roles: &[]string{},
Username: nil, Username: nil,
Database: nil, Database: nil,
@ -229,28 +258,32 @@ func TestMapFields(t *testing.T) {
Port: utils.Ptr(int64(2123456789)), Port: utils.Ptr(int64(2123456789)),
}, },
}, },
testRegion,
Model{ Model{
Id: types.StringValue("pid,iid,uid"), Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", projectId, testRegion, instanceId, userId)),
UserId: types.StringValue("uid"), UserId: types.StringValue(userId),
InstanceId: types.StringValue("iid"), InstanceId: types.StringValue(instanceId),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue(projectId),
Username: types.StringNull(), Username: types.StringNull(),
Database: types.StringNull(), Database: types.StringNull(),
Roles: types.SetValueMust(types.StringType, []attr.Value{}), Roles: types.SetValueMust(types.StringType, []attr.Value{}),
Host: types.StringNull(), Host: types.StringNull(),
Port: types.Int64Value(2123456789), Port: types.Int64Value(2123456789),
Region: types.StringValue(testRegion),
}, },
true, true,
}, },
{ {
"nil_response", "nil_response",
nil, nil,
testRegion,
Model{}, Model{},
false, false,
}, },
{ {
"nil_response_2", "nil_response_2",
&mongodbflex.GetUserResponse{}, &mongodbflex.GetUserResponse{},
testRegion,
Model{}, Model{},
false, false,
}, },
@ -259,6 +292,7 @@ func TestMapFields(t *testing.T) {
&mongodbflex.GetUserResponse{ &mongodbflex.GetUserResponse{
Item: &mongodbflex.InstanceResponseUser{}, Item: &mongodbflex.InstanceResponseUser{},
}, },
testRegion,
Model{}, Model{},
false, false,
}, },
@ -270,7 +304,7 @@ func TestMapFields(t *testing.T) {
InstanceId: tt.expected.InstanceId, InstanceId: tt.expected.InstanceId,
UserId: tt.expected.UserId, UserId: tt.expected.UserId,
} }
err := mapFields(tt.input, state) err := mapFields(tt.input, state, tt.region)
if !tt.isValid && err == nil { if !tt.isValid && err == nil {
t.Fatalf("Should have failed") t.Fatalf("Should have failed")
} }

View file

@ -18,9 +18,8 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags
} }
if providerData.MongoDBFlexCustomEndpoint != "" { if providerData.MongoDBFlexCustomEndpoint != "" {
apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.MongoDBFlexCustomEndpoint)) apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.MongoDBFlexCustomEndpoint))
} else {
apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion()))
} }
apiClient, err := mongodbflex.NewAPIClient(apiClientConfigOptions...) apiClient, err := mongodbflex.NewAPIClient(apiClientConfigOptions...)
if err != nil { if err != nil {
core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err))

View file

@ -45,7 +45,6 @@ func TestConfigureClient(t *testing.T) {
}, },
expected: func() *mongodbflex.APIClient { expected: func() *mongodbflex.APIClient {
apiClient, err := mongodbflex.NewAPIClient( apiClient, err := mongodbflex.NewAPIClient(
config.WithRegion("eu01"),
utils.UserAgentConfigOption(testVersion), utils.UserAgentConfigOption(testVersion),
) )
if err != nil { if err != nil {