From 438af8809f8336ad13e4865952da41abd3da2cbe Mon Sep 17 00:00:00 2001 From: Espen Albert Date: Thu, 26 Dec 2024 11:20:10 +0100 Subject: [PATCH 1/6] feat: Add ImportSplit3 function for parsing import strings --- internal/common/conversion/import_state.go | 8 +++ .../common/conversion/import_state_test.go | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/internal/common/conversion/import_state.go b/internal/common/conversion/import_state.go index 6d37b3b6c8..faf5ac88b7 100644 --- a/internal/common/conversion/import_state.go +++ b/internal/common/conversion/import_state.go @@ -10,6 +10,14 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" ) +func ImportSplit3(importRaw string) (bool, string, string, string) { + parts := strings.Split(importRaw, "/") + if len(parts) != 3 { + return false, "", "", "" + } + return true, parts[0], parts[1], parts[2] +} + func ImportStateProjectIDClusterName(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, attrNameProjectID, attrNameClusterName string) { parts := strings.SplitN(req.ID, "-", 2) if len(parts) != 2 { diff --git a/internal/common/conversion/import_state_test.go b/internal/common/conversion/import_state_test.go index dc5544511a..9727662c8f 100644 --- a/internal/common/conversion/import_state_test.go +++ b/internal/common/conversion/import_state_test.go @@ -23,3 +23,52 @@ func TestValidateClusterName(t *testing.T) { assert.Contains(t, err.Error(), "_invalidClusterName") assert.Contains(t, err.Error(), "cluster_name must be a string with length between 1 and 64, starting and ending with an alphanumeric character, and containing only alphanumeric characters and hyphens") } + +func TestImportSplit3(t *testing.T) { + tests := map[string]struct { + importRaw string + expected bool + part1 string + part2 string + part3 string + }{ + "valid input": { + importRaw: "part1/part2/part3", + expected: true, + part1: "part1", + part2: "part2", + part3: "part3", + }, + "invalid input with more parts": { + importRaw: "part1/part2/part3/part4", + expected: false, + part1: "", + part2: "", + part3: "", + }, + "invalid input with two parts": { + importRaw: "part1/part2", + expected: false, + part1: "", + part2: "", + part3: "", + }, + "invalid input with one part": { + importRaw: "part1", + expected: false, + part1: "", + part2: "", + part3: "", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ok, part1, part2, part3 := conversion.ImportSplit3(tc.importRaw) + assert.Equal(t, tc.expected, ok) + assert.Equal(t, tc.part1, part1) + assert.Equal(t, tc.part2, part2) + assert.Equal(t, tc.part3, part3) + }) + } +} From fd1e11e04124e5b792c50430df1544837fd5a54f Mon Sep 17 00:00:00 2001 From: Espen Albert Date: Thu, 26 Dec 2024 11:21:48 +0100 Subject: [PATCH 2/6] fix: Avoids import error for database_user when both username and auth database contain hyphens --- .../databaseuser/model_database_user_test.go | 66 +++++++++++++++++++ .../databaseuser/resource_database_user.go | 11 ++-- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/internal/service/databaseuser/model_database_user_test.go b/internal/service/databaseuser/model_database_user_test.go index cb7466c932..00864f2fd4 100644 --- a/internal/service/databaseuser/model_database_user_test.go +++ b/internal/service/databaseuser/model_database_user_test.go @@ -364,3 +364,69 @@ func getDatabaseUserModel(roles, labels, scopes basetypes.SetValue, password typ Scopes: scopes, } } + +func TestSplitDatabaseUserImportID(t *testing.T) { + const invalidDefaultString = "import format error: to import a Database User, use the format {project_id}-{username}-{auth_database_name} OR {project_id}/{username}/{auth_database_name}" + tests := map[string]struct { + importID string + projectID string + username string + authDBName string + errorString string + }{ + "valid input": { + importID: "664619d870c247237f4b86a6/my-username-dash/my-db-name", + projectID: "664619d870c247237f4b86a6", + username: "my-username-dash", + authDBName: "my-db-name", + }, + "valid input legacy": { + importID: "664619d870c247237f4b86a6-myUsernameCamel-mydbname", + projectID: "664619d870c247237f4b86a6", + username: "myUsernameCamel", + authDBName: "mydbname", + }, + "invalid input projectID": { + importID: "part1/part2/part3", + projectID: "part1", + username: "part2", + authDBName: "part3", + errorString: "project_id must be a 24 character hex string: part1", + }, + "invalid input with more parts": { + importID: "part1/part2/part3/part4", + projectID: "", + username: "", + authDBName: "", + errorString: invalidDefaultString, + }, + "invalid input with less parts": { + importID: "part1/part2", + projectID: "", + username: "", + authDBName: "", + errorString: invalidDefaultString, + }, + "empty input": { + importID: "", + projectID: "", + username: "", + authDBName: "", + errorString: invalidDefaultString, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + part1, part2, part3, err := databaseuser.SplitDatabaseUserImportID(tc.importID) + if tc.errorString != "" { + assert.EqualError(t, err, tc.errorString) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tc.projectID, part1) + assert.Equal(t, tc.username, part2) + assert.Equal(t, tc.authDBName, part3) + }) + } +} diff --git a/internal/service/databaseuser/resource_database_user.go b/internal/service/databaseuser/resource_database_user.go index ef392a9929..902f2755a6 100644 --- a/internal/service/databaseuser/resource_database_user.go +++ b/internal/service/databaseuser/resource_database_user.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" "github.com/mongodb/terraform-provider-mongodbatlas/internal/config" ) @@ -346,17 +347,19 @@ func (r *databaseUserRS) ImportState(ctx context.Context, req resource.ImportSta } func SplitDatabaseUserImportID(id string) (projectID, username, authDatabaseName string, err error) { + ok, projectID, username, authDatabaseName := conversion.ImportSplit3(id) + if ok { + err = conversion.ValidateProjectID(projectID) + return + } var re = regexp.MustCompile(`(?s)^([0-9a-fA-F]{24})-(.*)-([$a-z]{1,15})$`) parts := re.FindStringSubmatch(id) - if len(parts) != 4 { - err = errors.New("import format error: to import a Database User, use the format {project_id}-{username}-{auth_database_name}") + err = errors.New("import format error: to import a Database User, use the format {project_id}-{username}-{auth_database_name} OR {project_id}/{username}/{auth_database_name}") return } - projectID = parts[1] username = parts[2] authDatabaseName = parts[3] - return } From ab0f229fe2b124172094b1cc150c2317c104beb8 Mon Sep 17 00:00:00 2001 From: Espen Albert Date: Thu, 26 Dec 2024 11:23:19 +0100 Subject: [PATCH 3/6] chore: lint errors --- internal/common/conversion/import_state.go | 2 +- internal/common/conversion/import_state_test.go | 2 +- internal/service/databaseuser/model_database_user_test.go | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/common/conversion/import_state.go b/internal/common/conversion/import_state.go index faf5ac88b7..f4d5f212ec 100644 --- a/internal/common/conversion/import_state.go +++ b/internal/common/conversion/import_state.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" ) -func ImportSplit3(importRaw string) (bool, string, string, string) { +func ImportSplit3(importRaw string) (ok bool, part1, part2, part3 string) { parts := strings.Split(importRaw, "/") if len(parts) != 3 { return false, "", "", "" diff --git a/internal/common/conversion/import_state_test.go b/internal/common/conversion/import_state_test.go index 9727662c8f..42b0ee47dc 100644 --- a/internal/common/conversion/import_state_test.go +++ b/internal/common/conversion/import_state_test.go @@ -27,10 +27,10 @@ func TestValidateClusterName(t *testing.T) { func TestImportSplit3(t *testing.T) { tests := map[string]struct { importRaw string - expected bool part1 string part2 string part3 string + expected bool }{ "valid input": { importRaw: "part1/part2/part3", diff --git a/internal/service/databaseuser/model_database_user_test.go b/internal/service/databaseuser/model_database_user_test.go index 00864f2fd4..dfeaa1aae8 100644 --- a/internal/service/databaseuser/model_database_user_test.go +++ b/internal/service/databaseuser/model_database_user_test.go @@ -9,6 +9,7 @@ import ( "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion" "github.com/mongodb/terraform-provider-mongodbatlas/internal/service/databaseuser" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.mongodb.org/atlas-sdk/v20241113004/admin" ) @@ -420,9 +421,9 @@ func TestSplitDatabaseUserImportID(t *testing.T) { t.Run(name, func(t *testing.T) { part1, part2, part3, err := databaseuser.SplitDatabaseUserImportID(tc.importID) if tc.errorString != "" { - assert.EqualError(t, err, tc.errorString) + require.EqualError(t, err, tc.errorString) } else { - assert.NoError(t, err) + require.NoError(t, err) } assert.Equal(t, tc.projectID, part1) assert.Equal(t, tc.username, part2) From a56ed8f8f06fc0029b8e3c218832a16a63ebf0ed Mon Sep 17 00:00:00 2001 From: Espen Albert Date: Thu, 26 Dec 2024 11:26:52 +0100 Subject: [PATCH 4/6] chore: add changelog entry --- .changelog/2928.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/2928.txt diff --git a/.changelog/2928.txt b/.changelog/2928.txt new file mode 100644 index 0000000000..0b430d4476 --- /dev/null +++ b/.changelog/2928.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/mongodbatlas_database_user: Avoids import error for database_user when both username and auth database contain hyphens +``` From c866b36de8b4db820543ed6a2f791617403e361c Mon Sep 17 00:00:00 2001 From: Espen Albert Date: Mon, 30 Dec 2024 07:10:04 +0000 Subject: [PATCH 5/6] address PR comments --- docs/resources/database_user.md | 8 ++++++-- internal/service/databaseuser/model_database_user_test.go | 7 +++---- internal/service/databaseuser/resource_database_user.go | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/resources/database_user.md b/docs/resources/database_user.md index 8b812f1b2d..0b7c02a85e 100644 --- a/docs/resources/database_user.md +++ b/docs/resources/database_user.md @@ -178,10 +178,14 @@ In addition to all arguments above, the following attributes are exported: ## Import -Database users can be imported using project ID and username, in the format `project_id`-`username`-`auth_database_name`, e.g. +Database users can be imported using project ID, username, and auth database name in the format: + +1. `project_id`-`username`-`auth_database_name` +2. `project_id`/`username`/`auth_database_name` ``` -$ terraform import mongodbatlas_database_user.my_user 1112222b3bf99403840e8934-my_user-admin +terraform import mongodbatlas_database_user.my_user 1112222b3bf99403840e8934-my_user-admin # (1) +terraform import mongodbatlas_database_user.my_user 1112222b3bf99403840e8934/my-username-dash/my-db-name # (2) ``` ~> **NOTE:** Terraform will want to change the password after importing the user if a `password` argument is specified. diff --git a/internal/service/databaseuser/model_database_user_test.go b/internal/service/databaseuser/model_database_user_test.go index dfeaa1aae8..b2d281b045 100644 --- a/internal/service/databaseuser/model_database_user_test.go +++ b/internal/service/databaseuser/model_database_user_test.go @@ -367,7 +367,6 @@ func getDatabaseUserModel(roles, labels, scopes basetypes.SetValue, password typ } func TestSplitDatabaseUserImportID(t *testing.T) { - const invalidDefaultString = "import format error: to import a Database User, use the format {project_id}-{username}-{auth_database_name} OR {project_id}/{username}/{auth_database_name}" tests := map[string]struct { importID string projectID string @@ -399,21 +398,21 @@ func TestSplitDatabaseUserImportID(t *testing.T) { projectID: "", username: "", authDBName: "", - errorString: invalidDefaultString, + errorString: databaseuser.ErrorImportFormat, }, "invalid input with less parts": { importID: "part1/part2", projectID: "", username: "", authDBName: "", - errorString: invalidDefaultString, + errorString: databaseuser.ErrorImportFormat, }, "empty input": { importID: "", projectID: "", username: "", authDBName: "", - errorString: invalidDefaultString, + errorString: databaseuser.ErrorImportFormat, }, } diff --git a/internal/service/databaseuser/resource_database_user.go b/internal/service/databaseuser/resource_database_user.go index 902f2755a6..94a8b7103c 100644 --- a/internal/service/databaseuser/resource_database_user.go +++ b/internal/service/databaseuser/resource_database_user.go @@ -23,6 +23,7 @@ import ( const ( databaseUserResourceName = "database_user" + ErrorImportFormat = "import format error: to import a Database User, use the format {project_id}-{username}-{auth_database_name} OR {project_id}/{username}/{auth_database_name}" ) var _ resource.ResourceWithConfigure = &databaseUserRS{} @@ -355,7 +356,7 @@ func SplitDatabaseUserImportID(id string) (projectID, username, authDatabaseName var re = regexp.MustCompile(`(?s)^([0-9a-fA-F]{24})-(.*)-([$a-z]{1,15})$`) parts := re.FindStringSubmatch(id) if len(parts) != 4 { - err = errors.New("import format error: to import a Database User, use the format {project_id}-{username}-{auth_database_name} OR {project_id}/{username}/{auth_database_name}") + err = errors.New(ErrorImportFormat) return } projectID = parts[1] From caef9ba9e9bc43157e0a267400cfe34fba5bf8c5 Mon Sep 17 00:00:00 2001 From: Espen Albert Date: Tue, 31 Dec 2024 07:38:01 +0000 Subject: [PATCH 6/6] docs: clarify import format for database users in documentation --- docs/resources/database_user.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/resources/database_user.md b/docs/resources/database_user.md index 0b7c02a85e..057831b98e 100644 --- a/docs/resources/database_user.md +++ b/docs/resources/database_user.md @@ -180,8 +180,8 @@ In addition to all arguments above, the following attributes are exported: Database users can be imported using project ID, username, and auth database name in the format: -1. `project_id`-`username`-`auth_database_name` -2. `project_id`/`username`/`auth_database_name` +1. `project_id`-`username`-`auth_database_name` Only works if no `-` are used for `username`/`auth_database_name`. For example `my-username` should use (2). +2. `project_id`/`username`/`auth_database_name` Works in all cases (introduced after (1)) ``` terraform import mongodbatlas_database_user.my_user 1112222b3bf99403840e8934-my_user-admin # (1)