Skip to content

Commit 552e353

Browse files
Fix cloud_identity_group_membership to properly handle 403 responses (#7089) (#13496)
* Fix cloud_identity_group_membership to properly handle 403 responses when membership does not exist * Fix service account creation errors to cause entire test to fail immediately * Skip VCR for this test to avoid managing service accounts out-of-band Signed-off-by: Modular Magician <[email protected]> Signed-off-by: Modular Magician <[email protected]>
1 parent 2930e07 commit 552e353

4 files changed

+116
-1
lines changed

.changelog/7089.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:none
2+
3+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package google
2+
3+
import (
4+
"log"
5+
"strings"
6+
7+
"github.com/hashicorp/errwrap"
8+
"google.golang.org/api/googleapi"
9+
)
10+
11+
func transformCloudIdentityGroupMembershipReadError(err error) error {
12+
if gErr, ok := errwrap.GetType(err, &googleapi.Error{}).(*googleapi.Error); ok {
13+
if gErr.Code == 403 && strings.Contains(gErr.Message, "(or it may not exist)") {
14+
// This error occurs when either the group membership does not exist, or permission is denied. It is
15+
// deliberately ambiguous so that existence information is not revealed to the caller. However, for
16+
// the Read function, we can only assume that the membership does not exist, and proceed with attempting
17+
// other operations. Since handleNotFoundError(...) expects an error code of 404 when a resource does not
18+
// exist, to get the desired behavior, we modify the error code to be 404.
19+
gErr.Code = 404
20+
}
21+
22+
log.Printf("[DEBUG] Transformed CloudIdentityGroupMembership error")
23+
return gErr
24+
}
25+
26+
return err
27+
}

google/resource_cloud_identity_group_membership.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ func resourceCloudIdentityGroupMembershipRead(d *schema.ResourceData, meta inter
228228

229229
res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil)
230230
if err != nil {
231-
return handleNotFoundError(err, d, fmt.Sprintf("CloudIdentityGroupMembership %q", d.Id()))
231+
return handleNotFoundError(transformCloudIdentityGroupMembershipReadError(err), d, fmt.Sprintf("CloudIdentityGroupMembership %q", d.Id()))
232232
}
233233

234234
if err := d.Set("name", flattenCloudIdentityGroupMembershipName(res["name"], d, config)); err != nil {

google/resource_cloud_identity_group_membership_test.go

+85
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"testing"
55

66
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
7+
"google.golang.org/api/iam/v1"
78
)
89

910
func TestAccCloudIdentityGroupMembership_update(t *testing.T) {
@@ -174,3 +175,87 @@ resource "google_cloud_identity_group_membership" "basic" {
174175
}
175176
`, context)
176177
}
178+
179+
func TestAccCloudIdentityGroupMembership_membershipDoesNotExist(t *testing.T) {
180+
// Skip VCR because the service account needs to be created/deleted out of
181+
// band, and so those calls aren't recorded
182+
skipIfVcr(t)
183+
t.Parallel()
184+
185+
context := map[string]interface{}{
186+
"org_domain": getTestOrgDomainFromEnv(t),
187+
"cust_id": getTestCustIdFromEnv(t),
188+
"random_suffix": randString(t, 10),
189+
}
190+
191+
saId := "tf-test-sa-" + randString(t, 10)
192+
project := getTestProjectFromEnv()
193+
config := BootstrapConfig(t)
194+
195+
r := &iam.CreateServiceAccountRequest{
196+
AccountId: saId,
197+
ServiceAccount: &iam.ServiceAccount{},
198+
}
199+
200+
sa, err := config.NewIamClient(config.userAgent).Projects.ServiceAccounts.Create("projects/"+project, r).Do()
201+
if err != nil {
202+
t.Fatalf("Error creating service account: %s", err)
203+
}
204+
205+
context["member_id"] = sa.Email
206+
207+
vcrTest(t, resource.TestCase{
208+
PreCheck: func() { testAccPreCheck(t) },
209+
Providers: testAccProviders,
210+
CheckDestroy: testAccCheckCloudIdentityGroupMembershipDestroyProducer(t),
211+
Steps: []resource.TestStep{
212+
{
213+
Config: testAccCloudIdentityGroupMembership_dne(context),
214+
},
215+
{
216+
PreConfig: func() {
217+
config := googleProviderConfig(t)
218+
219+
_, err := config.NewIamClient(config.userAgent).Projects.ServiceAccounts.Delete(sa.Name).Do()
220+
if err != nil {
221+
t.Errorf("cannot delete service account %s: %v", sa.Name, err)
222+
return
223+
}
224+
},
225+
Config: testAccCloudIdentityGroupMembership_dne(context),
226+
PlanOnly: true,
227+
ExpectNonEmptyPlan: true,
228+
},
229+
},
230+
})
231+
}
232+
233+
func testAccCloudIdentityGroupMembership_dne(context map[string]interface{}) string {
234+
return Nprintf(`
235+
resource "google_cloud_identity_group" "group" {
236+
display_name = "tf-test-my-identity-group-%{random_suffix}"
237+
238+
parent = "customers/%{cust_id}"
239+
240+
group_key {
241+
id = "tf-test-my-identity-group-%{random_suffix}@%{org_domain}"
242+
}
243+
244+
labels = {
245+
"cloudidentity.googleapis.com/groups.discussion_forum" = ""
246+
}
247+
}
248+
249+
resource "google_cloud_identity_group_membership" "basic" {
250+
group = google_cloud_identity_group.group.id
251+
252+
preferred_member_key {
253+
id = "%{member_id}"
254+
}
255+
256+
roles {
257+
name = "MEMBER"
258+
}
259+
}
260+
`, context)
261+
}

0 commit comments

Comments
 (0)