Skip to content

Commit 35e4593

Browse files
Update service account creation to be more resilient to eventual cons… (#14547) (#23639)
[upstream:1deff66dadca78853f6aad15d2831ee4ffff6f26] Signed-off-by: Modular Magician <[email protected]>
1 parent 761a854 commit 35e4593

File tree

2 files changed

+41
-27
lines changed

2 files changed

+41
-27
lines changed

.changelog/14547.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
resourcemanager: updated service account creation to prevent failures due to eventual consistency in `google_service_account` resource
3+
```

google/services/resourcemanager/resource_google_service_account.go

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -138,54 +138,61 @@ func resourceGoogleServiceAccountCreate(d *schema.ResourceData, meta interface{}
138138
ServiceAccount: sa,
139139
}
140140

141-
sa, err = config.NewIamClient(userAgent).Projects.ServiceAccounts.Create("projects/"+project, r).Do()
141+
iamClient := config.NewIamClient(userAgent)
142+
sa, err = iamClient.Projects.ServiceAccounts.Create("projects/"+project, r).Do()
142143
if err != nil {
143144
gerr, ok := err.(*googleapi.Error)
144145
alreadyExists := ok && gerr.Code == 409 && d.Get("create_ignore_already_exists").(bool)
145146
if alreadyExists {
146-
sa = &iam.ServiceAccount{
147-
Name: fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", project, aid, project),
148-
}
147+
fullServiceAccountName := fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", project, aid, project)
148+
err = transport_tpg.Retry(transport_tpg.RetryOptions{
149+
RetryFunc: func() (operr error) {
150+
sa, saerr := iamClient.Projects.ServiceAccounts.Get(fullServiceAccountName).Do()
151+
152+
if saerr != nil {
153+
return saerr
154+
}
155+
156+
d.SetId(sa.Name)
157+
return populateResourceData(d, sa)
158+
},
159+
Timeout: d.Timeout(schema.TimeoutCreate),
160+
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{
161+
transport_tpg.IsNotFoundRetryableError("service account creation"),
162+
},
163+
})
164+
165+
return nil
149166
} else {
150167
return fmt.Errorf("Error creating service account: %s", err)
151168
}
152169
}
153170

154171
d.SetId(sa.Name)
155-
156-
err = transport_tpg.Retry(transport_tpg.RetryOptions{
157-
RetryFunc: func() (operr error) {
158-
_, saerr := config.NewIamClient(userAgent).Projects.ServiceAccounts.Get(d.Id()).Do()
159-
return saerr
160-
},
161-
Timeout: d.Timeout(schema.TimeoutCreate),
162-
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{
163-
transport_tpg.IsNotFoundRetryableError("service account creation"),
164-
transport_tpg.IsForbiddenIamServiceAccountRetryableError("service account creation"),
165-
},
166-
})
167-
168-
if err != nil {
169-
return fmt.Errorf("Error reading service account after creation: %s", err)
170-
}
172+
populateResourceData(d, sa)
171173

172174
// We poll until the resource is found due to eventual consistency issue
173-
// on part of the api https://cloud.google.com/iam/docs/overview#consistency
175+
// on part of the api https://cloud.google.com/iam/docs/overview#consistency.
176+
// Wait for at least 3 successful responses in a row to ensure result is consistent.
174177
// IAM API returns 403 when the queried SA is not found, so we must ignore both 404 & 403 errors
175-
err = transport_tpg.PollingWaitTime(resourceServiceAccountPollRead(d, meta), transport_tpg.PollCheckForExistenceWith403, "Creating Service Account", d.Timeout(schema.TimeoutCreate), 1)
176-
177-
if err != nil {
178-
return err
179-
}
178+
transport_tpg.PollingWaitTime(
179+
resourceServiceAccountPollRead(d, meta),
180+
transport_tpg.PollCheckForExistence,
181+
"Creating Service Account",
182+
d.Timeout(schema.TimeoutCreate),
183+
3, // Number of consecutive occurences.
184+
)
180185

181186
// We can't guarantee complete consistency even after polling,
182187
// so sleep for some additional time to reduce the likelihood of
183188
// eventual consistency failures.
184189
time.Sleep(10 * time.Second)
185190

186-
return resourceGoogleServiceAccountRead(d, meta)
191+
return nil
187192
}
188193

194+
// PollReadFunc for checking Service Account existence.
195+
// If resourceData is not nil, it will be updated with the response.
189196
func resourceServiceAccountPollRead(d *schema.ResourceData, meta interface{}) transport_tpg.PollReadFunc {
190197
return func() (map[string]interface{}, error) {
191198
config := meta.(*transport_tpg.Config)
@@ -217,6 +224,10 @@ func resourceGoogleServiceAccountRead(d *schema.ResourceData, meta interface{})
217224
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("Service Account %q", d.Id()))
218225
}
219226

227+
return populateResourceData(d, sa)
228+
}
229+
230+
func populateResourceData(d *schema.ResourceData, sa *iam.ServiceAccount) error {
220231
if err := d.Set("email", sa.Email); err != nil {
221232
return fmt.Errorf("Error setting email: %s", err)
222233
}

0 commit comments

Comments
 (0)