Skip to content
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

fix: adjust root project_id attribute in mongdbatlas_project_api_key resource to optional #1664

Merged
merged 2 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ resource "mongodbatlas_project" "atlas-project" {

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

project_assignment {
project_id = mongodbatlas_project.atlas-project.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ terraform {
required_providers {
mongodbatlas = {
source = "mongodb/mongodbatlas"
version = "~> 1.12.0"
version = "~> 1.13.2"
}
}
required_version = ">= 1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# Create Programmatic API Key + Assign Programmatic API Key to Project
resource "mongodbatlas_project_api_key" "test" {
description = "test create and assign"
project_id = var.project_id
project_assignment {
project_id = var.project_id
role_names = ["GROUP_OWNER"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ terraform {
required_providers {
mongodbatlas = {
source = "mongodb/mongodbatlas"
version = "~> 1.0"
version = "~> 1.13.2"
}
}
required_version = ">= 1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# Create Programmatic API Key + Assign Programmatic API Key to Project
resource "mongodbatlas_project_api_key" "test" {
description = "test create and assign"
project_id = var.project_id

project_assignment {
project_id = var.project_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ terraform {
required_providers {
mongodbatlas = {
source = "mongodb/mongodbatlas"
version = "~> 1.10.0"
version = "~> 1.13.2"
}
}
required_version = ">= 1.0"
Expand Down
137 changes: 76 additions & 61 deletions mongodbatlas/resource_project_api_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func ResourceProjectAPIKey() *schema.Resource {
Schema: map[string]*schema.Schema{
"project_id": {
Type: schema.TypeString,
Required: true,
Optional: true,
},
"api_key_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -79,42 +79,41 @@ type APIProjectAssignmentKeyInput struct {
RoleNames []string `json:"roles,omitempty"`
}

const errorNoProjectAssignmentDefined = "could not obtain a project id as no assignments are defined"

func resourceMongoDBAtlasProjectAPIKeyCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
conn := meta.(*config.MongoDBClient).Atlas
projectID := d.Get("project_id").(string)
createRequest := new(matlas.APIKeyInput)

var apiKey *matlas.APIKey
var err error
var resp *matlas.Response

createRequest := new(matlas.APIKeyInput)
createRequest.Desc = d.Get("description").(string)
if projectAssignments, ok := d.GetOk("project_assignment"); ok {
projectAssignmentList := ExpandProjectAssignmentSet(projectAssignments.(*schema.Set))
for _, apiKeyList := range projectAssignmentList {
if apiKeyList.ProjectID == projectID {
createRequest.Roles = apiKeyList.RoleNames
apiKey, resp, err = conn.ProjectAPIKeys.Create(ctx, projectID, createRequest)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
}

// creates api key using project id of first defined project assignment
firstAssignment := projectAssignmentList[0]
createRequest.Roles = firstAssignment.RoleNames
apiKey, resp, err = conn.ProjectAPIKeys.Create(ctx, firstAssignment.ProjectID, createRequest)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
}

for _, apiKeyList := range projectAssignmentList {
if apiKeyList.ProjectID != projectID {
createRequest.Roles = apiKeyList.RoleNames
_, err := conn.ProjectAPIKeys.Assign(ctx, apiKeyList.ProjectID, apiKey.ID, &matlas.AssignAPIKey{
Roles: createRequest.Roles,
})
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
// assign created api key to remaining project assignments
for _, apiKeyList := range projectAssignmentList[1:] {
createRequest.Roles = apiKeyList.RoleNames
_, err := conn.ProjectAPIKeys.Assign(ctx, apiKeyList.ProjectID, apiKey.ID, &matlas.AssignAPIKey{
Roles: createRequest.Roles,
})
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
}
}
Expand All @@ -128,8 +127,12 @@ func resourceMongoDBAtlasProjectAPIKeyCreate(ctx context.Context, d *schema.Reso
return diag.FromErr(fmt.Errorf("error setting `private_key`: %s", err))
}

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

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

projectAPIKeys, _, err := conn.ProjectAPIKeys.List(ctx, projectID, nil)
firstProjectID, err := getFirstProjectIDFromAssignments(d)
if err != nil {
return diag.FromErr(fmt.Errorf("could not obtain a project id from state: %s", err))
}

projectAPIKeys, _, err := conn.ProjectAPIKeys.List(ctx, *firstProjectID, nil)
if err != nil {
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
}
Expand All @@ -168,7 +175,7 @@ func resourceMongoDBAtlasProjectAPIKeyRead(ctx context.Context, d *schema.Resour

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

if err := d.Set("project_id", projectID); err != nil {
return diag.FromErr(fmt.Errorf("error setting `project_id`: %s", err))
}

d.SetId(conversion.EncodeStateID(map[string]string{
"project_id": projectID,
"api_key_id": apiKeyID,
}))

return nil
}

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

ids := conversion.DecodeStateID(d.Id())
projectID := ids["project_id"]
apiKeyID := ids["api_key_id"]

if d.HasChange("project_assignment") {
Expand Down Expand Up @@ -241,9 +238,14 @@ func resourceMongoDBAtlasProjectAPIKeyUpdate(ctx context.Context, d *schema.Reso
}
}

firstProjectID, err := getFirstProjectIDFromAssignments(d)
if err != nil {
return diag.FromErr(fmt.Errorf("could not obtain a project id from state: %s", err))
}

if d.HasChange("description") {
newDescription := d.Get("description").(string)
if _, _, err := connV2.ProgrammaticAPIKeysApi.UpdateApiKeyRoles(ctx, projectID, apiKeyID, &admin.UpdateAtlasProjectApiKey{
if _, _, err := connV2.ProgrammaticAPIKeysApi.UpdateApiKeyRoles(ctx, *firstProjectID, apiKeyID, &admin.UpdateAtlasProjectApiKey{
Desc: &newDescription,
}).Execute(); err != nil {
return diag.Errorf("error updating description in api key(%s): %s", apiKeyID, err)
Expand All @@ -256,11 +258,15 @@ func resourceMongoDBAtlasProjectAPIKeyUpdate(ctx context.Context, d *schema.Reso
func resourceMongoDBAtlasProjectAPIKeyDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
conn := meta.(*config.MongoDBClient).Atlas
ids := conversion.DecodeStateID(d.Id())
projectID := ids["project_id"]
apiKeyID := ids["api_key_id"]
var orgID string

projectAPIKeys, _, err := conn.ProjectAPIKeys.List(ctx, projectID, nil)
firstProjectID, err := getFirstProjectIDFromAssignments(d)
if err != nil {
return diag.FromErr(fmt.Errorf("could not obtain a project id from state: %s", err))
}

projectAPIKeys, _, err := conn.ProjectAPIKeys.List(ctx, *firstProjectID, nil)
if err != nil {
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
}
Expand All @@ -275,28 +281,20 @@ func resourceMongoDBAtlasProjectAPIKeyDelete(ctx context.Context, d *schema.Reso
}
}

_, roleOk := d.GetOk("role_names")
if !roleOk {
options := &matlas.ListOptions{}
options := &matlas.ListOptions{}

apiKeyOrgList, _, err := conn.Root.List(ctx, options)
if err != nil {
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
}
apiKeyOrgList, _, err := conn.Root.List(ctx, options)
if err != nil {
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
}

projectAssignments, err := getAPIProjectAssignments(ctx, conn, apiKeyOrgList, apiKeyID)
if err != nil {
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
}
projectAssignments, err := getAPIProjectAssignments(ctx, conn, apiKeyOrgList, apiKeyID)
if err != nil {
return diag.FromErr(fmt.Errorf("error getting api key information: %s", err))
}

for _, apiKey := range projectAssignments {
_, err = conn.ProjectAPIKeys.Unassign(ctx, apiKey.ProjectID, apiKeyID)
if err != nil {
return diag.FromErr(fmt.Errorf("error deleting project api key: %s", err))
}
}
} else {
_, err = conn.ProjectAPIKeys.Unassign(ctx, projectID, apiKeyID)
for _, apiKey := range projectAssignments {
_, err = conn.ProjectAPIKeys.Unassign(ctx, apiKey.ProjectID, apiKeyID)
if err != nil {
return diag.FromErr(fmt.Errorf("error deleting project api key: %s", err))
}
Expand All @@ -316,7 +314,7 @@ func resourceMongoDBAtlasProjectAPIKeyImportState(ctx context.Context, d *schema

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

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

if projectAssignments, err := newProjectAssignment(ctx, conn, apiKeyID); err == nil {
if err := d.Set("project_assignment", projectAssignments); err != nil {
return nil, fmt.Errorf("error setting `project_assignment`: %s", err)
}
}

d.SetId(conversion.EncodeStateID(map[string]string{
"project_id": projectID,
"api_key_id": val.ID,
Expand All @@ -345,6 +349,17 @@ func resourceMongoDBAtlasProjectAPIKeyImportState(ctx context.Context, d *schema
return []*schema.ResourceData{d}, nil
}

func getFirstProjectIDFromAssignments(d *schema.ResourceData) (*string, error) {
if projectAssignments, ok := d.GetOk("project_assignment"); ok {
projectAssignmentList := ExpandProjectAssignmentSet(projectAssignments.(*schema.Set))
if len(projectAssignmentList) < 1 {
return nil, errors.New(errorNoProjectAssignmentDefined)
}
return admin.PtrString(projectAssignmentList[0].ProjectID), nil // can safely assume at least one assigment is defined because of schema definition
}
return nil, errors.New(errorNoProjectAssignmentDefined)
}

func flattenProjectAPIKeyRoles(projectID string, apiKeyRoles []matlas.AtlasRole) []string {
if len(apiKeyRoles) == 0 {
return nil
Expand Down
62 changes: 62 additions & 0 deletions mongodbatlas/resource_project_api_key_migration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package mongodbatlas_test

import (
"fmt"
"os"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
)

func TestAccMigrationConfigRSProjectAPIKey_RemovingOptionalRootProjectID(t *testing.T) {
var (
resourceName = "mongodbatlas_project_api_key.test"
orgID = os.Getenv("MONGODB_ATLAS_ORG_ID")
projectName = acctest.RandomWithPrefix("test-acc")
description = fmt.Sprintf("test-acc-project-api_key-%s", acctest.RandString(5))
roleName = "GROUP_OWNER"
)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(t) },
CheckDestroy: testAccCheckMongoDBAtlasProjectAPIKeyDestroy,
Steps: []resource.TestStep{
{
ExternalProviders: map[string]resource.ExternalProvider{
"mongodbatlas": {
VersionConstraint: "1.13.1", // fixed version as this is the last version where root project id was required.
Source: "mongodb/mongodbatlas",
},
},
Config: testAccMongoDBAtlasProjectAPIKeyConfigBasic(orgID, projectName, description, roleName, true),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(resourceName, "project_id"),
resource.TestCheckResourceAttr(resourceName, "description", description),
resource.TestCheckResourceAttrSet(resourceName, "public_key"),
resource.TestCheckResourceAttr(resourceName, "project_assignment.#", "1"),
),
},
{
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
Config: testAccMongoDBAtlasProjectAPIKeyConfigBasic(orgID, projectName, description, roleName, true),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(resourceName, "project_id"),
resource.TestCheckResourceAttr(resourceName, "description", description),
resource.TestCheckResourceAttrSet(resourceName, "public_key"),
resource.TestCheckResourceAttr(resourceName, "project_assignment.#", "1"),
),
},
{
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
Config: testAccMongoDBAtlasProjectAPIKeyConfigBasic(orgID, projectName, description, roleName, false),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "description", description),
resource.TestCheckResourceAttrSet(resourceName, "public_key"),
resource.TestCheckResourceAttr(resourceName, "project_assignment.#", "1"),
),
},
},
})
}
Loading