Skip to content

Commit 641090b

Browse files
authored
fix: Fixes change assigments in mongodbatlas_project_api_key (#2737)
* fix changed assigments * fix TestAccProjectAPIKey_updateRole * changelog * join configMultiple to configBasic * don't allow duplicate projectIDs * fix TestAccProjectAPIKey_dataSources * refactor Create * refactor getAPIProjectAssignments and flattenProjectAssignments * refactor Read * refactor Delete * fix TestAccProjectAPIKey_recreateWhenDeletedExternally * refactor Import * remove flattenProjectAssignments, flattenProjectAPIKeyRoles and getAPIProjectAssignments * fix plural ds * fix basicTestCase * refactor Update description * refactor Update * refactor expandProjectAssignments * update changelog * initial checkAggr * checkExists * delete redundant TestAccProjectAPIKey_dataSources * refactor configDuplicatedProject * refactor configChangingProject * model file * more detailed changelog * revert import * doc * refactor checks
1 parent e08f9de commit 641090b

File tree

7 files changed

+393
-438
lines changed

7 files changed

+393
-438
lines changed

.changelog/2737.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_project_api_key: Validates `project_id` are unique across `project_assignment` blocks and fixes update issues with error `API_KEY_ALREADY_IN_GROUP`
3+
```

internal/service/projectapikey/data_source_project_api_key.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@ func dataSourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.
8484
return diag.FromErr(fmt.Errorf("error setting `private_key`: %s", err))
8585
}
8686

87-
if projectAssignments, err := newProjectAssignment(ctx, connV2, apiKeyID); err == nil {
88-
if err := d.Set("project_assignment", projectAssignments); err != nil {
89-
return diag.Errorf(ErrorProjectSetting, `project_assignment`, projectID, err)
90-
}
87+
details, _, err := getKeyDetails(ctx, connV2, apiKeyID)
88+
if err != nil {
89+
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
90+
}
91+
if err := d.Set("project_assignment", flattenProjectAssignments(details.GetRoles())); err != nil {
92+
return diag.FromErr(fmt.Errorf("error setting `project_assignment`: %s", err))
9193
}
9294
}
9395

internal/service/projectapikey/data_source_project_api_keys.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
88
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/id"
99
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
1011
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
1112
"go.mongodb.org/atlas-sdk/v20240805005/admin"
1213
)
@@ -76,12 +77,12 @@ func PluralDataSource() *schema.Resource {
7677

7778
func pluralDataSourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
7879
connV2 := meta.(*config.MongoDBClient).AtlasV2
79-
pageNum := d.Get("page_num").(int)
80-
itemsPerPage := d.Get("items_per_page").(int)
81-
82-
projectID := d.Get("project_id").(string)
83-
84-
apiKeys, _, err := connV2.ProgrammaticAPIKeysApi.ListProjectApiKeys(ctx, projectID).PageNum(pageNum).ItemsPerPage(itemsPerPage).Execute()
80+
params := &admin.ListProjectApiKeysApiParams{
81+
GroupId: d.Get("project_id").(string),
82+
PageNum: conversion.IntPtr(d.Get("page_num").(int)),
83+
ItemsPerPage: conversion.IntPtr(d.Get("items_per_page").(int)),
84+
}
85+
apiKeys, _, err := connV2.ProgrammaticAPIKeysApi.ListProjectApiKeysWithParams(ctx, params).Execute()
8586
if err != nil {
8687
return diag.FromErr(fmt.Errorf("error getting api keys information: %s", err))
8788
}
@@ -116,12 +117,11 @@ func flattenProjectAPIKeys(ctx context.Context, connV2 *admin.APIClient, apiKeys
116117
"private_key": apiKey.GetPrivateKey(),
117118
}
118119

119-
projectAssignment, err := newProjectAssignment(ctx, connV2, apiKey.GetId())
120+
details, _, err := getKeyDetails(ctx, connV2, apiKey.GetId())
120121
if err != nil {
121122
return nil, err
122123
}
123-
124-
results[k]["project_assignment"] = projectAssignment
124+
results[k]["project_assignment"] = flattenProjectAssignments(details.GetRoles())
125125
}
126126
return results, nil
127127
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package projectapikey
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"reflect"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
10+
"go.mongodb.org/atlas-sdk/v20240805005/admin"
11+
)
12+
13+
func expandProjectAssignments(projectAssignments *schema.Set) map[string][]string {
14+
results := make(map[string][]string)
15+
for _, val := range projectAssignments.List() {
16+
results[val.(map[string]any)["project_id"].(string)] = conversion.ExpandStringList(val.(map[string]any)["role_names"].(*schema.Set).List())
17+
}
18+
return results
19+
}
20+
21+
func flattenProjectAssignments(roles []admin.CloudAccessRoleAssignment) []map[string]any {
22+
assignments := make(map[string][]string)
23+
for _, role := range roles {
24+
if groupID := role.GetGroupId(); groupID != "" {
25+
assignments[groupID] = append(assignments[groupID], role.GetRoleName())
26+
}
27+
}
28+
var results []map[string]any
29+
for projectID, roles := range assignments {
30+
results = append(results, map[string]any{
31+
"project_id": projectID,
32+
"role_names": roles,
33+
})
34+
}
35+
return results
36+
}
37+
38+
func getAssignmentChanges(d *schema.ResourceData) (add, remove, update map[string][]string) {
39+
before, after := d.GetChange("project_assignment")
40+
add = expandProjectAssignments(after.(*schema.Set))
41+
remove = expandProjectAssignments(before.(*schema.Set))
42+
update = make(map[string][]string)
43+
44+
for projectID, rolesAfter := range add {
45+
if rolesBefore, ok := remove[projectID]; ok {
46+
if !sameRoles(rolesBefore, rolesAfter) {
47+
update[projectID] = rolesAfter
48+
}
49+
delete(remove, projectID)
50+
delete(add, projectID)
51+
}
52+
}
53+
return
54+
}
55+
56+
func sameRoles(roles1, roles2 []string) bool {
57+
set1 := make(map[string]struct{})
58+
for _, role := range roles1 {
59+
set1[role] = struct{}{}
60+
}
61+
set2 := make(map[string]struct{})
62+
for _, role := range roles2 {
63+
set2[role] = struct{}{}
64+
}
65+
return reflect.DeepEqual(set1, set2)
66+
}
67+
68+
// getKeyDetails returns nil error and nil details if not found as it's not considered an error
69+
func getKeyDetails(ctx context.Context, connV2 *admin.APIClient, apiKeyID string) (*admin.ApiKeyUserDetails, string, error) {
70+
root, _, err := connV2.RootApi.GetSystemStatus(ctx).Execute()
71+
if err != nil {
72+
return nil, "", err
73+
}
74+
for _, role := range root.ApiKey.GetRoles() {
75+
if orgID := role.GetOrgId(); orgID != "" {
76+
key, _, err := connV2.ProgrammaticAPIKeysApi.GetApiKey(ctx, orgID, apiKeyID).Execute()
77+
if err != nil {
78+
if admin.IsErrorCode(err, "API_KEY_NOT_FOUND") {
79+
return nil, orgID, nil
80+
}
81+
return nil, orgID, fmt.Errorf("error getting api key information: %s", err)
82+
}
83+
return key, orgID, nil
84+
}
85+
}
86+
return nil, "", nil
87+
}
88+
89+
func validateUniqueProjectIDs(d *schema.ResourceData) error {
90+
if projectAssignments, ok := d.GetOk("project_assignment"); ok {
91+
uniqueIDs := make(map[string]bool)
92+
for _, val := range projectAssignments.(*schema.Set).List() {
93+
projectID := val.(map[string]any)["project_id"].(string)
94+
if uniqueIDs[projectID] {
95+
return fmt.Errorf("duplicated projectID in assignments: %s", projectID)
96+
}
97+
uniqueIDs[projectID] = true
98+
}
99+
}
100+
return nil
101+
}

0 commit comments

Comments
 (0)