Skip to content

feat: Add new resource mongodbatlas_cloud_user_project_assignment #3568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: CLOUDP-320243-dev-2.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
84513be
WIP- Created resource
Jul 28, 2025
cb5b312
Added model tests
Jul 28, 2025
b92ebf9
Added tests
Jul 28, 2025
2f6bfee
Fixed import
Jul 29, 2025
bc7fe7b
Typo
Jul 30, 2025
8d3db26
Merge branch 'CLOUDP-320243-dev-2.0.0' into CLOUDP-333176
Jul 30, 2025
7271d8a
Merge branch 'CLOUDP-320243-dev-2.0.0' into CLOUDP-333176
Aug 6, 2025
9601faa
Preview new SDK version
Aug 6, 2025
fdd1ded
WIP - Migration test
Aug 6, 2025
b42949c
WIP - migration test
Aug 7, 2025
f8d2d0a
Updated SDK
Aug 7, 2025
ece5d8d
Revert "Updated SDK"
Aug 7, 2025
c7029fc
Merge branch 'CLOUDP-320243-dev-2.0.0' of github.com:mongodb/terrafor…
Aug 7, 2025
ece229a
Merge branch 'CLOUDP-320243-dev-2.0.0' into CLOUDP-333176
Aug 7, 2025
8690f0e
Updated SDK
Aug 7, 2025
fa00932
Fixed migration test
Aug 7, 2025
21b815a
Changelog
Aug 7, 2025
4e3f64a
Merge branch 'CLOUDP-320243-dev-2.0.0' of github.com:mongodb/terrafor…
Aug 7, 2025
0e54c53
Merge branch 'CLOUDP-320243-dev-2.0.0' into CLOUDP-333176
Aug 7, 2025
072a4b2
Merge branch 'CLOUDP-320243-dev-2.0.0' into CLOUDP-333176
Aug 11, 2025
b396420
CheckDestroy
Aug 11, 2025
aa22dc6
Refactor
Aug 11, 2025
d2d1136
typo
Aug 11, 2025
542fa5e
Fix
Aug 11, 2025
c48e857
Added TestNewAtlasUpdateReq
Aug 11, 2025
59950ba
Empty/nil test cases
Aug 12, 2025
e011813
Fix
Aug 12, 2025
12bbfa7
Added active user to test
Aug 12, 2025
866306d
Added MONGODB_ATLAS_USERNAME_2
Aug 12, 2025
a2ea83a
Changed Update to read from API
Aug 12, 2025
781231d
Added comment
Aug 13, 2025
bb955c5
feat: Add new singular data source mongodbatlas_cloud_user_project_as…
csanx Aug 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/3568.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
resource/mongodbatlas_cloud_user_project_assignment
```
3 changes: 3 additions & 0 deletions .github/workflows/acceptance-tests-runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ jobs:
- 'internal/service/controlplaneipaddresses/*.go'
cloud_user:
- 'internal/service/clouduserorgassignment/*.go'
- 'internal/service/clouduserprojectassignment/*.go'
- 'internal/service/clouduserteamassignment/*.go'
cluster:
- 'internal/service/cluster/*.go'
Expand Down Expand Up @@ -601,8 +602,10 @@ jobs:
MONGODB_ATLAS_PROJECT_OWNER_ID: ${{ inputs.mongodb_atlas_project_owner_id }}
MONGODB_ATLAS_ORG_ID: ${{ inputs.mongodb_atlas_org_id }}
MONGODB_ATLAS_USERNAME: ${{ vars.MONGODB_ATLAS_USERNAME }}
MONGODB_ATLAS_USERNAME_2: ${{ vars.MONGODB_ATLAS_USERNAME_2 }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any difference between MONGODB_ATLAS_USERNAME & MONGODB_ATLAS_USERNAME_2? or we're just adding for more test cases? thinking if we should add a comment here on what MONGODB_ATLAS_USERNAME_2 signifies

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe MONGODB_ATLAS_USERNAME has the Org Owner role, so if no Project Owner is specified, that user is assigned automatically. Then, when I try to use the user_project_assignment resource, I get this error:
POST: HTTP 400 Bad Request (Error code: "USER_ALREADY_IN_GROUP")
That’s why I used MONGODB_ATLAS_USERNAME_2 instead. I’ll add a comment in the code to clarify this.

ACCTEST_PACKAGES: |
./internal/service/clouduserorgassignment
./internal/service/clouduserprojectassignment
./internal/service/clouduserteamassignment
run: make testacc

Expand Down
2 changes: 2 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/apikeyprojectassignment"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/atlasuser"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/clouduserorgassignment"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/clouduserprojectassignment"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/clouduserteamassignment"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/controlplaneipaddresses"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/databaseuser"
Expand Down Expand Up @@ -491,6 +492,7 @@ func (p *MongodbtlasProvider) Resources(context.Context) []func() resource.Resou
resourcepolicy.Resource,
clouduserorgassignment.Resource,
apikeyprojectassignment.Resource,
clouduserprojectassignment.Resource,
teamprojectassignment.Resource,
clouduserteamassignment.Resource,
advancedclustertpf.Resource,
Expand Down
15 changes: 15 additions & 0 deletions internal/service/clouduserprojectassignment/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package clouduserprojectassignment_test

import (
"os"
"testing"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
)

func TestMain(m *testing.M) {
cleanup := acc.SetupSharedResources()
exitCode := m.Run()
cleanup()
os.Exit(exitCode)
}
98 changes: 98 additions & 0 deletions internal/service/clouduserprojectassignment/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package clouduserprojectassignment

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"

"go.mongodb.org/atlas-sdk/v20250312006/admin"
)

func NewTFModel(ctx context.Context, projectID string, apiResp *admin.GroupUserResponse) (*TFModel, diag.Diagnostics) {
diags := diag.Diagnostics{}

if apiResp == nil {
return nil, diags
}

roles := conversion.TFSetValueOrNull(ctx, &apiResp.Roles, types.StringType)

return &TFModel{
Country: types.StringPointerValue(apiResp.Country),
CreatedAt: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.CreatedAt)),
FirstName: types.StringPointerValue(apiResp.FirstName),
ProjectId: types.StringValue(projectID),
UserId: types.StringValue(apiResp.GetId()),
InvitationCreatedAt: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.InvitationCreatedAt)),
InvitationExpiresAt: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.InvitationExpiresAt)),
InviterUsername: types.StringPointerValue(apiResp.InviterUsername),
LastAuth: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.LastAuth)),
LastName: types.StringPointerValue(apiResp.LastName),
MobileNumber: types.StringPointerValue(apiResp.MobileNumber),
OrgMembershipStatus: types.StringValue(apiResp.GetOrgMembershipStatus()),
Roles: roles,
Username: types.StringValue(apiResp.GetUsername()),
}, diags
}

func NewProjectUserReq(ctx context.Context, plan *TFModel) (*admin.GroupUserRequest, diag.Diagnostics) {
roleNames := []string{}
if !plan.Roles.IsNull() && !plan.Roles.IsUnknown() {
roleNames = conversion.TypesSetToString(ctx, plan.Roles)
}

addProjectUserReq := admin.GroupUserRequest{
Username: plan.Username.ValueString(),
Roles: roleNames,
}
return &addProjectUserReq, nil
}

func NewAtlasUpdateReq(ctx context.Context, plan *TFModel, currentRoles []string) (addRequests, removeRequests []*admin.AddOrRemoveGroupRole, diags diag.Diagnostics) {
var desiredRoles []string
if !plan.Roles.IsNull() && !plan.Roles.IsUnknown() {
desiredRoles = conversion.TypesSetToString(ctx, plan.Roles)
}

rolesToAdd, rolesToRemove := diffRoles(currentRoles, desiredRoles)

addRequests = make([]*admin.AddOrRemoveGroupRole, 0, len(rolesToAdd))
for _, role := range rolesToAdd {
addRequests = append(addRequests, &admin.AddOrRemoveGroupRole{
GroupRole: role,
})
}

removeRequests = make([]*admin.AddOrRemoveGroupRole, 0, len(rolesToRemove))
for _, role := range rolesToRemove {
removeRequests = append(removeRequests, &admin.AddOrRemoveGroupRole{
GroupRole: role,
})
}

return addRequests, removeRequests, nil
}

func diffRoles(oldRoles, newRoles []string) (toAdd, toRemove []string) {
oldRolesMap := make(map[string]bool, len(oldRoles))

for _, role := range oldRoles {
oldRolesMap[role] = true
}

for _, role := range newRoles {
if oldRolesMap[role] {
delete(oldRolesMap, role)
} else {
toAdd = append(toAdd, role)
}
}

for role := range oldRolesMap {
toRemove = append(toRemove, role)
}

return toAdd, toRemove
}
Loading