From 04914f3fd3416b50f35bbb3315391269a2c46240 Mon Sep 17 00:00:00 2001 From: Carlo Goetz Date: Fri, 20 Feb 2026 15:25:49 +0100 Subject: [PATCH 1/3] feat(mongodbflex) instance + user: store IDs immediately STACKITTPR-385 --- .../services/mongodbflex/instance/resource.go | 22 ++- .../mongodbflex/mongodbflex_acc_test.go | 158 ++++++++++++++++++ .../services/mongodbflex/user/resource.go | 21 ++- 3 files changed, 183 insertions(+), 18 deletions(-) diff --git a/stackit/internal/services/mongodbflex/instance/resource.go b/stackit/internal/services/mongodbflex/instance/resource.go index 3f8600375..b74f8e912 100644 --- a/stackit/internal/services/mongodbflex/instance/resource.go +++ b/stackit/internal/services/mongodbflex/instance/resource.go @@ -14,7 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -426,14 +425,11 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques return } instanceId := *createResp.Id - ctx = tflog.SetField(ctx, "instance_id", instanceId) - diags = resp.State.SetAttribute(ctx, path.Root("project_id"), projectId) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - diags = resp.State.SetAttribute(ctx, path.Root("instance_id"), instanceId) - resp.Diagnostics.Append(diags...) + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": projectId, + "region": region, + "instance_id": instanceId, + }) if resp.Diagnostics.HasError() { return } @@ -719,9 +715,11 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS return } - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) - 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])...) + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": idParts[0], + "region": idParts[1], + "instance_id": idParts[2], + }) tflog.Info(ctx, "MongoDB Flex instance state imported") } diff --git a/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go b/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go index 0ab9598fd..38b0097fc 100644 --- a/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go +++ b/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go @@ -3,9 +3,12 @@ package mongodbflex_test import ( "context" "fmt" + "net/http" + "regexp" "strings" "testing" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -296,6 +299,161 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) { }) } +// slow test, delete has a 30s sleep... +func TestMongoDBInstanceSavesIDsOnError(t *testing.T) { + var ( + projectId = uuid.NewString() + instanceId = uuid.NewString() + ) + const ( + name = "instance-test" + region = "eu01" + ) + s := testutil.NewMockServer(t) + defer s.Server.Close() + tfConfig := fmt.Sprintf(` +provider "stackit" { + mongodbflex_custom_endpoint = "%s" + service_account_token = "mock-server-needs-no-auth" +} + +resource "stackit_mongodbflex_instance" "instance" { + project_id = "%s" + name = "%s" + options = { + type = "Replica" + snapshot_retention_days = 1 + daily_snapshot_retention_days = 1 + point_in_time_window_hours = 1 + } + storage = { + class = "premium-perf2-mongodb" + size = 10 + } + replicas = 1 + acl = ["192.168.0.0/16"] + flavor = { + cpu =2 + ram =4 + } + version = "7.0" + backup_schedule = "00 6 * * *" +} +`, s.Server.URL, projectId, name) + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "ListFlavors", + ToJsonBody: &mongodbflex.ListFlavorsResponse{Flavors: &[]mongodbflex.InstanceFlavor{ + { + Description: utils.Ptr("flava-flav"), + Cpu: utils.Ptr(int64(2)), + Id: utils.Ptr("flavour-id"), + Memory: utils.Ptr(int64(4)), + }, + }}, + }, + testutil.MockResponse{ + Description: "create instance", + ToJsonBody: &mongodbflex.CreateInstanceResponse{Id: utils.Ptr(instanceId)}, + }, + testutil.MockResponse{Description: "create waiter", StatusCode: http.StatusInternalServerError}, + ) + }, + Config: tfConfig, + ExpectError: regexp.MustCompile("Error creating instance.*"), + }, + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "refresh", + Handler: func(w http.ResponseWriter, req *http.Request) { + expected := fmt.Sprintf("/v2/projects/%s/regions/%s/instances/%s", projectId, region, instanceId) + if req.URL.Path != expected { + t.Errorf("expected request to %s, got %s", expected, req.URL.Path) + } + w.WriteHeader(http.StatusInternalServerError) + }, + }, + testutil.MockResponse{Description: "delete"}, + testutil.MockResponse{Description: "delete waiter", StatusCode: http.StatusNotFound}, + ) + }, + RefreshState: true, + ExpectError: regexp.MustCompile("Error reading instance.*"), + }, + }, + }) +} + +func TestMongoDBUserSavesIDsOnError(t *testing.T) { + projectId := uuid.NewString() + instanceId := uuid.NewString() + userId := uuid.NewString() + const region = "eu01" + s := testutil.NewMockServer(t) + defer s.Server.Close() + tfConfig := fmt.Sprintf(` +provider "stackit" { + mongodbflex_custom_endpoint = "%s" + service_account_token = "mock-server-needs-no-auth" +} + +resource "stackit_mongodbflex_user" "user" { + project_id = "%s" + instance_id = "%s" + username = "username" + roles = ["read"] + database = "db-name" +} +`, s.Server.URL, projectId, instanceId) + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "create user", + ToJsonBody: &mongodbflex.CreateUserResponse{Item: &mongodbflex.User{Id: utils.Ptr(userId)}}, + }, + testutil.MockResponse{Description: "failing waiter", StatusCode: http.StatusInternalServerError}, + ) + }, + Config: tfConfig, + ExpectError: regexp.MustCompile("Error creating user.*"), + }, + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "refresh user", + Handler: func(w http.ResponseWriter, req *http.Request) { + expected := fmt.Sprintf("/v2/projects/%s/regions/%s/instances/%s/users/%s", projectId, region, instanceId, userId) + if req.URL.Path != expected { + t.Errorf("expected request to %s, got %s", expected, req.URL.Path) + } + w.WriteHeader(http.StatusInternalServerError) + }, + }, + testutil.MockResponse{Description: "delete user"}, + testutil.MockResponse{Description: "delete user waiter", StatusCode: http.StatusNotFound}, + ) + }, + RefreshState: true, + ExpectError: regexp.MustCompile("Error reading user.*"), + }, + }, + }) +} + func testAccCheckMongoDBFlexDestroy(s *terraform.State) error { ctx := context.Background() var client *mongodbflex.APIClient diff --git a/stackit/internal/services/mongodbflex/user/resource.go b/stackit/internal/services/mongodbflex/user/resource.go index 85096f23e..7c11d2d2d 100644 --- a/stackit/internal/services/mongodbflex/user/resource.go +++ b/stackit/internal/services/mongodbflex/user/resource.go @@ -17,7 +17,6 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -261,7 +260,15 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r return } userId := *userResp.Item.Id - ctx = tflog.SetField(ctx, "user_id", userId) + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": projectId, + "region": region, + "instance_id": instanceId, + "user_id": userId, + }) + if resp.Diagnostics.HasError() { + return + } // Map response body to schema err = mapFieldsCreate(userResp, &model, region) @@ -448,10 +455,12 @@ func (r *userResource) ImportState(ctx context.Context, req resource.ImportState return } - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) - 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])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), idParts[3])...) + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": idParts[0], + "region": idParts[1], + "instance_id": idParts[2], + "user_id": idParts[3], + }) core.LogAndAddWarning(ctx, &resp.Diagnostics, "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.", From fef2dcd1ba719e582e7ec147626b4ba81e37b4ed Mon Sep 17 00:00:00 2001 From: Carlo Goetz Date: Fri, 20 Feb 2026 15:36:25 +0100 Subject: [PATCH 2/3] fix(mongodbflex) use us spelling --- stackit/internal/services/mongodbflex/mongodbflex_acc_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go b/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go index 38b0097fc..e24abf33c 100644 --- a/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go +++ b/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go @@ -353,7 +353,7 @@ resource "stackit_mongodbflex_instance" "instance" { { Description: utils.Ptr("flava-flav"), Cpu: utils.Ptr(int64(2)), - Id: utils.Ptr("flavour-id"), + Id: utils.Ptr("flavor-id"), Memory: utils.Ptr(int64(4)), }, }}, From 49285b3cec4f1ba2216fa7c84983eb2cea01f359 Mon Sep 17 00:00:00 2001 From: Carlo Goetz Date: Wed, 25 Feb 2026 15:58:54 +0100 Subject: [PATCH 3/3] fix(mongodbflex) move SavesIDsOnError tests into new file --- .../mongodbflex/mongodbflex_acc_test.go | 158 ---------------- .../services/mongodbflex/mongodbflex_test.go | 169 ++++++++++++++++++ 2 files changed, 169 insertions(+), 158 deletions(-) create mode 100644 stackit/internal/services/mongodbflex/mongodbflex_test.go diff --git a/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go b/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go index e24abf33c..0ab9598fd 100644 --- a/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go +++ b/stackit/internal/services/mongodbflex/mongodbflex_acc_test.go @@ -3,12 +3,9 @@ package mongodbflex_test import ( "context" "fmt" - "net/http" - "regexp" "strings" "testing" - "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -299,161 +296,6 @@ func TestAccMongoDBFlexFlexResource(t *testing.T) { }) } -// slow test, delete has a 30s sleep... -func TestMongoDBInstanceSavesIDsOnError(t *testing.T) { - var ( - projectId = uuid.NewString() - instanceId = uuid.NewString() - ) - const ( - name = "instance-test" - region = "eu01" - ) - s := testutil.NewMockServer(t) - defer s.Server.Close() - tfConfig := fmt.Sprintf(` -provider "stackit" { - mongodbflex_custom_endpoint = "%s" - service_account_token = "mock-server-needs-no-auth" -} - -resource "stackit_mongodbflex_instance" "instance" { - project_id = "%s" - name = "%s" - options = { - type = "Replica" - snapshot_retention_days = 1 - daily_snapshot_retention_days = 1 - point_in_time_window_hours = 1 - } - storage = { - class = "premium-perf2-mongodb" - size = 10 - } - replicas = 1 - acl = ["192.168.0.0/16"] - flavor = { - cpu =2 - ram =4 - } - version = "7.0" - backup_schedule = "00 6 * * *" -} -`, s.Server.URL, projectId, name) - - resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - { - PreConfig: func() { - s.Reset( - testutil.MockResponse{ - Description: "ListFlavors", - ToJsonBody: &mongodbflex.ListFlavorsResponse{Flavors: &[]mongodbflex.InstanceFlavor{ - { - Description: utils.Ptr("flava-flav"), - Cpu: utils.Ptr(int64(2)), - Id: utils.Ptr("flavor-id"), - Memory: utils.Ptr(int64(4)), - }, - }}, - }, - testutil.MockResponse{ - Description: "create instance", - ToJsonBody: &mongodbflex.CreateInstanceResponse{Id: utils.Ptr(instanceId)}, - }, - testutil.MockResponse{Description: "create waiter", StatusCode: http.StatusInternalServerError}, - ) - }, - Config: tfConfig, - ExpectError: regexp.MustCompile("Error creating instance.*"), - }, - { - PreConfig: func() { - s.Reset( - testutil.MockResponse{ - Description: "refresh", - Handler: func(w http.ResponseWriter, req *http.Request) { - expected := fmt.Sprintf("/v2/projects/%s/regions/%s/instances/%s", projectId, region, instanceId) - if req.URL.Path != expected { - t.Errorf("expected request to %s, got %s", expected, req.URL.Path) - } - w.WriteHeader(http.StatusInternalServerError) - }, - }, - testutil.MockResponse{Description: "delete"}, - testutil.MockResponse{Description: "delete waiter", StatusCode: http.StatusNotFound}, - ) - }, - RefreshState: true, - ExpectError: regexp.MustCompile("Error reading instance.*"), - }, - }, - }) -} - -func TestMongoDBUserSavesIDsOnError(t *testing.T) { - projectId := uuid.NewString() - instanceId := uuid.NewString() - userId := uuid.NewString() - const region = "eu01" - s := testutil.NewMockServer(t) - defer s.Server.Close() - tfConfig := fmt.Sprintf(` -provider "stackit" { - mongodbflex_custom_endpoint = "%s" - service_account_token = "mock-server-needs-no-auth" -} - -resource "stackit_mongodbflex_user" "user" { - project_id = "%s" - instance_id = "%s" - username = "username" - roles = ["read"] - database = "db-name" -} -`, s.Server.URL, projectId, instanceId) - - resource.UnitTest(t, resource.TestCase{ - ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - { - PreConfig: func() { - s.Reset( - testutil.MockResponse{ - Description: "create user", - ToJsonBody: &mongodbflex.CreateUserResponse{Item: &mongodbflex.User{Id: utils.Ptr(userId)}}, - }, - testutil.MockResponse{Description: "failing waiter", StatusCode: http.StatusInternalServerError}, - ) - }, - Config: tfConfig, - ExpectError: regexp.MustCompile("Error creating user.*"), - }, - { - PreConfig: func() { - s.Reset( - testutil.MockResponse{ - Description: "refresh user", - Handler: func(w http.ResponseWriter, req *http.Request) { - expected := fmt.Sprintf("/v2/projects/%s/regions/%s/instances/%s/users/%s", projectId, region, instanceId, userId) - if req.URL.Path != expected { - t.Errorf("expected request to %s, got %s", expected, req.URL.Path) - } - w.WriteHeader(http.StatusInternalServerError) - }, - }, - testutil.MockResponse{Description: "delete user"}, - testutil.MockResponse{Description: "delete user waiter", StatusCode: http.StatusNotFound}, - ) - }, - RefreshState: true, - ExpectError: regexp.MustCompile("Error reading user.*"), - }, - }, - }) -} - func testAccCheckMongoDBFlexDestroy(s *terraform.State) error { ctx := context.Background() var client *mongodbflex.APIClient diff --git a/stackit/internal/services/mongodbflex/mongodbflex_test.go b/stackit/internal/services/mongodbflex/mongodbflex_test.go new file mode 100644 index 000000000..db454ff69 --- /dev/null +++ b/stackit/internal/services/mongodbflex/mongodbflex_test.go @@ -0,0 +1,169 @@ +package mongodbflex + +import ( + "fmt" + "net/http" + "regexp" + "testing" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/mongodbflex" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +// slow test, delete has a 30s sleep... +func TestMongoDBInstanceSavesIDsOnError(t *testing.T) { + var ( + projectId = uuid.NewString() + instanceId = uuid.NewString() + ) + const ( + name = "instance-test" + region = "eu01" + ) + s := testutil.NewMockServer(t) + defer s.Server.Close() + tfConfig := fmt.Sprintf(` +provider "stackit" { + mongodbflex_custom_endpoint = "%s" + service_account_token = "mock-server-needs-no-auth" +} + +resource "stackit_mongodbflex_instance" "instance" { + project_id = "%s" + name = "%s" + options = { + type = "Replica" + snapshot_retention_days = 1 + daily_snapshot_retention_days = 1 + point_in_time_window_hours = 1 + } + storage = { + class = "premium-perf2-mongodb" + size = 10 + } + replicas = 1 + acl = ["192.168.0.0/16"] + flavor = { + cpu =2 + ram =4 + } + version = "7.0" + backup_schedule = "00 6 * * *" +} +`, s.Server.URL, projectId, name) + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "ListFlavors", + ToJsonBody: &mongodbflex.ListFlavorsResponse{Flavors: &[]mongodbflex.InstanceFlavor{ + { + Description: utils.Ptr("flava-flav"), + Cpu: utils.Ptr(int64(2)), + Id: utils.Ptr("flavor-id"), + Memory: utils.Ptr(int64(4)), + }, + }}, + }, + testutil.MockResponse{ + Description: "create instance", + ToJsonBody: &mongodbflex.CreateInstanceResponse{Id: utils.Ptr(instanceId)}, + }, + testutil.MockResponse{Description: "create waiter", StatusCode: http.StatusInternalServerError}, + ) + }, + Config: tfConfig, + ExpectError: regexp.MustCompile("Error creating instance.*"), + }, + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "refresh", + Handler: func(w http.ResponseWriter, req *http.Request) { + expected := fmt.Sprintf("/v2/projects/%s/regions/%s/instances/%s", projectId, region, instanceId) + if req.URL.Path != expected { + t.Errorf("expected request to %s, got %s", expected, req.URL.Path) + } + w.WriteHeader(http.StatusInternalServerError) + }, + }, + testutil.MockResponse{Description: "delete"}, + testutil.MockResponse{Description: "delete waiter", StatusCode: http.StatusNotFound}, + ) + }, + RefreshState: true, + ExpectError: regexp.MustCompile("Error reading instance.*"), + }, + }, + }) +} + +func TestMongoDBUserSavesIDsOnError(t *testing.T) { + projectId := uuid.NewString() + instanceId := uuid.NewString() + userId := uuid.NewString() + const region = "eu01" + s := testutil.NewMockServer(t) + defer s.Server.Close() + tfConfig := fmt.Sprintf(` +provider "stackit" { + mongodbflex_custom_endpoint = "%s" + service_account_token = "mock-server-needs-no-auth" +} + +resource "stackit_mongodbflex_user" "user" { + project_id = "%s" + instance_id = "%s" + username = "username" + roles = ["read"] + database = "db-name" +} +`, s.Server.URL, projectId, instanceId) + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "create user", + ToJsonBody: &mongodbflex.CreateUserResponse{Item: &mongodbflex.User{Id: utils.Ptr(userId)}}, + }, + testutil.MockResponse{Description: "failing waiter", StatusCode: http.StatusInternalServerError}, + ) + }, + Config: tfConfig, + ExpectError: regexp.MustCompile("Error creating user.*"), + }, + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "refresh user", + Handler: func(w http.ResponseWriter, req *http.Request) { + expected := fmt.Sprintf("/v2/projects/%s/regions/%s/instances/%s/users/%s", projectId, region, instanceId, userId) + if req.URL.Path != expected { + t.Errorf("expected request to %s, got %s", expected, req.URL.Path) + } + w.WriteHeader(http.StatusInternalServerError) + }, + }, + testutil.MockResponse{Description: "delete user"}, + testutil.MockResponse{Description: "delete user waiter", StatusCode: http.StatusNotFound}, + ) + }, + RefreshState: true, + ExpectError: regexp.MustCompile("Error reading user.*"), + }, + }, + }) +}