Skip to content

Commit 2f1ca7c

Browse files
Add error handling for ALREADY_EXISTS in IAM CreateServiceAccount call (#9727) (#16927)
* Add error handling for ALREADY_EXISTS in IAM CreateServiceAccount call * Use a separate unit test for create_ignore_already_exists [upstream:612663010b4da3022cade6f2a07f4d6beb2b6d51] Signed-off-by: Modular Magician <[email protected]>
1 parent 12fcca1 commit 2f1ca7c

File tree

4 files changed

+83
-2
lines changed

4 files changed

+83
-2
lines changed

.changelog/9727.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:note
2+
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.
3+
```

google/services/resourcemanager/resource_google_service_account.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
1515
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1616
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
17+
"google.golang.org/api/googleapi"
1718
"google.golang.org/api/iam/v1"
1819
)
1920

@@ -84,6 +85,12 @@ func ResourceGoogleServiceAccount() *schema.Resource {
8485
Computed: true,
8586
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.`,
8687
},
88+
"create_ignore_already_exists": {
89+
Type: schema.TypeBool,
90+
Optional: true,
91+
Computed: false,
92+
Description: `If set to true, skip service account creation if a service account with the same email already exists.`,
93+
},
8794
},
8895
UseJSONNumber: true,
8996
}
@@ -116,7 +123,15 @@ func resourceGoogleServiceAccountCreate(d *schema.ResourceData, meta interface{}
116123

117124
sa, err = config.NewIamClient(userAgent).Projects.ServiceAccounts.Create("projects/"+project, r).Do()
118125
if err != nil {
119-
return fmt.Errorf("Error creating service account: %s", err)
126+
gerr, ok := err.(*googleapi.Error)
127+
alreadyExists := ok && gerr.Code == 409 && d.Get("create_ignore_already_exists").(bool)
128+
if alreadyExists {
129+
sa = &iam.ServiceAccount{
130+
Name: fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", project, aid, project),
131+
}
132+
} else {
133+
return fmt.Errorf("Error creating service account: %s", err)
134+
}
120135
}
121136

122137
d.SetId(sa.Name)
@@ -218,7 +233,11 @@ func resourceGoogleServiceAccountDelete(d *schema.ResourceData, meta interface{}
218233
name := d.Id()
219234
_, err = config.NewIamClient(userAgent).Projects.ServiceAccounts.Delete(name).Do()
220235
if err != nil {
221-
return err
236+
gerr, ok := err.(*googleapi.Error)
237+
notFound := ok && gerr.Code == 404
238+
if !notFound {
239+
return fmt.Errorf("Error deleting service account: %s", err)
240+
}
222241
}
223242
d.SetId("")
224243
return nil

google/services/resourcemanager/resource_google_service_account_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,47 @@ func TestAccServiceAccount_basic(t *testing.T) {
9090
})
9191
}
9292

93+
// Test the option to ignore ALREADY_EXISTS error from creating a service account.
94+
func TestAccServiceAccount_createIgnoreAlreadyExists(t *testing.T) {
95+
t.Parallel()
96+
97+
accountId := "a" + acctest.RandString(t, 10)
98+
displayName := "Terraform Test"
99+
desc := "test description"
100+
project := envvar.GetTestProjectFromEnv()
101+
expectedEmail := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", accountId, project)
102+
acctest.VcrTest(t, resource.TestCase{
103+
PreCheck: func() { acctest.AccTestPreCheck(t) },
104+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
105+
Steps: []resource.TestStep{
106+
// The first step creates a basic service account
107+
{
108+
Config: testAccServiceAccountBasic(accountId, displayName, desc),
109+
Check: resource.ComposeTestCheckFunc(
110+
resource.TestCheckResourceAttr(
111+
"google_service_account.acceptance", "project", project),
112+
resource.TestCheckResourceAttr(
113+
"google_service_account.acceptance", "member", "serviceAccount:"+expectedEmail),
114+
),
115+
},
116+
{
117+
ResourceName: "google_service_account.acceptance",
118+
ImportStateId: fmt.Sprintf("projects/%s/serviceAccounts/%s", project, expectedEmail),
119+
ImportState: true,
120+
ImportStateVerify: true,
121+
},
122+
// The second step creates a new resource that duplicates with the existing service account.
123+
{
124+
Config: testAccServiceAccountCreateIgnoreAlreadyExists(accountId, displayName, desc),
125+
Check: resource.ComposeTestCheckFunc(
126+
resource.TestCheckResourceAttr(
127+
"google_service_account.duplicate", "member", "serviceAccount:"+expectedEmail),
128+
),
129+
},
130+
},
131+
})
132+
}
133+
93134
func TestAccServiceAccount_Disabled(t *testing.T) {
94135
t.Parallel()
95136

@@ -168,6 +209,22 @@ resource "google_service_account" "acceptance" {
168209
`, account, name, desc)
169210
}
170211

212+
func testAccServiceAccountCreateIgnoreAlreadyExists(account, name, desc string) string {
213+
return fmt.Sprintf(`
214+
resource "google_service_account" "acceptance" {
215+
account_id = "%v"
216+
display_name = "%v"
217+
description = "%v"
218+
}
219+
resource "google_service_account" "duplicate" {
220+
account_id = "%v"
221+
display_name = "%v"
222+
description = "%v"
223+
create_ignore_already_exists = true
224+
}
225+
`, account, name, desc, account, name, desc)
226+
}
227+
171228
func testAccServiceAccountWithProject(project, account, name string) string {
172229
return fmt.Sprintf(`
173230
resource "google_service_account" "acceptance" {

website/docs/r/google_service_account.html.markdown

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ The following arguments are supported:
5151
* `project` - (Optional) The ID of the project that the service account will be created in.
5252
Defaults to the provider project configuration.
5353

54+
* `create_ignore_already_exists` - (Optional) If set to true, skip service account creation if a service account with the same email already exists.
55+
5456
## Attributes Reference
5557

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

0 commit comments

Comments
 (0)