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

Add error handling for ALREADY_EXISTS in IAM CreateServiceAccount call #16927

Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions .changelog/9727.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:note
iam: introducde an optional resource argument to Google Cloud IAM Service Account. If `ignore_create_already_exists` is set to true, resource creation would succeed if response error is 409 ALREADY_EXISTS.
```
23 changes: 21 additions & 2 deletions google/services/resourcemanager/resource_google_service_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"google.golang.org/api/googleapi"
"google.golang.org/api/iam/v1"
)

Expand Down Expand Up @@ -84,6 +85,12 @@ func ResourceGoogleServiceAccount() *schema.Resource {
Computed: true,
Description: `The Identity of the service account in the form 'serviceAccount:{email}'. This value is often used to refer to the service account in order to grant IAM permissions.`,
},
"create_ignore_already_exists": {
Type: schema.TypeBool,
Optional: true,
Computed: false,
Description: `If set to true, skip service account creation if a service account with the same email already exists.`,
},
},
UseJSONNumber: true,
}
Expand Down Expand Up @@ -116,7 +123,15 @@ func resourceGoogleServiceAccountCreate(d *schema.ResourceData, meta interface{}

sa, err = config.NewIamClient(userAgent).Projects.ServiceAccounts.Create("projects/"+project, r).Do()
if err != nil {
return fmt.Errorf("Error creating service account: %s", err)
gerr, ok := err.(*googleapi.Error)
alreadyExists := ok && gerr.Code == 409 && d.Get("create_ignore_already_exists").(bool)
if alreadyExists {
sa = &iam.ServiceAccount{
Name: fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", project, aid, project),
}
} else {
return fmt.Errorf("Error creating service account: %s", err)
}
}

d.SetId(sa.Name)
Expand Down Expand Up @@ -218,7 +233,11 @@ func resourceGoogleServiceAccountDelete(d *schema.ResourceData, meta interface{}
name := d.Id()
_, err = config.NewIamClient(userAgent).Projects.ServiceAccounts.Delete(name).Do()
if err != nil {
return err
gerr, ok := err.(*googleapi.Error)
notFound := ok && gerr.Code == 404
if !notFound {
return fmt.Errorf("Error deleting service account: %s", err)
}
}
d.SetId("")
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,47 @@ func TestAccServiceAccount_basic(t *testing.T) {
})
}

// Test the option to ignore ALREADY_EXISTS error from creating a service account.
func TestAccServiceAccount_createIgnoreAlreadyExists(t *testing.T) {
t.Parallel()

accountId := "a" + acctest.RandString(t, 10)
displayName := "Terraform Test"
desc := "test description"
project := envvar.GetTestProjectFromEnv()
expectedEmail := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", accountId, project)
acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
// The first step creates a basic service account
{
Config: testAccServiceAccountBasic(accountId, displayName, desc),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"google_service_account.acceptance", "project", project),
resource.TestCheckResourceAttr(
"google_service_account.acceptance", "member", "serviceAccount:"+expectedEmail),
),
},
{
ResourceName: "google_service_account.acceptance",
ImportStateId: fmt.Sprintf("projects/%s/serviceAccounts/%s", project, expectedEmail),
ImportState: true,
ImportStateVerify: true,
},
// The second step creates a new resource that duplicates with the existing service account.
{
Config: testAccServiceAccountCreateIgnoreAlreadyExists(accountId, displayName, desc),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"google_service_account.duplicate", "member", "serviceAccount:"+expectedEmail),
),
},
},
})
}

func TestAccServiceAccount_Disabled(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -168,6 +209,22 @@ resource "google_service_account" "acceptance" {
`, account, name, desc)
}

func testAccServiceAccountCreateIgnoreAlreadyExists(account, name, desc string) string {
return fmt.Sprintf(`
resource "google_service_account" "acceptance" {
account_id = "%v"
display_name = "%v"
description = "%v"
}
resource "google_service_account" "duplicate" {
account_id = "%v"
display_name = "%v"
description = "%v"
create_ignore_already_exists = true
}
`, account, name, desc, account, name, desc)
}

func testAccServiceAccountWithProject(project, account, name string) string {
return fmt.Sprintf(`
resource "google_service_account" "acceptance" {
Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/google_service_account.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ The following arguments are supported:
* `project` - (Optional) The ID of the project that the service account will be created in.
Defaults to the provider project configuration.

* `create_ignore_already_exists` - (Optional) If set to true, skip service account creation if a service account with the same email already exists.

## Attributes Reference

In addition to the arguments listed above, the following computed attributes are
Expand Down