Skip to content

Commit f166785

Browse files
csanxCristina Sánchez Sánchez
andauthored
feat: Adds new mongodbatlas_cloud_user_team_assignment resource (#3502)
* Extracted users attribute common schema to dsschema/users_schema.go * Added attribute users to team data source * Modified tests * Modified doc * Added changelog * Created ressource, schema and models * WIP - Implementing Create method * WIP - Added markdown description for attributes in schema and fixed model functions * Fixed variables names * WIP - Implement Read, Update, Delete, Import * Fixed model attributes names. * Minor change * Added tests * Fix * Add github * Fix * Fix * Changelog * Fix * Fixed function naming * Changed test * Changed `project_role_assignments` from List to Set * Changed the attribute name `project_roles_assignments` to project_role_assignments in `organization` and `team` DS for consistency in naming. * minor changes * Fix * Fix * Fix * Changed `project_role_assignments` from List to Set * Removed redundant check * Code improvements * feat: Adds new singular data source `mongodbatlas_cloud_user_team_assignment` (#3517) * Added DS schema generation and Read operation * Add to provider * Fix * Added tests * Changelog * Modified tests * Removed redundant condition * Removed IsUserID --------- Co-authored-by: Cristina Sánchez Sánchez <[email protected]> --------- Co-authored-by: Cristina Sánchez Sánchez <[email protected]>
1 parent 05bf2a7 commit f166785

File tree

16 files changed

+1010
-10
lines changed

16 files changed

+1010
-10
lines changed

.changelog/3502.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_team_assignment
3+
```

.changelog/3517.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_team_assignment
3+
```

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ jobs:
302302
- 'internal/service/controlplaneipaddresses/*.go'
303303
cloud_user:
304304
- 'internal/service/clouduserorgassignment/*.go'
305+
- 'internal/service/clouduserteamassignment/*.go'
305306
cluster:
306307
- 'internal/service/cluster/*.go'
307308
cluster_outage_simulation:
@@ -592,7 +593,7 @@ jobs:
592593
MONGODB_ATLAS_LAST_VERSION: ${{ needs.get-provider-version.outputs.provider_version }}
593594
ACCTEST_PACKAGES: ./internal/service/controlplaneipaddresses
594595
run: make testacc
595-
596+
596597
cloud_user:
597598
needs: [ change-detection, get-provider-version ]
598599
if: ${{ needs.change-detection.outputs.cloud_user == 'true' || inputs.test_group == 'cloud_user' }}
@@ -613,9 +614,13 @@ jobs:
613614
env:
614615
MONGODB_ATLAS_LAST_VERSION: ${{ needs.get-provider-version.outputs.provider_version }}
615616
MONGODB_ATLAS_TEAMS_IDS: ${{ inputs.mongodb_atlas_teams_ids }}
616-
ACCTEST_PACKAGES: ./internal/service/clouduserorgassignment
617+
MONGODB_ATLAS_PROJECT_OWNER_ID: ${{ inputs.mongodb_atlas_project_owner_id }}
618+
MONGODB_ATLAS_ORG_ID: ${{ inputs.mongodb_atlas_org_id }}
619+
ACCTEST_PACKAGES: |
620+
./internal/service/clouduserorgassignment
621+
./internal/service/clouduserteamassignment
617622
run: make testacc
618-
623+
619624
cluster:
620625
needs: [ change-detection, get-provider-version ]
621626
if: ${{ needs.change-detection.outputs.cluster == 'true' || inputs.test_group == 'cluster' }}

internal/common/conversion/flatten_expand.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ func FlattenUsers(users []admin.OrgUserResponse) []map[string]any {
5757
func flattenUserRoles(roles admin.OrgUserRolesResponse) []map[string]any {
5858
ret := make([]map[string]any, 0)
5959
roleMap := map[string]any{
60-
"org_roles": []string{},
61-
"project_roles_assignments": []map[string]any{},
60+
"org_roles": []string{},
61+
"project_role_assignments": []map[string]any{},
6262
}
6363
if roles.HasOrgRoles() {
6464
roleMap["org_roles"] = roles.GetOrgRoles()
6565
}
6666
if roles.HasGroupRoleAssignments() {
67-
roleMap["project_roles_assignments"] = flattenProjectRolesAssignments(roles.GetGroupRoleAssignments())
67+
roleMap["project_role_assignments"] = flattenProjectRolesAssignments(roles.GetGroupRoleAssignments())
6868
}
6969
ret = append(ret, roleMap)
7070
return ret

internal/common/dsschema/users_schema.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func DSOrgUsersSchema() *schema.Schema {
2828
Computed: true,
2929
Elem: &schema.Schema{Type: schema.TypeString},
3030
},
31-
"project_roles_assignments": {
31+
"project_role_assignments": {
3232
Type: schema.TypeSet,
3333
Computed: true,
3434
Elem: &schema.Resource{

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/clouduserteamassignment"
3334
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/controlplaneipaddresses"
3435
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/databaseuser"
3536
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/encryptionatrest"
@@ -461,6 +462,7 @@ func (p *MongodbtlasProvider) DataSources(context.Context) []func() datasource.D
461462
resourcepolicy.DataSource,
462463
resourcepolicy.PluralDataSource,
463464
clouduserorgassignment.DataSource,
465+
clouduserteamassignment.DataSource,
464466
apikeyprojectassignment.DataSource,
465467
apikeyprojectassignment.PluralDataSource,
466468
}
@@ -489,6 +491,7 @@ func (p *MongodbtlasProvider) Resources(context.Context) []func() resource.Resou
489491
resourcepolicy.Resource,
490492
clouduserorgassignment.Resource,
491493
apikeyprojectassignment.Resource,
494+
clouduserteamassignment.Resource,
492495
}
493496
if config.PreviewProviderV2AdvancedCluster() {
494497
resources = append(resources, advancedclustertpf.Resource)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package clouduserteamassignment
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/datasource"
8+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
9+
"go.mongodb.org/atlas-sdk/v20250312005/admin"
10+
)
11+
12+
var _ datasource.DataSource = &cloudUserTeamAssignmentDS{}
13+
var _ datasource.DataSourceWithConfigure = &cloudUserTeamAssignmentDS{}
14+
15+
func DataSource() datasource.DataSource {
16+
return &cloudUserTeamAssignmentDS{
17+
DSCommon: config.DSCommon{
18+
DataSourceName: resourceName,
19+
},
20+
}
21+
}
22+
23+
type cloudUserTeamAssignmentDS struct {
24+
config.DSCommon
25+
}
26+
27+
func (d *cloudUserTeamAssignmentDS) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
28+
resp.Schema = dataSourceSchema()
29+
}
30+
31+
func (d *cloudUserTeamAssignmentDS) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
32+
var state TFUserTeamAssignmentModel
33+
resp.Diagnostics.Append(req.Config.Get(ctx, &state)...)
34+
if resp.Diagnostics.HasError() {
35+
return
36+
}
37+
38+
connV2 := d.Client.AtlasV2
39+
orgID := state.OrgId.ValueString()
40+
teamID := state.TeamId.ValueString()
41+
userID := state.UserId.ValueString()
42+
username := state.Username.ValueString()
43+
44+
if username == "" && userID == "" {
45+
resp.Diagnostics.AddError("invalid configuration", "either username or user_id must be provided")
46+
return
47+
}
48+
49+
var userListResp *admin.PaginatedOrgUser
50+
var userResp *admin.OrgUserResponse
51+
var err error
52+
53+
if userID != "" {
54+
userListResp, _, err = connV2.MongoDBCloudUsersApi.ListTeamUsers(ctx, orgID, teamID).Execute()
55+
if err != nil {
56+
resp.Diagnostics.AddError(fmt.Sprintf("error retrieving resource by user_id: %s", userID), err.Error())
57+
return
58+
}
59+
60+
if userListResp == nil || len(userListResp.GetResults()) == 0 {
61+
resp.Diagnostics.AddError("resource not found", "no user found with the specified user_id")
62+
return
63+
}
64+
results := userListResp.GetResults()
65+
for i := range results {
66+
if results[i].GetId() == userID {
67+
userResp = &results[i]
68+
break
69+
}
70+
}
71+
} else if username != "" {
72+
params := &admin.ListTeamUsersApiParams{
73+
OrgId: orgID,
74+
TeamId: teamID,
75+
Username: &username,
76+
}
77+
userListResp, _, err = connV2.MongoDBCloudUsersApi.ListTeamUsersWithParams(ctx, params).Execute()
78+
if err != nil {
79+
resp.Diagnostics.AddError(fmt.Sprintf("error retrieving resource by username: %s", username), err.Error())
80+
return
81+
}
82+
83+
if userListResp == nil || len(userListResp.GetResults()) == 0 {
84+
resp.Diagnostics.AddError("resource not found", "no user found with the specified username")
85+
return
86+
}
87+
88+
userResp = &(userListResp.GetResults())[0]
89+
}
90+
91+
if userResp == nil {
92+
resp.State.RemoveResource(ctx)
93+
return
94+
}
95+
96+
newCloudUserTeamAssignmentModel, diags := NewTFUserTeamAssignmentModel(ctx, userResp)
97+
if diags.HasError() {
98+
resp.Diagnostics.Append(diags...)
99+
return
100+
}
101+
102+
newCloudUserTeamAssignmentModel.OrgId = state.OrgId
103+
newCloudUserTeamAssignmentModel.TeamId = state.TeamId
104+
resp.Diagnostics.Append(resp.State.Set(ctx, newCloudUserTeamAssignmentModel)...)
105+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package clouduserteamassignment_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: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package clouduserteamassignment
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+
"go.mongodb.org/atlas-sdk/v20250312005/admin"
10+
)
11+
12+
func NewTFUserTeamAssignmentModel(ctx context.Context, apiResp *admin.OrgUserResponse) (*TFUserTeamAssignmentModel, diag.Diagnostics) {
13+
diags := diag.Diagnostics{}
14+
15+
if apiResp == nil {
16+
return nil, diags
17+
}
18+
19+
rolesObj, rolesDiags := NewTFRolesModel(ctx, &apiResp.Roles)
20+
diags.Append(rolesDiags...)
21+
22+
teamIDs := conversion.TFSetValueOrNull(ctx, apiResp.TeamIds, types.StringType)
23+
24+
userTeamAssignment := TFUserTeamAssignmentModel{
25+
UserId: types.StringValue(apiResp.GetId()),
26+
Username: types.StringValue(apiResp.GetUsername()),
27+
OrgMembershipStatus: types.StringValue(apiResp.GetOrgMembershipStatus()),
28+
Roles: rolesObj,
29+
TeamIds: teamIDs,
30+
InvitationCreatedAt: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.InvitationCreatedAt)),
31+
InvitationExpiresAt: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.InvitationExpiresAt)),
32+
InviterUsername: types.StringPointerValue(apiResp.InviterUsername),
33+
Country: types.StringPointerValue(apiResp.Country),
34+
FirstName: types.StringPointerValue(apiResp.FirstName),
35+
LastName: types.StringPointerValue(apiResp.LastName),
36+
CreatedAt: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.CreatedAt)),
37+
LastAuth: types.StringPointerValue(conversion.TimePtrToStringPtr(apiResp.LastAuth)),
38+
MobileNumber: types.StringPointerValue(apiResp.MobileNumber),
39+
}
40+
41+
return &userTeamAssignment, nil
42+
}
43+
44+
func NewTFRolesModel(ctx context.Context, roles *admin.OrgUserRolesResponse) (types.Object, diag.Diagnostics) {
45+
diags := diag.Diagnostics{}
46+
47+
if roles == nil {
48+
return types.ObjectNull(RolesObjectAttrTypes), diags
49+
}
50+
51+
orgRoles := conversion.TFSetValueOrNull(ctx, roles.OrgRoles, types.StringType)
52+
53+
projectRoleAssignmentsSet := NewTFProjectRoleAssignments(ctx, roles.GroupRoleAssignments)
54+
55+
rolesModel := TFRolesModel{
56+
OrgRoles: orgRoles,
57+
ProjectRoleAssignments: projectRoleAssignmentsSet,
58+
}
59+
60+
rolesObj, _ := types.ObjectValueFrom(ctx, RolesObjectAttrTypes, rolesModel)
61+
return rolesObj, diags
62+
}
63+
64+
func NewTFProjectRoleAssignments(ctx context.Context, groupRoleAssignments *[]admin.GroupRoleAssignment) types.Set {
65+
if groupRoleAssignments == nil {
66+
return types.SetNull(ProjectRoleAssignmentsAttrType)
67+
}
68+
69+
var projectRoleAssignments []TFProjectRoleAssignmentsModel
70+
71+
for _, pra := range *groupRoleAssignments {
72+
projectID := types.StringPointerValue(pra.GroupId)
73+
projectRoles := conversion.TFSetValueOrNull(ctx, pra.GroupRoles, types.StringType)
74+
75+
projectRoleAssignments = append(projectRoleAssignments, TFProjectRoleAssignmentsModel{
76+
ProjectId: projectID,
77+
ProjectRoles: projectRoles,
78+
})
79+
}
80+
81+
praSet, _ := types.SetValueFrom(ctx, ProjectRoleAssignmentsAttrType.ElemType.(types.ObjectType), projectRoleAssignments)
82+
return praSet
83+
}
84+
85+
func NewUserTeamAssignmentReq(ctx context.Context, plan *TFUserTeamAssignmentModel) (*admin.AddOrRemoveUserFromTeam, diag.Diagnostics) {
86+
addOrRemoveUserFromTeam := admin.AddOrRemoveUserFromTeam{
87+
Id: plan.UserId.ValueString(),
88+
}
89+
return &addOrRemoveUserFromTeam, nil
90+
}

0 commit comments

Comments
 (0)