Skip to content

Commit f1c8453

Browse files
csanxCristina Sánchez Sánchez
andauthored
feat: Add new resource mongodbatlas_cloud_user_project_assignment (#3568)
* WIP- Created resource * Added model tests * Added tests * Fixed import * Typo * Preview new SDK version * WIP - Migration test * WIP - migration test * Updated SDK * Revert "Updated SDK" This reverts commit f8d2d0a. * Updated SDK * Fixed migration test * Changelog * CheckDestroy * Refactor * typo * Fix * Added TestNewAtlasUpdateReq * Empty/nil test cases * Fix * Added active user to test * Added MONGODB_ATLAS_USERNAME_2 * Changed Update to read from API * Added comment * feat: Add new singular data source mongodbatlas_cloud_user_project_assignment (#3569) * Add schema and Read() method * Add data source to provider * WIP * Tests * Changed tests * Changelog * Refactor * Typo * Fix * Updated tests * Fixed test * Added error --------- Co-authored-by: Cristina Sánchez Sánchez <[email protected]> --------- Co-authored-by: Cristina Sánchez Sánchez <[email protected]>
1 parent e69da12 commit f1c8453

File tree

13 files changed

+1227
-0
lines changed

13 files changed

+1227
-0
lines changed

.changelog/3568.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-resource
2+
resource/mongodbatlas_cloud_user_project_assignment
3+
```

.changelog/3569.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-datasource
2+
data-source/mongodbatlas_cloud_user_project_assignment
3+
```

.github/workflows/acceptance-tests-runner.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ jobs:
305305
- 'internal/service/controlplaneipaddresses/*.go'
306306
cloud_user:
307307
- 'internal/service/clouduserorgassignment/*.go'
308+
- 'internal/service/clouduserprojectassignment/*.go'
308309
- 'internal/service/clouduserteamassignment/*.go'
309310
cluster:
310311
- 'internal/service/cluster/*.go'
@@ -601,8 +602,10 @@ jobs:
601602
MONGODB_ATLAS_PROJECT_OWNER_ID: ${{ inputs.mongodb_atlas_project_owner_id }}
602603
MONGODB_ATLAS_ORG_ID: ${{ inputs.mongodb_atlas_org_id }}
603604
MONGODB_ATLAS_USERNAME: ${{ vars.MONGODB_ATLAS_USERNAME }}
605+
MONGODB_ATLAS_USERNAME_2: ${{ vars.MONGODB_ATLAS_USERNAME_2 }}
604606
ACCTEST_PACKAGES: |
605607
./internal/service/clouduserorgassignment
608+
./internal/service/clouduserprojectassignment
606609
./internal/service/clouduserteamassignment
607610
run: make testacc
608611

internal/provider/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/apikeyprojectassignment"
3131
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/atlasuser"
3232
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/clouduserorgassignment"
33+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/clouduserprojectassignment"
3334
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/clouduserteamassignment"
3435
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/controlplaneipaddresses"
3536
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/databaseuser"
@@ -462,6 +463,7 @@ func (p *MongodbtlasProvider) DataSources(context.Context) []func() datasource.D
462463
resourcepolicy.DataSource,
463464
resourcepolicy.PluralDataSource,
464465
clouduserorgassignment.DataSource,
466+
clouduserprojectassignment.DataSource,
465467
clouduserteamassignment.DataSource,
466468
teamprojectassignment.DataSource,
467469
apikeyprojectassignment.DataSource,
@@ -491,6 +493,7 @@ func (p *MongodbtlasProvider) Resources(context.Context) []func() resource.Resou
491493
resourcepolicy.Resource,
492494
clouduserorgassignment.Resource,
493495
apikeyprojectassignment.Resource,
496+
clouduserprojectassignment.Resource,
494497
teamprojectassignment.Resource,
495498
clouduserteamassignment.Resource,
496499
advancedclustertpf.Resource,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package clouduserprojectassignment
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/datasource"
7+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
8+
)
9+
10+
var _ datasource.DataSource = &cloudUserProjectAssignmentDS{}
11+
var _ datasource.DataSourceWithConfigure = &cloudUserProjectAssignmentDS{}
12+
13+
func DataSource() datasource.DataSource {
14+
return &cloudUserProjectAssignmentDS{
15+
DSCommon: config.DSCommon{
16+
DataSourceName: resourceName,
17+
},
18+
}
19+
}
20+
21+
type cloudUserProjectAssignmentDS struct {
22+
config.DSCommon
23+
}
24+
25+
func (d *cloudUserProjectAssignmentDS) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
26+
resp.Schema = dataSourceSchema()
27+
}
28+
29+
func (d *cloudUserProjectAssignmentDS) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
30+
var state TFModel
31+
resp.Diagnostics.Append(req.Config.Get(ctx, &state)...)
32+
if resp.Diagnostics.HasError() {
33+
return
34+
}
35+
36+
connV2 := d.Client.AtlasV2
37+
projectID := state.ProjectId.ValueString()
38+
userID := state.UserId.ValueString()
39+
username := state.Username.ValueString()
40+
41+
if username == "" && userID == "" {
42+
resp.Diagnostics.AddError("invalid configuration", "either username or user_id must be provided")
43+
return
44+
}
45+
userResp, err := fetchProjectUser(ctx, connV2, projectID, userID, username)
46+
if err != nil {
47+
resp.Diagnostics.AddError(errorReadingUser, err.Error())
48+
return
49+
}
50+
if userResp == nil {
51+
resp.Diagnostics.AddError("resource not found", "no user found with the specified identifier")
52+
return
53+
}
54+
55+
newCloudUserProjectAssignmentModel, diags := NewTFModel(ctx, projectID, userResp)
56+
if diags.HasError() {
57+
resp.Diagnostics.Append(diags...)
58+
return
59+
}
60+
resp.Diagnostics.Append(resp.State.Set(ctx, newCloudUserProjectAssignmentModel)...)
61+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package clouduserprojectassignment_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
8+
)
9+
10+
func TestMain(m *testing.M) {
11+
cleanup := acc.SetupSharedResources()
12+
exitCode := m.Run()
13+
cleanup()
14+
os.Exit(exitCode)
15+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package clouduserprojectassignment
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/diag"
7+
"github.com/hashicorp/terraform-plugin-framework/types"
8+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
9+
10+
"go.mongodb.org/atlas-sdk/v20250312006/admin"
11+
)
12+
13+
func NewTFModel(ctx context.Context, projectID string, apiResp *admin.GroupUserResponse) (*TFModel, diag.Diagnostics) {
14+
diags := diag.Diagnostics{}
15+
16+
if apiResp == nil {
17+
return nil, diags
18+
}
19+
20+
roles := conversion.TFSetValueOrNull(ctx, &apiResp.Roles, types.StringType)
21+
22+
return &TFModel{
23+
Country: types.StringPointerValue(apiResp.Country),
24+
CreatedAt: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.CreatedAt)),
25+
FirstName: types.StringPointerValue(apiResp.FirstName),
26+
ProjectId: types.StringValue(projectID),
27+
UserId: types.StringValue(apiResp.Id),
28+
InvitationCreatedAt: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.InvitationCreatedAt)),
29+
InvitationExpiresAt: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.InvitationExpiresAt)),
30+
InviterUsername: types.StringPointerValue(apiResp.InviterUsername),
31+
LastAuth: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.LastAuth)),
32+
LastName: types.StringPointerValue(apiResp.LastName),
33+
MobileNumber: types.StringPointerValue(apiResp.MobileNumber),
34+
OrgMembershipStatus: types.StringValue(apiResp.GetOrgMembershipStatus()),
35+
Roles: roles,
36+
Username: types.StringValue(apiResp.GetUsername()),
37+
}, diags
38+
}
39+
40+
func NewProjectUserReq(ctx context.Context, plan *TFModel) (*admin.GroupUserRequest, diag.Diagnostics) {
41+
roleNames := []string{}
42+
if !plan.Roles.IsNull() && !plan.Roles.IsUnknown() {
43+
roleNames = conversion.TypesSetToString(ctx, plan.Roles)
44+
}
45+
46+
addProjectUserReq := admin.GroupUserRequest{
47+
Username: plan.Username.ValueString(),
48+
Roles: roleNames,
49+
}
50+
return &addProjectUserReq, nil
51+
}
52+
53+
func NewAtlasUpdateReq(ctx context.Context, plan *TFModel, currentRoles []string) (addRequests, removeRequests []*admin.AddOrRemoveGroupRole, diags diag.Diagnostics) {
54+
var desiredRoles []string
55+
if !plan.Roles.IsNull() && !plan.Roles.IsUnknown() {
56+
desiredRoles = conversion.TypesSetToString(ctx, plan.Roles)
57+
}
58+
59+
rolesToAdd, rolesToRemove := diffRoles(currentRoles, desiredRoles)
60+
61+
addRequests = make([]*admin.AddOrRemoveGroupRole, 0, len(rolesToAdd))
62+
for _, role := range rolesToAdd {
63+
addRequests = append(addRequests, &admin.AddOrRemoveGroupRole{
64+
GroupRole: role,
65+
})
66+
}
67+
68+
removeRequests = make([]*admin.AddOrRemoveGroupRole, 0, len(rolesToRemove))
69+
for _, role := range rolesToRemove {
70+
removeRequests = append(removeRequests, &admin.AddOrRemoveGroupRole{
71+
GroupRole: role,
72+
})
73+
}
74+
75+
return addRequests, removeRequests, nil
76+
}
77+
78+
func diffRoles(oldRoles, newRoles []string) (toAdd, toRemove []string) {
79+
oldRolesMap := make(map[string]bool, len(oldRoles))
80+
81+
for _, role := range oldRoles {
82+
oldRolesMap[role] = true
83+
}
84+
85+
for _, role := range newRoles {
86+
if oldRolesMap[role] {
87+
delete(oldRolesMap, role)
88+
} else {
89+
toAdd = append(toAdd, role)
90+
}
91+
}
92+
93+
for role := range oldRolesMap {
94+
toRemove = append(toRemove, role)
95+
}
96+
97+
return toAdd, toRemove
98+
}

0 commit comments

Comments
 (0)