Skip to content

Commit 8dd67d8

Browse files
fix: adjust root project_id attribute in mongdbatlas_project_api_key resource to optional
1 parent 53462a3 commit 8dd67d8

File tree

12 files changed

+297
-108
lines changed

12 files changed

+297
-108
lines changed

examples/atlas-api-key/create-and-assign-pak-seperately/main.tf

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ resource "mongodbatlas_project" "atlas-project" {
55

66
resource "mongodbatlas_project_api_key" "api_1" {
77
description = "test api_key multi"
8-
project_id = mongodbatlas_project.atlas-project.id
98

109
project_assignment {
1110
project_id = mongodbatlas_project.atlas-project.id

examples/atlas-api-key/create-and-assign-pak-seperately/versions.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ terraform {
22
required_providers {
33
mongodbatlas = {
44
source = "mongodb/mongodbatlas"
5-
version = "~> 1.12.0"
5+
version = "~> 1.13.2"
66
}
77
}
88
required_version = ">= 1.0"

examples/atlas-api-key/create-and-assign-pak-together/main.tf

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# Create Programmatic API Key + Assign Programmatic API Key to Project
33
resource "mongodbatlas_project_api_key" "test" {
44
description = "test create and assign"
5-
project_id = var.project_id
65
project_assignment {
76
project_id = var.project_id
87
role_names = ["GROUP_OWNER"]

examples/atlas-api-key/create-and-assign-pak-together/versions.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ terraform {
22
required_providers {
33
mongodbatlas = {
44
source = "mongodb/mongodbatlas"
5-
version = "~> 1.0"
5+
version = "~> 1.13.2"
66
}
77
}
88
required_version = ">= 1.0"

examples/atlas-api-key/create-api-key-assign-to-multiple-projects/main.tf

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# Create Programmatic API Key + Assign Programmatic API Key to Project
33
resource "mongodbatlas_project_api_key" "test" {
44
description = "test create and assign"
5-
project_id = var.project_id
65

76
project_assignment {
87
project_id = var.project_id

examples/atlas-api-key/create-api-key-assign-to-multiple-projects/versions.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ terraform {
22
required_providers {
33
mongodbatlas = {
44
source = "mongodb/mongodbatlas"
5-
version = "~> 1.10.0"
5+
version = "~> 1.13.2"
66
}
77
}
88
required_version = ">= 1.0"

mongodbatlas/resource_project_api_key.go

+76-61
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func ResourceProjectAPIKey() *schema.Resource {
3131
Schema: map[string]*schema.Schema{
3232
"project_id": {
3333
Type: schema.TypeString,
34-
Required: true,
34+
Optional: true,
3535
},
3636
"api_key_id": {
3737
Type: schema.TypeString,
@@ -79,42 +79,41 @@ type APIProjectAssignmentKeyInput struct {
7979
RoleNames []string `json:"roles,omitempty"`
8080
}
8181

82+
const errorNoProjectAssignmentDefined = "could not obtain a project id as no assignments are defined"
83+
8284
func resourceMongoDBAtlasProjectAPIKeyCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
8385
conn := meta.(*config.MongoDBClient).Atlas
84-
projectID := d.Get("project_id").(string)
85-
createRequest := new(matlas.APIKeyInput)
8686

8787
var apiKey *matlas.APIKey
8888
var err error
8989
var resp *matlas.Response
9090

91+
createRequest := new(matlas.APIKeyInput)
9192
createRequest.Desc = d.Get("description").(string)
9293
if projectAssignments, ok := d.GetOk("project_assignment"); ok {
9394
projectAssignmentList := ExpandProjectAssignmentSet(projectAssignments.(*schema.Set))
94-
for _, apiKeyList := range projectAssignmentList {
95-
if apiKeyList.ProjectID == projectID {
96-
createRequest.Roles = apiKeyList.RoleNames
97-
apiKey, resp, err = conn.ProjectAPIKeys.Create(ctx, projectID, createRequest)
98-
if err != nil {
99-
if resp != nil && resp.StatusCode == http.StatusNotFound {
100-
d.SetId("")
101-
return nil
102-
}
103-
}
95+
96+
// creates api key using project id of first defined project assignment
97+
firstAssignment := projectAssignmentList[0]
98+
createRequest.Roles = firstAssignment.RoleNames
99+
apiKey, resp, err = conn.ProjectAPIKeys.Create(ctx, firstAssignment.ProjectID, createRequest)
100+
if err != nil {
101+
if resp != nil && resp.StatusCode == http.StatusNotFound {
102+
d.SetId("")
103+
return nil
104104
}
105105
}
106106

107-
for _, apiKeyList := range projectAssignmentList {
108-
if apiKeyList.ProjectID != projectID {
109-
createRequest.Roles = apiKeyList.RoleNames
110-
_, err := conn.ProjectAPIKeys.Assign(ctx, apiKeyList.ProjectID, apiKey.ID, &matlas.AssignAPIKey{
111-
Roles: createRequest.Roles,
112-
})
113-
if err != nil {
114-
if resp != nil && resp.StatusCode == http.StatusNotFound {
115-
d.SetId("")
116-
return nil
117-
}
107+
// assign created api key to remaining project assignments
108+
for _, apiKeyList := range projectAssignmentList[1:] {
109+
createRequest.Roles = apiKeyList.RoleNames
110+
_, err := conn.ProjectAPIKeys.Assign(ctx, apiKeyList.ProjectID, apiKey.ID, &matlas.AssignAPIKey{
111+
Roles: createRequest.Roles,
112+
})
113+
if err != nil {
114+
if resp != nil && resp.StatusCode == http.StatusNotFound {
115+
d.SetId("")
116+
return nil
118117
}
119118
}
120119
}
@@ -128,8 +127,12 @@ func resourceMongoDBAtlasProjectAPIKeyCreate(ctx context.Context, d *schema.Reso
128127
return diag.FromErr(fmt.Errorf("error setting `private_key`: %s", err))
129128
}
130129

130+
firstProjectID, err := getFirstProjectIDFromAssignments(d)
131+
if err != nil {
132+
return diag.FromErr(fmt.Errorf("could not obtain a project id from state: %s", err))
133+
}
131134
d.SetId(conversion.EncodeStateID(map[string]string{
132-
"project_id": projectID,
135+
"project_id": *firstProjectID, // defined to avoid breaking changes (preserve id format), can be removed when root project_id is removed.
133136
"api_key_id": apiKey.ID,
134137
}))
135138

@@ -140,10 +143,14 @@ func resourceMongoDBAtlasProjectAPIKeyRead(ctx context.Context, d *schema.Resour
140143
// Get client connection.
141144
conn := meta.(*config.MongoDBClient).Atlas
142145
ids := conversion.DecodeStateID(d.Id())
143-
projectID := ids["project_id"]
144146
apiKeyID := ids["api_key_id"]
145147

146-
projectAPIKeys, _, err := conn.ProjectAPIKeys.List(ctx, projectID, nil)
148+
firstProjectID, err := getFirstProjectIDFromAssignments(d)
149+
if err != nil {
150+
return diag.FromErr(fmt.Errorf("could not obtain a project id from state: %s", err))
151+
}
152+
153+
projectAPIKeys, _, err := conn.ProjectAPIKeys.List(ctx, *firstProjectID, nil)
147154
if err != nil {
148155
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
149156
}
@@ -168,7 +175,7 @@ func resourceMongoDBAtlasProjectAPIKeyRead(ctx context.Context, d *schema.Resour
168175

169176
if projectAssignments, err := newProjectAssignment(ctx, conn, apiKeyID); err == nil {
170177
if err := d.Set("project_assignment", projectAssignments); err != nil {
171-
return diag.Errorf(ErrorProjectSetting, `created`, projectID, err)
178+
return diag.Errorf("error setting `project_assignment` : %s", err)
172179
}
173180
}
174181
}
@@ -178,15 +185,6 @@ func resourceMongoDBAtlasProjectAPIKeyRead(ctx context.Context, d *schema.Resour
178185
return nil
179186
}
180187

181-
if err := d.Set("project_id", projectID); err != nil {
182-
return diag.FromErr(fmt.Errorf("error setting `project_id`: %s", err))
183-
}
184-
185-
d.SetId(conversion.EncodeStateID(map[string]string{
186-
"project_id": projectID,
187-
"api_key_id": apiKeyID,
188-
}))
189-
190188
return nil
191189
}
192190

@@ -195,7 +193,6 @@ func resourceMongoDBAtlasProjectAPIKeyUpdate(ctx context.Context, d *schema.Reso
195193
connV2 := meta.(*config.MongoDBClient).AtlasV2
196194

197195
ids := conversion.DecodeStateID(d.Id())
198-
projectID := ids["project_id"]
199196
apiKeyID := ids["api_key_id"]
200197

201198
if d.HasChange("project_assignment") {
@@ -241,9 +238,14 @@ func resourceMongoDBAtlasProjectAPIKeyUpdate(ctx context.Context, d *schema.Reso
241238
}
242239
}
243240

241+
firstProjectID, err := getFirstProjectIDFromAssignments(d)
242+
if err != nil {
243+
return diag.FromErr(fmt.Errorf("could not obtain a project id from state: %s", err))
244+
}
245+
244246
if d.HasChange("description") {
245247
newDescription := d.Get("description").(string)
246-
if _, _, err := connV2.ProgrammaticAPIKeysApi.UpdateApiKeyRoles(ctx, projectID, apiKeyID, &admin.UpdateAtlasProjectApiKey{
248+
if _, _, err := connV2.ProgrammaticAPIKeysApi.UpdateApiKeyRoles(ctx, *firstProjectID, apiKeyID, &admin.UpdateAtlasProjectApiKey{
247249
Desc: &newDescription,
248250
}).Execute(); err != nil {
249251
return diag.Errorf("error updating description in api key(%s): %s", apiKeyID, err)
@@ -256,11 +258,15 @@ func resourceMongoDBAtlasProjectAPIKeyUpdate(ctx context.Context, d *schema.Reso
256258
func resourceMongoDBAtlasProjectAPIKeyDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
257259
conn := meta.(*config.MongoDBClient).Atlas
258260
ids := conversion.DecodeStateID(d.Id())
259-
projectID := ids["project_id"]
260261
apiKeyID := ids["api_key_id"]
261262
var orgID string
262263

263-
projectAPIKeys, _, err := conn.ProjectAPIKeys.List(ctx, projectID, nil)
264+
firstProjectID, err := getFirstProjectIDFromAssignments(d)
265+
if err != nil {
266+
return diag.FromErr(fmt.Errorf("could not obtain a project id from state: %s", err))
267+
}
268+
269+
projectAPIKeys, _, err := conn.ProjectAPIKeys.List(ctx, *firstProjectID, nil)
264270
if err != nil {
265271
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
266272
}
@@ -275,28 +281,20 @@ func resourceMongoDBAtlasProjectAPIKeyDelete(ctx context.Context, d *schema.Reso
275281
}
276282
}
277283

278-
_, roleOk := d.GetOk("role_names")
279-
if !roleOk {
280-
options := &matlas.ListOptions{}
284+
options := &matlas.ListOptions{}
281285

282-
apiKeyOrgList, _, err := conn.Root.List(ctx, options)
283-
if err != nil {
284-
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
285-
}
286+
apiKeyOrgList, _, err := conn.Root.List(ctx, options)
287+
if err != nil {
288+
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
289+
}
286290

287-
projectAssignments, err := getAPIProjectAssignments(ctx, conn, apiKeyOrgList, apiKeyID)
288-
if err != nil {
289-
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
290-
}
291+
projectAssignments, err := getAPIProjectAssignments(ctx, conn, apiKeyOrgList, apiKeyID)
292+
if err != nil {
293+
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
294+
}
291295

292-
for _, apiKey := range projectAssignments {
293-
_, err = conn.ProjectAPIKeys.Unassign(ctx, apiKey.ProjectID, apiKeyID)
294-
if err != nil {
295-
return diag.FromErr(fmt.Errorf("error deleting project api key: %s", err))
296-
}
297-
}
298-
} else {
299-
_, err = conn.ProjectAPIKeys.Unassign(ctx, projectID, apiKeyID)
296+
for _, apiKey := range projectAssignments {
297+
_, err = conn.ProjectAPIKeys.Unassign(ctx, apiKey.ProjectID, apiKeyID)
300298
if err != nil {
301299
return diag.FromErr(fmt.Errorf("error deleting project api key: %s", err))
302300
}
@@ -316,7 +314,7 @@ func resourceMongoDBAtlasProjectAPIKeyImportState(ctx context.Context, d *schema
316314

317315
parts := strings.SplitN(d.Id(), "-", 2)
318316
if len(parts) != 2 {
319-
return nil, errors.New("import format error: to import a api key use the format {org_id}-{api_key_id}")
317+
return nil, errors.New("import format error: to import a api key use the format {project_id}-{api_key_id}")
320318
}
321319

322320
projectID := parts[0]
@@ -336,6 +334,12 @@ func resourceMongoDBAtlasProjectAPIKeyImportState(ctx context.Context, d *schema
336334
return nil, fmt.Errorf("error setting `public_key`: %s", err)
337335
}
338336

337+
if projectAssignments, err := newProjectAssignment(ctx, conn, apiKeyID); err == nil {
338+
if err := d.Set("project_assignment", projectAssignments); err != nil {
339+
return nil, fmt.Errorf("error setting `project_assignment`: %s", err)
340+
}
341+
}
342+
339343
d.SetId(conversion.EncodeStateID(map[string]string{
340344
"project_id": projectID,
341345
"api_key_id": val.ID,
@@ -345,6 +349,17 @@ func resourceMongoDBAtlasProjectAPIKeyImportState(ctx context.Context, d *schema
345349
return []*schema.ResourceData{d}, nil
346350
}
347351

352+
func getFirstProjectIDFromAssignments(d *schema.ResourceData) (*string, error) {
353+
if projectAssignments, ok := d.GetOk("project_assignment"); ok {
354+
projectAssignmentList := ExpandProjectAssignmentSet(projectAssignments.(*schema.Set))
355+
if len(projectAssignmentList) < 1 {
356+
return nil, errors.New(errorNoProjectAssignmentDefined)
357+
}
358+
return admin.PtrString(projectAssignmentList[0].ProjectID), nil // can safely assume at least one assigment is defined because of schema definition
359+
}
360+
return nil, errors.New(errorNoProjectAssignmentDefined)
361+
}
362+
348363
func flattenProjectAPIKeyRoles(projectID string, apiKeyRoles []matlas.AtlasRole) []string {
349364
if len(apiKeyRoles) == 0 {
350365
return nil
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package mongodbatlas_test
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
9+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
10+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
11+
)
12+
13+
func TestAccMigrationConfigRSProjectAPIKey_RemovingOptionalRootProjectID(t *testing.T) {
14+
var (
15+
resourceName = "mongodbatlas_project_api_key.test"
16+
orgID = os.Getenv("MONGODB_ATLAS_ORG_ID")
17+
projectName = acctest.RandomWithPrefix("test-acc")
18+
description = fmt.Sprintf("test-acc-project-api_key-%s", acctest.RandString(5))
19+
roleName = "GROUP_OWNER"
20+
)
21+
22+
resource.ParallelTest(t, resource.TestCase{
23+
PreCheck: func() { acc.PreCheckBasic(t) },
24+
CheckDestroy: testAccCheckMongoDBAtlasProjectAPIKeyDestroy,
25+
Steps: []resource.TestStep{
26+
{
27+
ExternalProviders: map[string]resource.ExternalProvider{
28+
"mongodbatlas": {
29+
VersionConstraint: "1.13.1", // fixed version as this is the last version where root project id was required.
30+
Source: "mongodb/mongodbatlas",
31+
},
32+
},
33+
Config: testAccMongoDBAtlasProjectAPIKeyConfigBasic(orgID, projectName, description, roleName, true),
34+
Check: resource.ComposeTestCheckFunc(
35+
resource.TestCheckResourceAttrSet(resourceName, "project_id"),
36+
resource.TestCheckResourceAttr(resourceName, "description", description),
37+
resource.TestCheckResourceAttrSet(resourceName, "public_key"),
38+
resource.TestCheckResourceAttr(resourceName, "project_assignment.#", "1"),
39+
),
40+
},
41+
{
42+
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
43+
Config: testAccMongoDBAtlasProjectAPIKeyConfigBasic(orgID, projectName, description, roleName, true),
44+
Check: resource.ComposeTestCheckFunc(
45+
resource.TestCheckResourceAttrSet(resourceName, "project_id"),
46+
resource.TestCheckResourceAttr(resourceName, "description", description),
47+
resource.TestCheckResourceAttrSet(resourceName, "public_key"),
48+
resource.TestCheckResourceAttr(resourceName, "project_assignment.#", "1"),
49+
),
50+
},
51+
{
52+
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
53+
Config: testAccMongoDBAtlasProjectAPIKeyConfigBasic(orgID, projectName, description, roleName, false),
54+
Check: resource.ComposeTestCheckFunc(
55+
resource.TestCheckResourceAttr(resourceName, "description", description),
56+
resource.TestCheckResourceAttrSet(resourceName, "public_key"),
57+
resource.TestCheckResourceAttr(resourceName, "project_assignment.#", "1"),
58+
),
59+
},
60+
},
61+
})
62+
}

0 commit comments

Comments
 (0)