Skip to content

Commit f7d100e

Browse files
authored
feat: Adds description field to mongodbatlas_database_user (#3280)
* feat: Adds description field to `mongodbatlas_database_user` * test: Adds description test for database user * test: Update unit tests * refactor: Support not sending empty description on create * update tests * feat: Adds description field to data sources for `mongodbatlas_database_user` * chore: Add changelog entry * doc: Document new description field * refactor: rename `TfDatabaseUserModel` variables * rename to local diags * move importID split to ImportState function * fix: only switch back to state value when response is empty and state is null
1 parent 9ec1e33 commit f7d100e

14 files changed

+120
-71
lines changed

.changelog/3280.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
```release-note:enhancement
2+
resource/mongodbatlas_database_user: Adds `description` field
3+
```
4+
5+
```release-note:enhancement
6+
data-source/mongodbatlas_database_user: Adds `description` field
7+
```
8+
9+
```release-note:enhancement
10+
data-source/mongodbatlas_database_users: Adds `description` field
11+
```

docs/data-sources/database_user.md

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Note: OIDC support is only avalible starting in [MongoDB 7.0](https://www.mongod
7575
In addition to all arguments above, the following attributes are exported:
7676

7777
* `id` - Autogenerated Unique ID for this data source.
78+
* `description` - Description of this database user.
7879
* `roles` - List of user’s roles and the databases / collections on which the roles apply. A role allows the user to perform particular actions on the specified database. A role on the admin database can include privileges that apply to the other databases as well. See [Roles](#roles) below for more details.
7980
* `x509_type` - X.509 method by which the provided username is authenticated.
8081
* `aws_iam_type` - The new database user authenticates with AWS IAM credentials. Default is `NONE`, `USER` means user has AWS IAM user credentials, `ROLE` - means user has credentials associated with an AWS IAM role.

docs/data-sources/database_users.md

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ In addition to all arguments above, the following attributes are exported:
7777

7878
* `project_id` - ID of the Atlas project the user belongs to.
7979
* `username` - Username for authenticating to MongoDB.
80+
* `description` - Description of this database user.
8081
* `roles` - List of user’s roles and the databases / collections on which the roles apply. A role allows the user to perform particular actions on the specified database. A role on the admin database can include privileges that apply to the other databases as well. See [Roles](#roles) below for more details.
8182
* `auth_database_name` - (Required) Database against which Atlas authenticates the user. A user must provide both a username and authentication database to log into MongoDB.
8283
Possible values include:

docs/resources/database_user.md

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ Accepted values include:
125125
* `roles` - (Required) List of user’s roles and the databases / collections on which the roles apply. A role allows the user to perform particular actions on the specified database. A role on the admin database can include privileges that apply to the other databases as well. See [Roles](#roles) below for more details.
126126
* `username` - (Required) Username for authenticating to MongoDB. USER_ARN or ROLE_ARN if `aws_iam_type` is USER or ROLE.
127127
* `password` - (Required) User's initial password. A value is required to create the database user, however the argument may be removed from your Terraform configuration after user creation without impacting the user, password or Terraform management. If you do change management of the password to outside of Terraform it is advised to remove the argument from the Terraform configuration. IMPORTANT --- Passwords may show up in Terraform related logs and it will be stored in the Terraform state file as plain-text. Password can be changed after creation using your preferred method, e.g. via the MongoDB Atlas UI, to ensure security.
128+
* `description` - (Optional) Description of this database user.
128129

129130
* `x509_type` - (Optional) X.509 method by which the provided username is authenticated. If no value is given, Atlas uses the default value of NONE. The accepted types are:
130131
* `NONE` - The user does not use X.509 authentication.

internal/service/databaseuser/data_source_database_user.go

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type TfDatabaseUserDSModel struct {
2626
ProjectID types.String `tfsdk:"project_id"`
2727
AuthDatabaseName types.String `tfsdk:"auth_database_name"`
2828
Username types.String `tfsdk:"username"`
29+
Description types.String `tfsdk:"description"`
2930
X509Type types.String `tfsdk:"x509_type"`
3031
OIDCAuthType types.String `tfsdk:"oidc_auth_type"`
3132
LDAPAuthType types.String `tfsdk:"ldap_auth_type"`
@@ -53,6 +54,9 @@ func (d *databaseUserDS) Schema(ctx context.Context, req datasource.SchemaReques
5354
"username": schema.StringAttribute{
5455
Required: true,
5556
},
57+
"description": schema.StringAttribute{
58+
Computed: true,
59+
},
5660
"x509_type": schema.StringAttribute{
5761
Computed: true,
5862
},

internal/service/databaseuser/data_source_database_user_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestAccConfigDSDatabaseUser_basic(t *testing.T) {
3030
resource.TestCheckResourceAttr(dataSourceName, "roles.0.role_name", roleName),
3131
resource.TestCheckResourceAttr(dataSourceName, "roles.0.database_name", "admin"),
3232
resource.TestCheckResourceAttr(dataSourceName, "labels.#", "2"),
33+
resource.TestCheckNoResourceAttr(dataSourceName, "description"),
3334
),
3435
},
3536
},

internal/service/databaseuser/data_source_database_users.go

+3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ func (d *DatabaseUsersDS) Schema(ctx context.Context, req datasource.SchemaReque
6060
"username": schema.StringAttribute{
6161
Computed: true,
6262
},
63+
"description": schema.StringAttribute{
64+
Computed: true,
65+
},
6366
"x509_type": schema.StringAttribute{
6467
Computed: true,
6568
},

internal/service/databaseuser/data_source_database_users_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ func TestAccConfigDSDatabaseUsers_basic(t *testing.T) {
2727
resource.TestCheckResourceAttrSet(dataSourcePluralName, "results.0.username"),
2828
resource.TestCheckResourceAttrSet(dataSourcePluralName, "results.0.roles.#"),
2929
resource.TestCheckResourceAttrSet(dataSourcePluralName, "results.0.labels.#"),
30+
resource.TestCheckNoResourceAttr(dataSourcePluralName, "results.0.description"),
3031
resource.TestCheckResourceAttrSet(dataSourcePluralName, "results.1.username"),
3132
resource.TestCheckResourceAttrSet(dataSourcePluralName, "results.1.roles.#"),
3233
resource.TestCheckResourceAttrSet(dataSourcePluralName, "results.1.labels.#"),
34+
resource.TestCheckNoResourceAttr(dataSourcePluralName, "results.1.description"),
3335
),
3436
},
3537
},

internal/service/databaseuser/model_database_user.go

+30-19
Original file line numberDiff line numberDiff line change
@@ -11,47 +11,53 @@ import (
1111
"go.mongodb.org/atlas-sdk/v20250312002/admin"
1212
)
1313

14-
func NewMongoDBDatabaseUser(ctx context.Context, statePasswordValue types.String, dbUserModel *TfDatabaseUserModel) (*admin.CloudDatabaseUser, diag.Diagnostics) {
14+
func NewMongoDBDatabaseUser(ctx context.Context, statePasswordValue, stateDescriptionValue types.String, plan *TfDatabaseUserModel) (*admin.CloudDatabaseUser, diag.Diagnostics) {
1515
var rolesModel []*TfRoleModel
1616
var labelsModel []*TfLabelModel
1717
var scopesModel []*TfScopeModel
1818

19-
diags := dbUserModel.Roles.ElementsAs(ctx, &rolesModel, false)
19+
diags := plan.Roles.ElementsAs(ctx, &rolesModel, false)
2020
if diags.HasError() {
2121
return nil, diags
2222
}
2323

24-
diags = dbUserModel.Labels.ElementsAs(ctx, &labelsModel, false)
24+
diags = plan.Labels.ElementsAs(ctx, &labelsModel, false)
2525
if diags.HasError() {
2626
return nil, diags
2727
}
2828

29-
diags = dbUserModel.Scopes.ElementsAs(ctx, &scopesModel, false)
29+
diags = plan.Scopes.ElementsAs(ctx, &scopesModel, false)
3030
if diags.HasError() {
3131
return nil, diags
3232
}
3333

3434
result := admin.CloudDatabaseUser{
35-
GroupId: dbUserModel.ProjectID.ValueString(),
36-
Username: dbUserModel.Username.ValueString(),
37-
X509Type: dbUserModel.X509Type.ValueStringPointer(),
38-
AwsIAMType: dbUserModel.AWSIAMType.ValueStringPointer(),
39-
OidcAuthType: dbUserModel.OIDCAuthType.ValueStringPointer(),
40-
LdapAuthType: dbUserModel.LDAPAuthType.ValueStringPointer(),
41-
DatabaseName: dbUserModel.AuthDatabaseName.ValueString(),
35+
GroupId: plan.ProjectID.ValueString(),
36+
Username: plan.Username.ValueString(),
37+
Description: plan.Description.ValueStringPointer(),
38+
X509Type: plan.X509Type.ValueStringPointer(),
39+
AwsIAMType: plan.AWSIAMType.ValueStringPointer(),
40+
OidcAuthType: plan.OIDCAuthType.ValueStringPointer(),
41+
LdapAuthType: plan.LDAPAuthType.ValueStringPointer(),
42+
DatabaseName: plan.AuthDatabaseName.ValueString(),
4243
Roles: NewMongoDBAtlasRoles(rolesModel),
4344
Labels: NewMongoDBAtlasLabels(labelsModel),
4445
Scopes: NewMongoDBAtlasScopes(scopesModel),
4546
}
4647

47-
if statePasswordValue != dbUserModel.Password {
48+
if statePasswordValue != plan.Password {
4849
// Password value has been modified or no previous state was present. Password is only updated if changed in the terraform configuration CLOUDP-235738
49-
result.Password = dbUserModel.Password.ValueStringPointer()
50+
result.Password = plan.Password.ValueStringPointer()
51+
}
52+
if plan.Description.IsNull() && !stateDescriptionValue.Equal(plan.Description) {
53+
// description is an optional attribute (i.e. null by default), if it is removed from the config during an update
54+
// (i.e. user wants to remove the existing description from the database user), we send an empty string ("") as the value in API request for update (dumping null is not supported in the SDK)
55+
result.Description = conversion.Pointer("")
5056
}
5157
return &result, nil
5258
}
5359

54-
func NewTfDatabaseUserModel(ctx context.Context, model *TfDatabaseUserModel, dbUser *admin.CloudDatabaseUser) (*TfDatabaseUserModel, diag.Diagnostics) {
60+
func NewTfDatabaseUserModel(ctx context.Context, inModel *TfDatabaseUserModel, dbUser *admin.CloudDatabaseUser) (*TfDatabaseUserModel, diag.Diagnostics) {
5561
rolesSet, diagnostic := types.SetValueFrom(ctx, RoleObjectType, NewTFRolesModel(dbUser.GetRoles()))
5662
if diagnostic.HasError() {
5763
return nil, diagnostic
@@ -73,11 +79,12 @@ func NewTfDatabaseUserModel(ctx context.Context, model *TfDatabaseUserModel, dbU
7379
"username": dbUser.Username,
7480
"auth_database_name": dbUser.DatabaseName,
7581
})
76-
databaseUserModel := &TfDatabaseUserModel{
82+
outModel := &TfDatabaseUserModel{
7783
ID: types.StringValue(encodedID),
7884
ProjectID: types.StringValue(dbUser.GroupId),
7985
AuthDatabaseName: types.StringValue(dbUser.DatabaseName),
8086
Username: types.StringValue(dbUser.Username),
87+
Description: types.StringPointerValue(dbUser.Description),
8188
X509Type: types.StringValue(dbUser.GetX509Type()),
8289
OIDCAuthType: types.StringValue(dbUser.GetOidcAuthType()),
8390
LDAPAuthType: types.StringValue(dbUser.GetLdapAuthType()),
@@ -87,12 +94,15 @@ func NewTfDatabaseUserModel(ctx context.Context, model *TfDatabaseUserModel, dbU
8794
Scopes: scopesSet,
8895
}
8996

90-
if model != nil && model.Password.ValueString() != "" {
97+
if inModel != nil && inModel.Password.ValueString() != "" {
9198
// The Password is not retuned from the endpoint so we use the one provided in the model
92-
databaseUserModel.Password = model.Password
99+
outModel.Password = inModel.Password
93100
}
94-
95-
return databaseUserModel, nil
101+
if inModel != nil && outModel.Description.Equal(types.StringValue("")) && inModel.Description.IsNull() {
102+
// null != "" in TPF: Error: Provider produced inconsistent result after apply. .description: was null, but now cty.StringVal("")
103+
outModel.Description = types.StringNull()
104+
}
105+
return outModel, nil
96106
}
97107

98108
func NewTFDatabaseDSUserModel(ctx context.Context, dbUser *admin.CloudDatabaseUser) (*TfDatabaseUserDSModel, diag.Diagnostics) {
@@ -102,6 +112,7 @@ func NewTFDatabaseDSUserModel(ctx context.Context, dbUser *admin.CloudDatabaseUs
102112
ProjectID: types.StringValue(dbUser.GroupId),
103113
AuthDatabaseName: types.StringValue(dbUser.DatabaseName),
104114
Username: types.StringValue(dbUser.Username),
115+
Description: types.StringPointerValue(dbUser.Description),
105116
X509Type: types.StringValue(dbUser.GetX509Type()),
106117
OIDCAuthType: types.StringValue(dbUser.GetOidcAuthType()),
107118
LDAPAuthType: types.StringValue(dbUser.GetLdapAuthType()),

internal/service/databaseuser/model_database_user_test.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var (
6565
GroupId: projectID,
6666
DatabaseName: authDatabaseName,
6767
Username: username,
68+
Description: conversion.Pointer(""),
6869
Password: &password,
6970
X509Type: &x509Type,
7071
OidcAuthType: &oidCAuthType,
@@ -77,6 +78,7 @@ var (
7778
cloudDatabaseUserWithoutPassword = &admin.CloudDatabaseUser{
7879
GroupId: projectID,
7980
DatabaseName: authDatabaseName,
81+
Description: conversion.Pointer(""),
8082
Username: username,
8183
X509Type: &x509Type,
8284
OidcAuthType: &oidCAuthType,
@@ -143,7 +145,7 @@ func TestNewMongoDBDatabaseUser(t *testing.T) {
143145

144146
for i, tc := range testCases {
145147
t.Run(tc.name, func(t *testing.T) {
146-
resultModel, err := databaseuser.NewMongoDBDatabaseUser(t.Context(), tc.passwordStateValue, &testCases[i].tfDatabaseUserModel)
148+
resultModel, err := databaseuser.NewMongoDBDatabaseUser(t.Context(), tc.passwordStateValue, types.StringValue(""), &testCases[i].tfDatabaseUserModel)
147149

148150
if (err != nil) != tc.expectedError {
149151
t.Errorf("Case %s: Received unexpected error: %v", tc.name, err)
@@ -164,7 +166,7 @@ func TestNewTfDatabaseUserModel(t *testing.T) {
164166
{
165167
name: "Success TfDatabaseUserModel",
166168
sdkDatabaseUser: cloudDatabaseUser,
167-
currentModel: databaseuser.TfDatabaseUserModel{Password: types.StringValue(password)},
169+
currentModel: databaseuser.TfDatabaseUserModel{Password: types.StringValue(password), Description: types.StringValue("")},
168170
expectedResult: getDatabaseUserModel(rolesSet, labelsSet, scopesSet, types.StringValue(password)),
169171
expectedError: false,
170172
},
@@ -355,6 +357,7 @@ func getDatabaseUserModel(roles, labels, scopes basetypes.SetValue, password typ
355357
ProjectID: types.StringValue(projectID),
356358
AuthDatabaseName: types.StringValue(authDatabaseName),
357359
Username: types.StringValue(username),
360+
Description: types.StringValue(""),
358361
Password: password,
359362
X509Type: types.StringValue(x509Type),
360363
OIDCAuthType: types.StringValue(oidCAuthType),

0 commit comments

Comments
 (0)