Skip to content

Commit 8b21315

Browse files
Add create_ignore_already_exists to google_cloud_identity_group_membership (#14250) (#23376)
[upstream:527d99e9bc723085900474191259de7594e2e918] Signed-off-by: Modular Magician <[email protected]>
1 parent 3c1d4f3 commit 8b21315

File tree

5 files changed

+267
-0
lines changed

5 files changed

+267
-0
lines changed

.changelog/14250.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
unknown: Add create_ignore_already_exists to google_cloud_identity_group_membership

google/services/cloudidentity/resource_cloud_identity_group_membership.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"time"
2929

3030
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
31+
"google.golang.org/api/googleapi"
3132

3233
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
3334
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
@@ -127,6 +128,12 @@ and must be in the form of 'identitysources/{identity_source_id}'.`,
127128
Computed: true,
128129
Description: `The time when the Membership was last updated.`,
129130
},
131+
"create_ignore_already_exists": {
132+
Type: schema.TypeBool,
133+
Optional: true,
134+
Description: `If set to true, skip group member creation if a membership with the same name already exists. Defaults to false.`,
135+
Default: false,
136+
},
130137
},
131138
UseJSONNumber: true,
132139
}
@@ -201,6 +208,75 @@ func resourceCloudIdentityGroupMembershipCreate(d *schema.ResourceData, meta int
201208
}
202209

203210
headers := make(http.Header)
211+
if d.Get("create_ignore_already_exists").(bool) {
212+
log.Printf("[DEBUG] Calling get GroupMembership to check if membership already exists")
213+
preferredMemberKeyPropTyped := tpgresource.CheckStringMap(preferredMemberKeyProp)
214+
215+
params := map[string]string{
216+
"memberKey.id": preferredMemberKeyPropTyped["id"],
217+
}
218+
if ns, ok := preferredMemberKeyPropTyped["namespace"]; ok && ns != "" {
219+
params["memberKey.namespace"] = ns
220+
}
221+
getUrl, err := transport_tpg.AddQueryParams(url+":lookup", params)
222+
if err != nil {
223+
return err
224+
}
225+
226+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
227+
Config: config,
228+
Method: "GET",
229+
Project: billingProject,
230+
RawURL: getUrl,
231+
UserAgent: userAgent,
232+
Headers: headers,
233+
})
234+
// Do normal create if membership does not exist
235+
236+
if err != nil {
237+
gerr, ok := err.(*googleapi.Error)
238+
notFound := ok && gerr.Code == 404
239+
// If group membership is not found, we can proceed with the create operation.
240+
if !notFound {
241+
return fmt.Errorf("Error checking if GroupMembership exists: %s", err)
242+
}
243+
} else {
244+
// Set computed resource properties from create API response so that they're available on the subsequent Read call.
245+
err = resourceCloudIdentityGroupMembershipPostCreateSetComputedFields(d, meta, res)
246+
if err != nil {
247+
return fmt.Errorf("setting computed ID format fields: %w", err)
248+
}
249+
250+
// Store the ID now
251+
id, err := tpgresource.ReplaceVars(d, config, "{{name}}")
252+
if err != nil {
253+
return fmt.Errorf("Error constructing id: %s", err)
254+
}
255+
d.SetId(id)
256+
257+
// `name` is autogenerated from the api so needs to be set post-create
258+
name, ok := res["name"]
259+
if !ok {
260+
respBody, ok := res["response"]
261+
if !ok {
262+
return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.")
263+
}
264+
265+
name, ok = respBody.(map[string]interface{})["name"]
266+
if !ok {
267+
return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.")
268+
}
269+
}
270+
if err := d.Set("name", name.(string)); err != nil {
271+
return fmt.Errorf("Error setting name: %s", err)
272+
}
273+
d.SetId(name.(string))
274+
275+
log.Printf("[DEBUG] Finished creating GroupMembership %q: %#v", d.Id(), res)
276+
277+
return resourceCloudIdentityGroupMembershipRead(d, meta)
278+
}
279+
}
204280
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
205281
Config: config,
206282
Method: "POST",
@@ -283,6 +359,13 @@ func resourceCloudIdentityGroupMembershipRead(d *schema.ResourceData, meta inter
283359
return transport_tpg.HandleNotFoundError(transformCloudIdentityGroupMembershipReadError(err), d, fmt.Sprintf("CloudIdentityGroupMembership %q", d.Id()))
284360
}
285361

362+
// Explicitly set virtual fields to default values if unset
363+
if _, ok := d.GetOkExists("create_ignore_already_exists"); !ok {
364+
if err := d.Set("create_ignore_already_exists", false); err != nil {
365+
return fmt.Errorf("Error setting create_ignore_already_exists: %s", err)
366+
}
367+
}
368+
286369
if err := d.Set("name", flattenCloudIdentityGroupMembershipName(res["name"], d, config)); err != nil {
287370
return fmt.Errorf("Error reading GroupMembership: %s", err)
288371
}
@@ -491,6 +574,10 @@ func resourceCloudIdentityGroupMembershipImport(d *schema.ResourceData, meta int
491574
}
492575
d.SetId(id)
493576

577+
// Explicitly set virtual fields to default values on import
578+
if err := d.Set("create_ignore_already_exists", false); err != nil {
579+
return nil, fmt.Errorf("Error setting create_ignore_already_exists: %s", err)
580+
}
494581
// Configure "group" property, which does not appear in the response body.
495582
group := regexp.MustCompile(`groups/[^/]+`).FindString(id)
496583
if err := d.Set("group", group); err != nil {

google/services/cloudidentity/resource_cloud_identity_group_membership_generated_meta.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ api_service_name: 'cloudidentity.googleapis.com'
55
api_version: 'v1'
66
api_resource_type_kind: 'Membership'
77
fields:
8+
- field: 'create_ignore_already_exists'
9+
provider_only: true
810
- field: 'create_time'
911
- field: 'group'
1012
provider_only: true

google/services/cloudidentity/resource_cloud_identity_group_membership_test.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,3 +448,178 @@ func testAccCheckCloudIdentityGroupMembershipDestroyProducer(t *testing.T) func(
448448
return nil
449449
}
450450
}
451+
452+
// Test setting create_ignore_already_exists on an existing resource
453+
func testAccCloudIdentityGroupMembership_existingResourceCreateIgnoreAlreadyExists(t *testing.T) {
454+
context := map[string]interface{}{
455+
"org_domain": envvar.GetTestOrgDomainFromEnv(t),
456+
"cust_id": envvar.GetTestCustIdFromEnv(t),
457+
"random_suffix": acctest.RandString(t, 10),
458+
}
459+
id := "groups/groupId/memberships/membershipId"
460+
461+
acctest.VcrTest(t, resource.TestCase{
462+
PreCheck: func() { acctest.AccTestPreCheck(t) },
463+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
464+
CheckDestroy: testAccCheckCloudIdentityGroupMembershipDestroyProducer(t),
465+
Steps: []resource.TestStep{
466+
// The first step creates a new resource with create_ignore_already_exists=false
467+
{
468+
Config: testAccCloudIdentityGroupMembershipCreateIgnoreAlreadyExists(context, false),
469+
Check: resource.TestCheckResourceAttr("google_cloud_identity_group_membership.acceptance", "id", id),
470+
},
471+
{
472+
ResourceName: "google_cloud_identity_group_membership.acceptance",
473+
ImportStateId: id,
474+
ImportState: true,
475+
ImportStateVerify: true,
476+
ImportStateVerifyIgnore: []string{"create_ignore_already_exists"}, // Import leaves this field out when false
477+
},
478+
// The second step updates the resource to have create_ignore_already_exists=true
479+
{
480+
Config: testAccCloudIdentityGroupMembershipCreateIgnoreAlreadyExists(context, true),
481+
Check: resource.TestCheckResourceAttr("google_cloud_identity_group_membership.acceptance", "id", id),
482+
},
483+
},
484+
})
485+
}
486+
487+
// Test the option to ignore ALREADY_EXISTS error from creating a Source Repository.
488+
func testAccCloudIdentityGroupMembership_createIgnoreAlreadyExists(t *testing.T) {
489+
context := map[string]interface{}{
490+
"org_domain": envvar.GetTestOrgDomainFromEnv(t),
491+
"cust_id": envvar.GetTestCustIdFromEnv(t),
492+
"random_suffix": acctest.RandString(t, 10),
493+
}
494+
id := "groups/groupId/memberships/membershipId"
495+
496+
acctest.VcrTest(t, resource.TestCase{
497+
PreCheck: func() { acctest.AccTestPreCheck(t) },
498+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
499+
CheckDestroy: testAccCheckCloudIdentityGroupMembershipDestroyProducer(t),
500+
Steps: []resource.TestStep{
501+
// The first step creates a group membership
502+
{
503+
Config: testAccCloudIdentityGroupMembershipCreateIgnoreAlreadyExists(context, false),
504+
Check: resource.TestCheckResourceAttr("google_cloud_identity_group_membership.acceptance", "id", id),
505+
},
506+
{
507+
ResourceName: "google_cloud_identity_group_membership.acceptance",
508+
ImportStateId: id,
509+
ImportState: true,
510+
ImportStateVerify: true,
511+
},
512+
// The second step creates a new resource that duplicates with the existing group membership
513+
{
514+
Config: testAccCloudIdentityGroupMembershipDuplicateIgnoreAlreadyExists(context),
515+
Check: resource.ComposeTestCheckFunc(
516+
resource.TestCheckResourceAttr("google_cloud_identity_group_membership.acceptance", "id", id),
517+
resource.TestCheckResourceAttr("google_cloud_identity_group_membership.duplicate", "id", id),
518+
),
519+
},
520+
},
521+
})
522+
}
523+
524+
func testAccCloudIdentityGroupMembershipCreateIgnoreAlreadyExists(context map[string]interface{}, ignore_already_exists bool) string {
525+
context["create_ignore_already_exists"] = fmt.Sprintf("%t", ignore_already_exists)
526+
return acctest.Nprintf(`
527+
resource "google_cloud_identity_group" "group" {
528+
display_name = "tf-test-my-identity-group%{random_suffix}"
529+
530+
parent = "customers/%{cust_id}"
531+
532+
group_key {
533+
id = "tf-test-my-identity-group%{random_suffix}@%{org_domain}"
534+
}
535+
536+
labels = {
537+
"cloudidentity.googleapis.com/groups.discussion_forum" = ""
538+
}
539+
}
540+
resource "google_cloud_identity_group" "child-group" {
541+
display_name = "tf-test-my-identity-group%{random_suffix}-child"
542+
543+
parent = "customers/%{cust_id}"
544+
545+
group_key {
546+
id = "tf-test-my-identity-group%{random_suffix}-child@%{org_domain}"
547+
}
548+
549+
labels = {
550+
"cloudidentity.googleapis.com/groups.discussion_forum" = ""
551+
}
552+
}
553+
554+
resource "google_cloud_identity_group_membership" "acceptance" {
555+
group = google_cloud_identity_group.group.id
556+
557+
preferred_member_key {
558+
id = google_cloud_identity_group.child-group.group_key[0].id
559+
}
560+
561+
roles {
562+
name = "MEMBER"
563+
}
564+
565+
create_ignore_already_exists = %{create_ignore_already_exists}
566+
}
567+
`, context)
568+
}
569+
570+
func testAccCloudIdentityGroupMembershipDuplicateIgnoreAlreadyExists(context map[string]interface{}) string {
571+
return acctest.Nprintf(`
572+
resource "google_cloud_identity_group" "group" {
573+
display_name = "tf-test-my-identity-group%{random_suffix}"
574+
575+
parent = "customers/%{cust_id}"
576+
577+
group_key {
578+
id = "tf-test-my-identity-group%{random_suffix}@%{org_domain}"
579+
}
580+
581+
labels = {
582+
"cloudidentity.googleapis.com/groups.discussion_forum" = ""
583+
}
584+
}
585+
resource "google_cloud_identity_group" "child-group" {
586+
display_name = "tf-test-my-identity-group%{random_suffix}-child"
587+
588+
parent = "customers/%{cust_id}"
589+
590+
group_key {
591+
id = "tf-test-my-identity-group%{random_suffix}-child@%{org_domain}"
592+
}
593+
594+
labels = {
595+
"cloudidentity.googleapis.com/groups.discussion_forum" = ""
596+
}
597+
}
598+
599+
resource "google_cloud_identity_group_membership" "acceptance" {
600+
group = google_cloud_identity_group.group.id
601+
602+
preferred_member_key {
603+
id = google_cloud_identity_group.child-group.group_key[0].id
604+
}
605+
606+
roles {
607+
name = "MEMBER"
608+
}
609+
}
610+
611+
resource "google_cloud_identity_group_membership" "duplicate" {
612+
group = google_cloud_identity_group.group.id
613+
614+
preferred_member_key {
615+
id = google_cloud_identity_group.child-group.group_key[0].id
616+
}
617+
618+
roles {
619+
name = "MEMBER"
620+
}
621+
622+
create_ignore_already_exists = true
623+
}
624+
`, context)
625+
}

website/docs/r/cloud_identity_group_membership.html.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ The following arguments are supported:
167167
EntityKey of the member.
168168
Structure is [documented below](#nested_preferred_member_key).
169169

170+
* `create_ignore_already_exists` - (Optional) If set to true, skip group member creation if a membership with the same name already exists. Defaults to false.
171+
170172

171173
<a name="nested_member_key"></a>The `member_key` block supports:
172174

0 commit comments

Comments
 (0)