Skip to content

Commit 25b52dd

Browse files
fix: Support update of usernames in mongodbatlas_team resource by ensuring team is never empty (#2669)
* include new update logic with acceptance test and unit testing * adding changelog entry * fmt fix * define additional username env variable in CI, use same values for dev and qa * remove redundant initialization
1 parent 232c150 commit 25b52dd

File tree

8 files changed

+312
-57
lines changed

8 files changed

+312
-57
lines changed

.changelog/2669.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
resource/mongodbatlas_team: Fixes update logic of `usernames` attribute ensuring team is never emptied
3+
```

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ on:
3838
mongodb_atlas_teams_ids:
3939
type: string
4040
required: true
41-
mongodb_atlas_username:
42-
type: string
43-
required: true
4441
azure_atlas_app_id:
4542
type: string
4643
required: true
@@ -503,7 +500,8 @@ jobs:
503500
- name: Acceptance Tests
504501
env:
505502
MONGODB_ATLAS_PROJECT_OWNER_ID: ${{ inputs.mongodb_atlas_project_owner_id }}
506-
MONGODB_ATLAS_USERNAME: ${{ inputs.mongodb_atlas_username }}
503+
MONGODB_ATLAS_USERNAME: ${{ vars.MONGODB_ATLAS_USERNAME }}
504+
MONGODB_ATLAS_USERNAME_2: ${{ vars.MONGODB_ATLAS_USERNAME_2 }}
507505
AZURE_ATLAS_APP_ID: ${{ inputs.azure_atlas_app_id }}
508506
AZURE_SERVICE_PRINCIPAL_ID: ${{ inputs.azure_service_principal_id }}
509507
AZURE_TENANT_ID: ${{ inputs.azure_tenant_id }}

.github/workflows/acceptance-tests.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ jobs:
9797
mongodb_atlas_base_url: ${{ inputs.atlas_cloud_env == 'qa' && vars.MONGODB_ATLAS_BASE_URL_QA || vars.MONGODB_ATLAS_BASE_URL }}
9898
mongodb_atlas_project_owner_id: ${{ inputs.atlas_cloud_env == 'qa' && vars.MONGODB_ATLAS_PROJECT_OWNER_ID_QA || vars.MONGODB_ATLAS_PROJECT_OWNER_ID }}
9999
mongodb_atlas_teams_ids: ${{ inputs.atlas_cloud_env == 'qa' && vars.MONGODB_ATLAS_TEAMS_IDS_QA || vars.MONGODB_ATLAS_TEAMS_IDS }}
100-
mongodb_atlas_username: ${{ inputs.atlas_cloud_env == 'qa' && vars.MONGODB_ATLAS_USERNAME_CLOUD_QA || vars.MONGODB_ATLAS_USERNAME_CLOUD_DEV }}
101100
azure_atlas_app_id: ${{ inputs.atlas_cloud_env == 'qa' && vars.AZURE_ATLAS_APP_ID_QA || vars.AZURE_ATLAS_APP_ID }}
102101
azure_service_principal_id: ${{ inputs.atlas_cloud_env == 'qa' && vars.AZURE_SERVICE_PRINCIPAL_ID_QA || vars.AZURE_SERVICE_PRINCIPAL_ID }}
103102
azure_tenant_id: ${{ inputs.atlas_cloud_env == 'qa' && vars.AZURE_TENANT_ID_QA || vars.AZURE_TENANT_ID }}

internal/service/team/resource_team.go

Lines changed: 4 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -148,63 +148,15 @@ func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.
148148
}
149149

150150
if d.HasChange("usernames") {
151-
users, _, err := connV2.TeamsApi.ListTeamUsers(ctx, orgID, teamID).Execute()
152-
151+
existingUsers, _, err := connV2.TeamsApi.ListTeamUsers(ctx, orgID, teamID).Execute()
153152
if err != nil {
154153
return diag.FromErr(fmt.Errorf(errorTeamRead, err))
155154
}
155+
newUsernames := conversion.ExpandStringList(d.Get("usernames").(*schema.Set).List())
156156

157-
index := make(map[string]admin.CloudAppUser)
158-
for i := range users.GetResults() {
159-
index[users.GetResults()[i].GetUsername()] = users.GetResults()[i]
160-
}
161-
162-
cleanUsers := func() error {
163-
for i := range users.GetResults() {
164-
_, err := connV2.TeamsApi.RemoveTeamUser(ctx, orgID, teamID, users.GetResults()[i].GetId()).Execute()
165-
if err != nil {
166-
return fmt.Errorf("error deleting Atlas User (%s) information: %s", teamID, err)
167-
}
168-
}
169-
return nil
170-
}
171-
172-
var newUsers []admin.AddUserToTeam
173-
174-
for _, username := range d.Get("usernames").(*schema.Set).List() {
175-
user, _, err := connV2.MongoDBCloudUsersApi.GetUserByUsername(ctx, username.(string)).Execute()
176-
177-
updatedUserData := user
178-
179-
if err != nil {
180-
if !strings.Contains(err.Error(), "401") {
181-
return diag.FromErr(fmt.Errorf("error getting Atlas User (%s) information: %s", username, err))
182-
}
183-
184-
log.Printf("[WARN] error fetching information user for (%s): %s\n", username, err)
185-
if user == nil {
186-
log.Printf("[WARN] there is no runtime information to fetch, checking in the existing users")
187-
188-
cached, ok := index[username.(string)]
189-
190-
if !ok {
191-
log.Printf("[WARN] no information in cached for (%s)", username)
192-
return diag.FromErr(fmt.Errorf("error getting Atlas User (%s) information: %s", username, err))
193-
}
194-
updatedUserData = &cached
195-
}
196-
}
197-
newUsers = append(newUsers, admin.AddUserToTeam{Id: updatedUserData.GetId()})
198-
}
199-
200-
err = cleanUsers()
201-
if err != nil {
202-
return diag.FromErr(err)
203-
}
204-
205-
_, _, err = connV2.TeamsApi.AddTeamUser(ctx, orgID, teamID, &newUsers).Execute()
157+
err = UpdateTeamUsers(connV2.TeamsApi, connV2.MongoDBCloudUsersApi, existingUsers, newUsernames, orgID, teamID)
206158
if err != nil {
207-
return diag.FromErr(fmt.Errorf(errorTeamAddUsers, err))
159+
return diag.FromErr(fmt.Errorf("error when updating usernames in team: %s", err))
208160
}
209161
}
210162

internal/service/team/resource_team_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,58 @@ func TestAccConfigRSTeam_basic(t *testing.T) {
6565
})
6666
}
6767

68+
func TestAccConfigRSTeam_updatingUsernames(t *testing.T) {
69+
var (
70+
resourceName = "mongodbatlas_team.test"
71+
orgID = os.Getenv("MONGODB_ATLAS_ORG_ID")
72+
firstUser = os.Getenv("MONGODB_ATLAS_USERNAME")
73+
secondUser = os.Getenv("MONGODB_ATLAS_USERNAME_2")
74+
usernames = []string{firstUser}
75+
updatedSingleUsername = []string{secondUser}
76+
updatedBothUsername = []string{firstUser, secondUser}
77+
name = acc.RandomName()
78+
)
79+
80+
resource.ParallelTest(t, resource.TestCase{
81+
PreCheck: func() { acc.PreCheckAtlasUsernames(t) },
82+
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
83+
CheckDestroy: acc.CheckDestroyTeam,
84+
Steps: []resource.TestStep{
85+
{
86+
Config: configBasic(orgID, name, usernames),
87+
Check: resource.ComposeAggregateTestCheckFunc(
88+
checkExists(resourceName),
89+
resource.TestCheckResourceAttrSet(resourceName, "org_id"),
90+
resource.TestCheckResourceAttr(resourceName, "name", name),
91+
resource.TestCheckResourceAttr(resourceName, "usernames.#", "1"),
92+
resource.TestCheckTypeSetElemAttr(resourceName, "usernames.*", usernames[0]),
93+
),
94+
},
95+
{
96+
Config: configBasic(orgID, name, updatedSingleUsername),
97+
Check: resource.ComposeAggregateTestCheckFunc(
98+
checkExists(resourceName),
99+
resource.TestCheckResourceAttrSet(resourceName, "org_id"),
100+
resource.TestCheckResourceAttr(resourceName, "name", name),
101+
resource.TestCheckResourceAttr(resourceName, "usernames.#", "1"),
102+
resource.TestCheckTypeSetElemAttr(resourceName, "usernames.*", updatedSingleUsername[0]),
103+
),
104+
},
105+
{
106+
Config: configBasic(orgID, name, updatedBothUsername),
107+
Check: resource.ComposeAggregateTestCheckFunc(
108+
checkExists(resourceName),
109+
resource.TestCheckResourceAttrSet(resourceName, "org_id"),
110+
resource.TestCheckResourceAttr(resourceName, "name", name),
111+
resource.TestCheckResourceAttr(resourceName, "usernames.#", "2"),
112+
resource.TestCheckTypeSetElemAttr(resourceName, "usernames.*", updatedBothUsername[0]),
113+
resource.TestCheckTypeSetElemAttr(resourceName, "usernames.*", updatedBothUsername[1]),
114+
),
115+
},
116+
},
117+
})
118+
}
119+
68120
func TestAccConfigRSTeam_legacyName(t *testing.T) {
69121
var (
70122
resourceName = "mongodbatlas_teams.test"
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package team
2+
3+
import (
4+
"context"
5+
6+
"go.mongodb.org/atlas-sdk/v20240805004/admin"
7+
)
8+
9+
func UpdateTeamUsers(teamsAPI admin.TeamsApi, usersAPI admin.MongoDBCloudUsersApi, existingTeamUsers *admin.PaginatedApiAppUser, newUsernames []string, orgID, teamID string) error {
10+
validNewUsers, err := ValidateUsernames(usersAPI, newUsernames)
11+
if err != nil {
12+
return err
13+
}
14+
usersToAdd, usersToRemove, err := GetChangesForTeamUsers(existingTeamUsers.GetResults(), validNewUsers)
15+
if err != nil {
16+
return err
17+
}
18+
19+
// Avoid leaving the team empty with no users by first making new additions, ensuring no API validation errors
20+
21+
var userToAddModels []admin.AddUserToTeam
22+
for i := range usersToAdd {
23+
userToAddModels = append(userToAddModels, admin.AddUserToTeam{Id: usersToAdd[i]})
24+
}
25+
// save all users to add
26+
if len(userToAddModels) > 0 {
27+
_, _, err = teamsAPI.AddTeamUser(context.Background(), orgID, teamID, &userToAddModels).Execute()
28+
if err != nil {
29+
return err
30+
}
31+
}
32+
33+
for i := range usersToRemove {
34+
// remove user from team
35+
_, err := teamsAPI.RemoveTeamUser(context.Background(), orgID, teamID, usersToRemove[i]).Execute()
36+
if err != nil {
37+
return err
38+
}
39+
}
40+
41+
return nil
42+
}
43+
44+
func ValidateUsernames(c admin.MongoDBCloudUsersApi, usernames []string) ([]admin.CloudAppUser, error) {
45+
var validUsers []admin.CloudAppUser
46+
for _, elem := range usernames {
47+
userToAdd, _, err := c.GetUserByUsername(context.Background(), elem).Execute()
48+
if err != nil {
49+
return nil, err
50+
}
51+
validUsers = append(validUsers, *userToAdd)
52+
}
53+
return validUsers, nil
54+
}
55+
56+
func GetChangesForTeamUsers(currentUsers, newUsers []admin.CloudAppUser) (toAdd, toDelete []string, err error) {
57+
// Create two sets to store the elements in A and B
58+
currentUsersSet := InitUserSet(currentUsers)
59+
newUsersSet := InitUserSet(newUsers)
60+
61+
// Iterate over the elements in B and add them to the toAdd array if they are not in A
62+
for elem := range newUsersSet {
63+
if _, ok := currentUsersSet[elem]; !ok {
64+
toAdd = append(toAdd, elem)
65+
}
66+
}
67+
68+
// Iterate over the elements in A and add them to the toDelete array if they are not in B
69+
for elem := range currentUsersSet {
70+
if _, ok := newUsersSet[elem]; !ok {
71+
toDelete = append(toDelete, elem)
72+
}
73+
}
74+
75+
return toAdd, toDelete, nil
76+
}
77+
78+
func InitUserSet(users []admin.CloudAppUser) map[string]interface{} {
79+
usersSet := make(map[string]interface{}, len(users))
80+
for i := 0; i < len(users); i++ {
81+
usersSet[users[i].GetId()] = true
82+
}
83+
return usersSet
84+
}

0 commit comments

Comments
 (0)