diff --git a/.changelog/3696.txt b/.changelog/3696.txt new file mode 100644 index 0000000000..c575b2b054 --- /dev/null +++ b/.changelog/3696.txt @@ -0,0 +1,6 @@ +```release-note:new-resource +`google_cloud_identity_group` (TPGB-only) +``` +```release-note:note +added the `https://www.googleapis.com/auth/cloud-identity` scope to the provider by default +``` diff --git a/google-beta/config.go b/google-beta/config.go index 43faea555e..5150700346 100644 --- a/google-beta/config.go +++ b/google-beta/config.go @@ -91,6 +91,7 @@ type Config struct { BinaryAuthorizationBasePath string CloudBuildBasePath string CloudFunctionsBasePath string + CloudIdentityBasePath string CloudIotBasePath string CloudRunBasePath string CloudSchedulerBasePath string @@ -244,6 +245,7 @@ var BillingDefaultBasePath = "https://billingbudgets.googleapis.com/v1beta1/" var BinaryAuthorizationDefaultBasePath = "https://binaryauthorization.googleapis.com/v1/" var CloudBuildDefaultBasePath = "https://cloudbuild.googleapis.com/v1/" var CloudFunctionsDefaultBasePath = "https://cloudfunctions.googleapis.com/v1/" +var CloudIdentityDefaultBasePath = "https://cloudidentity.googleapis.com/v1beta1/" var CloudIotDefaultBasePath = "https://cloudiot.googleapis.com/v1/" var CloudRunDefaultBasePath = "https://{{location}}-run.googleapis.com/" var CloudSchedulerDefaultBasePath = "https://cloudscheduler.googleapis.com/v1/" @@ -292,6 +294,7 @@ var VPCAccessDefaultBasePath = "https://vpcaccess.googleapis.com/v1/" var defaultClientScopes = []string{ "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-identity", "https://www.googleapis.com/auth/ndev.clouddns.readwrite", "https://www.googleapis.com/auth/devstorage.full_control", "https://www.googleapis.com/auth/userinfo.email", @@ -772,6 +775,7 @@ func ConfigureBasePaths(c *Config) { c.BinaryAuthorizationBasePath = BinaryAuthorizationDefaultBasePath c.CloudBuildBasePath = CloudBuildDefaultBasePath c.CloudFunctionsBasePath = CloudFunctionsDefaultBasePath + c.CloudIdentityBasePath = CloudIdentityDefaultBasePath c.CloudIotBasePath = CloudIotDefaultBasePath c.CloudRunBasePath = CloudRunDefaultBasePath c.CloudSchedulerBasePath = CloudSchedulerDefaultBasePath diff --git a/google-beta/provider.go b/google-beta/provider.go index 635f2abd37..0c6be353f0 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -205,6 +205,14 @@ func Provider() terraform.ResourceProvider { "GOOGLE_CLOUD_FUNCTIONS_CUSTOM_ENDPOINT", }, CloudFunctionsDefaultBasePath), }, + "cloud_identity_custom_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCustomEndpoint, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_CLOUD_IDENTITY_CUSTOM_ENDPOINT", + }, CloudIdentityDefaultBasePath), + }, "cloud_iot_custom_endpoint": { Type: schema.TypeString, Optional: true, @@ -663,9 +671,9 @@ func Provider() terraform.ResourceProvider { return provider } -// Generated resources: 165 +// Generated resources: 167 // Generated IAM resources: 66 -// Total generated resources: 231 +// Total generated resources: 233 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -705,6 +713,8 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_cloudfunctions_function_iam_binding": ResourceIamBinding(CloudFunctionsCloudFunctionIamSchema, CloudFunctionsCloudFunctionIamUpdaterProducer, CloudFunctionsCloudFunctionIdParseFunc), "google_cloudfunctions_function_iam_member": ResourceIamMember(CloudFunctionsCloudFunctionIamSchema, CloudFunctionsCloudFunctionIamUpdaterProducer, CloudFunctionsCloudFunctionIdParseFunc), "google_cloudfunctions_function_iam_policy": ResourceIamPolicy(CloudFunctionsCloudFunctionIamSchema, CloudFunctionsCloudFunctionIamUpdaterProducer, CloudFunctionsCloudFunctionIdParseFunc), + "google_cloud_identity_group": resourceCloudIdentityGroup(), + "google_cloud_identity_group_membership": resourceCloudIdentityGroupMembership(), "google_cloudiot_registry": resourceCloudIotDeviceRegistry(), "google_cloud_run_domain_mapping": resourceCloudRunDomainMapping(), "google_cloud_run_service": resourceCloudRunService(), @@ -1085,6 +1095,7 @@ func providerConfigure(d *schema.ResourceData, p *schema.Provider, terraformVers config.BinaryAuthorizationBasePath = d.Get("binary_authorization_custom_endpoint").(string) config.CloudBuildBasePath = d.Get("cloud_build_custom_endpoint").(string) config.CloudFunctionsBasePath = d.Get("cloud_functions_custom_endpoint").(string) + config.CloudIdentityBasePath = d.Get("cloud_identity_custom_endpoint").(string) config.CloudIotBasePath = d.Get("cloud_iot_custom_endpoint").(string) config.CloudRunBasePath = d.Get("cloud_run_custom_endpoint").(string) config.CloudSchedulerBasePath = d.Get("cloud_scheduler_custom_endpoint").(string) diff --git a/google-beta/provider_test.go b/google-beta/provider_test.go index 8cbeffe6e7..2767b439db 100644 --- a/google-beta/provider_test.go +++ b/google-beta/provider_test.go @@ -65,6 +65,14 @@ var orgEnvVars = []string{ "GOOGLE_ORG", } +var custIdEnvVars = []string{ + "GOOGLE_CUST_ID", +} + +var identityUserEnvVars = []string{ + "GOOGLE_IDENTITY_USER", +} + var orgEnvDomainVars = []string{ "GOOGLE_ORG_DOMAIN", } @@ -837,6 +845,14 @@ func getTestZoneFromEnv() string { return multiEnvSearch(zoneEnvVars) } +func getTestCustIdFromEnv(t *testing.T) string { + return multiEnvSearch(custIdEnvVars) +} + +func getTestIdentityUserFromEnv(t *testing.T) string { + return multiEnvSearch(identityUserEnvVars) +} + // Firestore can't be enabled at the same time as Datastore, so we need a new // project to manage it until we can enable Firestore programmatically. func getTestFirestoreProjectFromEnv(t *testing.T) string { diff --git a/google-beta/resource_bigquery_data_transfer_config.go b/google-beta/resource_bigquery_data_transfer_config.go index e03ff2d796..53d973b8e9 100644 --- a/google-beta/resource_bigquery_data_transfer_config.go +++ b/google-beta/resource_bigquery_data_transfer_config.go @@ -204,7 +204,15 @@ func resourceBigqueryDataTransferConfigCreate(d *schema.ResourceData, meta inter // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_billing_budget.go b/google-beta/resource_billing_budget.go index 29e936769d..e47fb52e63 100644 --- a/google-beta/resource_billing_budget.go +++ b/google-beta/resource_billing_budget.go @@ -247,7 +247,15 @@ func resourceBillingBudgetCreate(d *schema.ResourceData, meta interface{}) error // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_cloud_identity_group.go b/google-beta/resource_cloud_identity_group.go new file mode 100644 index 0000000000..48ec6ce242 --- /dev/null +++ b/google-beta/resource_cloud_identity_group.go @@ -0,0 +1,444 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceCloudIdentityGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudIdentityGroupCreate, + Read: resourceCloudIdentityGroupRead, + Update: resourceCloudIdentityGroupUpdate, + Delete: resourceCloudIdentityGroupDelete, + + Importer: &schema.ResourceImporter{ + State: resourceCloudIdentityGroupImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "group_key": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: `EntityKey of the Group.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The ID of the entity. + +For Google-managed entities, the id must be the email address of an existing +group or user. + +For external-identity-mapped entities, the id must be a string conforming +to the Identity Source's requirements. + +Must be unique within a namespace.`, + }, + "namespace": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The namespace in which the entity exists. + +If not specified, the EntityKey represents a Google-managed entity +such as a Google user or a Google Group. + +If specified, the EntityKey represents an external-identity-mapped group. +The namespace must correspond to an identity source created in Admin Console +and must be in the form of 'identitysources/{identity_source_id}'.`, + }, + }, + }, + }, + "labels": { + Type: schema.TypeMap, + Required: true, + ForceNew: true, + Description: `The labels that apply to the Group. + +Must not contain more than one entry. Must contain the entry +'cloudidentity.googleapis.com/groups.discussion_forum': '' if the Group is a Google Group or +'system/groups/external': '' if the Group is an external-identity-mapped group.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "parent": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The resource name of the entity under which this Group resides in the +Cloud Identity resource hierarchy. + +Must be of the form identitysources/{identity_source_id} for external-identity-mapped +groups or customers/{customer_id} for Google Groups.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `An extended description to help users determine the purpose of a Group. +Must not be longer than 4,096 characters.`, + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Description: `The display name of the Group.`, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `The time when the Group was created.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `Resource name of the Group in the format: groups/{group_id}, where group_id +is the unique ID assigned to the Group.`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `The time when the Group was last updated.`, + }, + }, + } +} + +func resourceCloudIdentityGroupCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + groupKeyProp, err := expandCloudIdentityGroupGroupKey(d.Get("group_key"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("group_key"); !isEmptyValue(reflect.ValueOf(groupKeyProp)) && (ok || !reflect.DeepEqual(v, groupKeyProp)) { + obj["groupKey"] = groupKeyProp + } + parentProp, err := expandCloudIdentityGroupParent(d.Get("parent"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("parent"); !isEmptyValue(reflect.ValueOf(parentProp)) && (ok || !reflect.DeepEqual(v, parentProp)) { + obj["parent"] = parentProp + } + displayNameProp, err := expandCloudIdentityGroupDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(displayNameProp)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + descriptionProp, err := expandCloudIdentityGroupDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + labelsProp, err := expandCloudIdentityGroupLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + + url, err := replaceVars(d, config, "{{CloudIdentityBasePath}}groups") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Group: %#v", obj) + res, err := sendRequestWithTimeout(config, "POST", "", url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating Group: %s", err) + } + if err := d.Set("name", flattenCloudIdentityGroupName(res["name"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "name": %s`, err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Group %q: %#v", d.Id(), res) + + // `name` is autogenerated from the api so needs to be set post-create + name, ok := res["name"] + if !ok { + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + } + d.Set("name", name.(string)) + d.SetId(name.(string)) + + return resourceCloudIdentityGroupRead(d, meta) +} + +func resourceCloudIdentityGroupRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{CloudIdentityBasePath}}{{name}}") + if err != nil { + return err + } + + res, err := sendRequest(config, "GET", "", url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("CloudIdentityGroup %q", d.Id())) + } + + if err := d.Set("name", flattenCloudIdentityGroupName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading Group: %s", err) + } + if err := d.Set("group_key", flattenCloudIdentityGroupGroupKey(res["groupKey"], d, config)); err != nil { + return fmt.Errorf("Error reading Group: %s", err) + } + if err := d.Set("parent", flattenCloudIdentityGroupParent(res["parent"], d, config)); err != nil { + return fmt.Errorf("Error reading Group: %s", err) + } + if err := d.Set("display_name", flattenCloudIdentityGroupDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading Group: %s", err) + } + if err := d.Set("description", flattenCloudIdentityGroupDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading Group: %s", err) + } + if err := d.Set("create_time", flattenCloudIdentityGroupCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Group: %s", err) + } + if err := d.Set("update_time", flattenCloudIdentityGroupUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Group: %s", err) + } + if err := d.Set("labels", flattenCloudIdentityGroupLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading Group: %s", err) + } + + return nil +} + +func resourceCloudIdentityGroupUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + displayNameProp, err := expandCloudIdentityGroupDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + descriptionProp, err := expandCloudIdentityGroupDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + + url, err := replaceVars(d, config, "{{CloudIdentityBasePath}}{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating Group %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("display_name") { + updateMask = append(updateMask, "displayName") + } + + if d.HasChange("description") { + updateMask = append(updateMask, "description") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + _, err = sendRequestWithTimeout(config, "PATCH", "", url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating Group %q: %s", d.Id(), err) + } + + return resourceCloudIdentityGroupRead(d, meta) +} + +func resourceCloudIdentityGroupDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{CloudIdentityBasePath}}{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting Group %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", "", url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "Group") + } + + log.Printf("[DEBUG] Finished deleting Group %q: %#v", d.Id(), res) + return nil +} + +func resourceCloudIdentityGroupImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenCloudIdentityGroupName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupGroupKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["id"] = + flattenCloudIdentityGroupGroupKeyId(original["id"], d, config) + transformed["namespace"] = + flattenCloudIdentityGroupGroupKeyNamespace(original["namespace"], d, config) + return []interface{}{transformed} +} +func flattenCloudIdentityGroupGroupKeyId(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupGroupKeyNamespace(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupParent(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupDisplayName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupCreateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupUpdateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandCloudIdentityGroupGroupKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedId, err := expandCloudIdentityGroupGroupKeyId(original["id"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedId); val.IsValid() && !isEmptyValue(val) { + transformed["id"] = transformedId + } + + transformedNamespace, err := expandCloudIdentityGroupGroupKeyNamespace(original["namespace"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespace); val.IsValid() && !isEmptyValue(val) { + transformed["namespace"] = transformedNamespace + } + + return transformed, nil +} + +func expandCloudIdentityGroupGroupKeyId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudIdentityGroupGroupKeyNamespace(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudIdentityGroupParent(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudIdentityGroupDisplayName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudIdentityGroupDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudIdentityGroupLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} diff --git a/google-beta/resource_cloud_identity_group_generated_test.go b/google-beta/resource_cloud_identity_group_generated_test.go new file mode 100644 index 0000000000..6d2ef65a0d --- /dev/null +++ b/google-beta/resource_cloud_identity_group_generated_test.go @@ -0,0 +1,91 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccCloudIdentityGroup_cloudIdentityGroupsBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_domain": getTestOrgDomainFromEnv(t), + "cust_id": getTestCustIdFromEnv(t), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckCloudIdentityGroupDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudIdentityGroup_cloudIdentityGroupsBasicExample(context), + }, + }, + }) +} + +func testAccCloudIdentityGroup_cloudIdentityGroupsBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_cloud_identity_group" "cloud_identity_group_basic" { + provider = google-beta + display_name = "tf-test-my-identity-group%{random_suffix}" + + parent = "customers/%{cust_id}" + + group_key { + id = "tf-test-my-identity-group%{random_suffix}@%{org_domain}" + } + + labels = { + "cloudidentity.googleapis.com/groups.discussion_forum" = "" + } +} +`, context) +} + +func testAccCheckCloudIdentityGroupDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_cloud_identity_group" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{CloudIdentityBasePath}}{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", "", url, nil) + if err == nil { + return fmt.Errorf("CloudIdentityGroup still exists at %s", url) + } + } + + return nil + } +} diff --git a/google-beta/resource_cloud_identity_group_membership.go b/google-beta/resource_cloud_identity_group_membership.go new file mode 100644 index 0000000000..0246ab1873 --- /dev/null +++ b/google-beta/resource_cloud_identity_group_membership.go @@ -0,0 +1,527 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceCloudIdentityGroupMembership() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudIdentityGroupMembershipCreate, + Read: resourceCloudIdentityGroupMembershipRead, + Update: resourceCloudIdentityGroupMembershipUpdate, + Delete: resourceCloudIdentityGroupMembershipDelete, + + Importer: &schema.ResourceImporter{ + State: resourceCloudIdentityGroupMembershipImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "group": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: `The name of the Group to create this membership in.`, + }, + "roles": { + Type: schema.TypeList, + Required: true, + Description: `The MembershipRoles that apply to the Membership. +Must not contain duplicate MembershipRoles with the same name.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"OWNER", "MANAGER", "MEMBER"}, false), + Description: `The name of the MembershipRole. Must be one of OWNER, MANAGER, MEMBER. Possible values: ["OWNER", "MANAGER", "MEMBER"]`, + }, + }, + }, + }, + "member_key": { + Type: schema.TypeList, + Computed: true, + Optional: true, + ForceNew: true, + Description: `EntityKey of the member.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The ID of the entity. + +For Google-managed entities, the id must be the email address of an existing +group or user. + +For external-identity-mapped entities, the id must be a string conforming +to the Identity Source's requirements. + +Must be unique within a namespace.`, + }, + "namespace": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The namespace in which the entity exists. + +If not specified, the EntityKey represents a Google-managed entity +such as a Google user or a Google Group. + +If specified, the EntityKey represents an external-identity-mapped group. +The namespace must correspond to an identity source created in Admin Console +and must be in the form of 'identitysources/{identity_source_id}'.`, + }, + }, + }, + ExactlyOneOf: []string{"member_key", "preferred_member_key"}, + }, + "preferred_member_key": { + Type: schema.TypeList, + Computed: true, + Optional: true, + ForceNew: true, + Description: `EntityKey of the member.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The ID of the entity. + +For Google-managed entities, the id must be the email address of an existing +group or user. + +For external-identity-mapped entities, the id must be a string conforming +to the Identity Source's requirements. + +Must be unique within a namespace.`, + }, + "namespace": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The namespace in which the entity exists. + +If not specified, the EntityKey represents a Google-managed entity +such as a Google user or a Google Group. + +If specified, the EntityKey represents an external-identity-mapped group. +The namespace must correspond to an identity source created in Admin Console +and must be in the form of 'identitysources/{identity_source_id}'.`, + }, + }, + }, + ExactlyOneOf: []string{"member_key", "preferred_member_key"}, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `The time when the Membership was created.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource name of the Membership, of the form groups/{group_id}/memberships/{membership_id}.`, + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: `The type of the membership.`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `The time when the Membership was last updated.`, + }, + }, + } +} + +func resourceCloudIdentityGroupMembershipCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + memberKeyProp, err := expandCloudIdentityGroupMembershipMemberKey(d.Get("member_key"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("member_key"); !isEmptyValue(reflect.ValueOf(memberKeyProp)) && (ok || !reflect.DeepEqual(v, memberKeyProp)) { + obj["memberKey"] = memberKeyProp + } + preferredMemberKeyProp, err := expandCloudIdentityGroupMembershipPreferredMemberKey(d.Get("preferred_member_key"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("preferred_member_key"); !isEmptyValue(reflect.ValueOf(preferredMemberKeyProp)) && (ok || !reflect.DeepEqual(v, preferredMemberKeyProp)) { + obj["preferredMemberKey"] = preferredMemberKeyProp + } + rolesProp, err := expandCloudIdentityGroupMembershipRoles(d.Get("roles"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("roles"); !isEmptyValue(reflect.ValueOf(rolesProp)) && (ok || !reflect.DeepEqual(v, rolesProp)) { + obj["roles"] = rolesProp + } + + url, err := replaceVars(d, config, "{{CloudIdentityBasePath}}{{group}}/memberships") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new GroupMembership: %#v", obj) + res, err := sendRequestWithTimeout(config, "POST", "", url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating GroupMembership: %s", err) + } + if err := d.Set("name", flattenCloudIdentityGroupMembershipName(res["name"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "name": %s`, err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating GroupMembership %q: %#v", d.Id(), res) + + // `name` is autogenerated from the api so needs to be set post-create + name, ok := res["name"] + if !ok { + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + } + d.Set("name", name.(string)) + d.SetId(name.(string)) + + return resourceCloudIdentityGroupMembershipRead(d, meta) +} + +func resourceCloudIdentityGroupMembershipRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{CloudIdentityBasePath}}{{name}}") + if err != nil { + return err + } + + res, err := sendRequest(config, "GET", "", url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("CloudIdentityGroupMembership %q", d.Id())) + } + + if err := d.Set("name", flattenCloudIdentityGroupMembershipName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading GroupMembership: %s", err) + } + if err := d.Set("member_key", flattenCloudIdentityGroupMembershipMemberKey(res["memberKey"], d, config)); err != nil { + return fmt.Errorf("Error reading GroupMembership: %s", err) + } + if err := d.Set("preferred_member_key", flattenCloudIdentityGroupMembershipPreferredMemberKey(res["preferredMemberKey"], d, config)); err != nil { + return fmt.Errorf("Error reading GroupMembership: %s", err) + } + if err := d.Set("create_time", flattenCloudIdentityGroupMembershipCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading GroupMembership: %s", err) + } + if err := d.Set("update_time", flattenCloudIdentityGroupMembershipUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading GroupMembership: %s", err) + } + if err := d.Set("roles", flattenCloudIdentityGroupMembershipRoles(res["roles"], d, config)); err != nil { + return fmt.Errorf("Error reading GroupMembership: %s", err) + } + if err := d.Set("type", flattenCloudIdentityGroupMembershipType(res["type"], d, config)); err != nil { + return fmt.Errorf("Error reading GroupMembership: %s", err) + } + + return nil +} + +func resourceCloudIdentityGroupMembershipUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + memberKeyProp, err := expandCloudIdentityGroupMembershipMemberKey(d.Get("member_key"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("member_key"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, memberKeyProp)) { + obj["memberKey"] = memberKeyProp + } + preferredMemberKeyProp, err := expandCloudIdentityGroupMembershipPreferredMemberKey(d.Get("preferred_member_key"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("preferred_member_key"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, preferredMemberKeyProp)) { + obj["preferredMemberKey"] = preferredMemberKeyProp + } + rolesProp, err := expandCloudIdentityGroupMembershipRoles(d.Get("roles"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("roles"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, rolesProp)) { + obj["roles"] = rolesProp + } + + url, err := replaceVars(d, config, "{{CloudIdentityBasePath}}{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating GroupMembership %q: %#v", d.Id(), obj) + _, err = sendRequestWithTimeout(config, "PUT", "", url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating GroupMembership %q: %s", d.Id(), err) + } + + return resourceCloudIdentityGroupMembershipRead(d, meta) +} + +func resourceCloudIdentityGroupMembershipDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{CloudIdentityBasePath}}{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting GroupMembership %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", "", url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "GroupMembership") + } + + log.Printf("[DEBUG] Finished deleting GroupMembership %q: %#v", d.Id(), res) + return nil +} + +func resourceCloudIdentityGroupMembershipImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenCloudIdentityGroupMembershipName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupMembershipMemberKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["id"] = + flattenCloudIdentityGroupMembershipMemberKeyId(original["id"], d, config) + transformed["namespace"] = + flattenCloudIdentityGroupMembershipMemberKeyNamespace(original["namespace"], d, config) + return []interface{}{transformed} +} +func flattenCloudIdentityGroupMembershipMemberKeyId(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupMembershipMemberKeyNamespace(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupMembershipPreferredMemberKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["id"] = + flattenCloudIdentityGroupMembershipPreferredMemberKeyId(original["id"], d, config) + transformed["namespace"] = + flattenCloudIdentityGroupMembershipPreferredMemberKeyNamespace(original["namespace"], d, config) + return []interface{}{transformed} +} +func flattenCloudIdentityGroupMembershipPreferredMemberKeyId(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupMembershipPreferredMemberKeyNamespace(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupMembershipCreateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupMembershipUpdateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupMembershipRoles(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "name": flattenCloudIdentityGroupMembershipRolesName(original["name"], d, config), + }) + } + return transformed +} +func flattenCloudIdentityGroupMembershipRolesName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenCloudIdentityGroupMembershipType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandCloudIdentityGroupMembershipMemberKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedId, err := expandCloudIdentityGroupMembershipMemberKeyId(original["id"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedId); val.IsValid() && !isEmptyValue(val) { + transformed["id"] = transformedId + } + + transformedNamespace, err := expandCloudIdentityGroupMembershipMemberKeyNamespace(original["namespace"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespace); val.IsValid() && !isEmptyValue(val) { + transformed["namespace"] = transformedNamespace + } + + return transformed, nil +} + +func expandCloudIdentityGroupMembershipMemberKeyId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudIdentityGroupMembershipMemberKeyNamespace(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudIdentityGroupMembershipPreferredMemberKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedId, err := expandCloudIdentityGroupMembershipPreferredMemberKeyId(original["id"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedId); val.IsValid() && !isEmptyValue(val) { + transformed["id"] = transformedId + } + + transformedNamespace, err := expandCloudIdentityGroupMembershipPreferredMemberKeyNamespace(original["namespace"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespace); val.IsValid() && !isEmptyValue(val) { + transformed["namespace"] = transformedNamespace + } + + return transformed, nil +} + +func expandCloudIdentityGroupMembershipPreferredMemberKeyId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudIdentityGroupMembershipPreferredMemberKeyNamespace(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudIdentityGroupMembershipRoles(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedName, err := expandCloudIdentityGroupMembershipRolesName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) { + transformed["name"] = transformedName + } + + req = append(req, transformed) + } + return req, nil +} + +func expandCloudIdentityGroupMembershipRolesName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google-beta/resource_cloud_identity_group_membership_generated_test.go b/google-beta/resource_cloud_identity_group_membership_generated_test.go new file mode 100644 index 0000000000..f288484a02 --- /dev/null +++ b/google-beta/resource_cloud_identity_group_membership_generated_test.go @@ -0,0 +1,177 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccCloudIdentityGroupMembership_cloudIdentityGroupMembershipExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_domain": getTestOrgDomainFromEnv(t), + "cust_id": getTestCustIdFromEnv(t), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckCloudIdentityGroupMembershipDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudIdentityGroupMembership_cloudIdentityGroupMembershipExample(context), + }, + }, + }) +} + +func testAccCloudIdentityGroupMembership_cloudIdentityGroupMembershipExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_cloud_identity_group" "group" { + provider = google-beta + display_name = "tf-test-my-identity-group%{random_suffix}" + + parent = "customers/%{cust_id}" + + group_key { + id = "tf-test-my-identity-group%{random_suffix}@%{org_domain}" + } + + labels = { + "cloudidentity.googleapis.com/groups.discussion_forum" = "" + } +} + +resource "google_cloud_identity_group" "child-group" { + provider = google-beta + display_name = "tf-test-my-identity-group%{random_suffix}-child" + + parent = "customers/%{cust_id}" + + group_key { + id = "tf-test-my-identity-group%{random_suffix}-child@%{org_domain}" + } + + labels = { + "cloudidentity.googleapis.com/groups.discussion_forum" = "" + } +} + +resource "google_cloud_identity_group_membership" "cloud_identity_group_membership_basic" { + provider = google-beta + group = google_cloud_identity_group.group.id + + member_key { + id = google_cloud_identity_group.child-group.group_key[0].id + } + + roles { + name = "MEMBER" + } +} +`, context) +} + +func TestAccCloudIdentityGroupMembership_cloudIdentityGroupMembershipUserExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_domain": getTestOrgDomainFromEnv(t), + "cust_id": getTestCustIdFromEnv(t), + "identity_user": getTestIdentityUserFromEnv(t), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckCloudIdentityGroupMembershipDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudIdentityGroupMembership_cloudIdentityGroupMembershipUserExample(context), + }, + }, + }) +} + +func testAccCloudIdentityGroupMembership_cloudIdentityGroupMembershipUserExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_cloud_identity_group" "group" { + provider = google-beta + display_name = "tf-test-my-identity-group%{random_suffix}" + + parent = "customers/%{cust_id}" + + group_key { + id = "tf-test-my-identity-group%{random_suffix}@%{org_domain}" + } + + labels = { + "cloudidentity.googleapis.com/groups.discussion_forum" = "" + } +} + +resource "google_cloud_identity_group_membership" "cloud_identity_group_membership_basic" { + provider = google-beta + group = google_cloud_identity_group.group.id + + member_key { + id = "%{identity_user}@%{org_domain}" + } + + roles { + name = "MEMBER" + } + + roles { + name = "MANAGER" + } +} +`, context) +} + +func testAccCheckCloudIdentityGroupMembershipDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_cloud_identity_group_membership" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{CloudIdentityBasePath}}{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", "", url, nil) + if err == nil { + return fmt.Errorf("CloudIdentityGroupMembership still exists at %s", url) + } + } + + return nil + } +} diff --git a/google-beta/resource_cloud_identity_group_membership_sweeper_test.go b/google-beta/resource_cloud_identity_group_membership_sweeper_test.go new file mode 100644 index 0000000000..061e9463a4 --- /dev/null +++ b/google-beta/resource_cloud_identity_group_membership_sweeper_test.go @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func init() { + resource.AddTestSweepers("CloudIdentityGroupMembership", &resource.Sweeper{ + Name: "CloudIdentityGroupMembership", + F: testSweepCloudIdentityGroupMembership, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepCloudIdentityGroupMembership(region string) error { + resourceName := "CloudIdentityGroupMembership" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := getTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://cloudidentity.googleapis.com/v1beta1/{{group}}/memberships", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["groupMemberships"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://cloudidentity.googleapis.com/v1beta1/{{name}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/google-beta/resource_cloud_identity_group_sweeper_test.go b/google-beta/resource_cloud_identity_group_sweeper_test.go new file mode 100644 index 0000000000..a8c2f17270 --- /dev/null +++ b/google-beta/resource_cloud_identity_group_sweeper_test.go @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func init() { + resource.AddTestSweepers("CloudIdentityGroup", &resource.Sweeper{ + Name: "CloudIdentityGroup", + F: testSweepCloudIdentityGroup, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepCloudIdentityGroup(region string) error { + resourceName := "CloudIdentityGroup" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := getTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://cloudidentity.googleapis.com/v1beta1/groups", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["groups"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://cloudidentity.googleapis.com/v1beta1/{{name}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/google-beta/resource_cloud_identity_group_test.go b/google-beta/resource_cloud_identity_group_test.go new file mode 100644 index 0000000000..1fbd3fc328 --- /dev/null +++ b/google-beta/resource_cloud_identity_group_test.go @@ -0,0 +1,51 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccCloudIdentityGroup_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_domain": getTestOrgDomainFromEnv(t), + "cust_id": getTestCustIdFromEnv(t), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckCloudIdentityGroupDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudIdentityGroup_cloudIdentityGroupsBasicExample(context), + }, + { + Config: testAccCloudIdentityGroup_update(context), + }, + }, + }) +} + +func testAccCloudIdentityGroup_update(context map[string]interface{}) string { + return Nprintf(` +resource "google_cloud_identity_group" "cloud_identity_group_basic" { + provider = google-beta + display_name = "tf-test-my-identity-group%{random_suffix}-update" + description = "my-description" + + parent = "customers/%{cust_id}" + + group_key { + id = "tf-test-my-identity-group%{random_suffix}@%{org_domain}" + } + + labels = { + "cloudidentity.googleapis.com/groups.discussion_forum" = "" + } +} +`, context) +} diff --git a/google-beta/resource_dialogflow_entity_type.go b/google-beta/resource_dialogflow_entity_type.go index ffbb073f63..fe25d33476 100644 --- a/google-beta/resource_dialogflow_entity_type.go +++ b/google-beta/resource_dialogflow_entity_type.go @@ -168,7 +168,15 @@ func resourceDialogflowEntityTypeCreate(d *schema.ResourceData, meta interface{} // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_dialogflow_intent.go b/google-beta/resource_dialogflow_intent.go index b91c0c0eeb..006332b736 100644 --- a/google-beta/resource_dialogflow_intent.go +++ b/google-beta/resource_dialogflow_intent.go @@ -277,7 +277,15 @@ func resourceDialogflowIntentCreate(d *schema.ResourceData, meta interface{}) er // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_iap_brand.go b/google-beta/resource_iap_brand.go index 68cae00d76..14d54dc630 100644 --- a/google-beta/resource_iap_brand.go +++ b/google-beta/resource_iap_brand.go @@ -131,7 +131,15 @@ func resourceIapBrandCreate(d *schema.ResourceData, meta interface{}) error { // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_logging_metric.go b/google-beta/resource_logging_metric.go index 77f3aaaa88..d3803a957c 100644 --- a/google-beta/resource_logging_metric.go +++ b/google-beta/resource_logging_metric.go @@ -340,7 +340,15 @@ func resourceLoggingMetricCreate(d *schema.ResourceData, meta interface{}) error // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_monitoring_alert_policy.go b/google-beta/resource_monitoring_alert_policy.go index 60cd5a2721..0ad551b71d 100644 --- a/google-beta/resource_monitoring_alert_policy.go +++ b/google-beta/resource_monitoring_alert_policy.go @@ -805,7 +805,15 @@ func resourceMonitoringAlertPolicyCreate(d *schema.ResourceData, meta interface{ // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_monitoring_group.go b/google-beta/resource_monitoring_group.go index 1b3003b212..267fd12407 100644 --- a/google-beta/resource_monitoring_group.go +++ b/google-beta/resource_monitoring_group.go @@ -150,7 +150,15 @@ func resourceMonitoringGroupCreate(d *schema.ResourceData, meta interface{}) err // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_monitoring_notification_channel.go b/google-beta/resource_monitoring_notification_channel.go index 5288042722..0ce3f9435e 100644 --- a/google-beta/resource_monitoring_notification_channel.go +++ b/google-beta/resource_monitoring_notification_channel.go @@ -237,7 +237,15 @@ func resourceMonitoringNotificationChannelCreate(d *schema.ResourceData, meta in // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_monitoring_uptime_check_config.go b/google-beta/resource_monitoring_uptime_check_config.go index 2b882eba02..fdf3959926 100644 --- a/google-beta/resource_monitoring_uptime_check_config.go +++ b/google-beta/resource_monitoring_uptime_check_config.go @@ -392,7 +392,15 @@ func resourceMonitoringUptimeCheckConfigCreate(d *schema.ResourceData, meta inte // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_security_center_source.go b/google-beta/resource_security_center_source.go index 06a48f126d..57d622d945 100644 --- a/google-beta/resource_security_center_source.go +++ b/google-beta/resource_security_center_source.go @@ -119,7 +119,15 @@ func resourceSecurityCenterSourceCreate(d *schema.ResourceData, meta interface{} // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/google-beta/resource_security_scanner_scan_config.go b/google-beta/resource_security_scanner_scan_config.go index 65db3de5e8..fc4a3578c3 100644 --- a/google-beta/resource_security_scanner_scan_config.go +++ b/google-beta/resource_security_scanner_scan_config.go @@ -287,7 +287,15 @@ func resourceSecurityScannerScanConfigCreate(d *schema.ResourceData, meta interf // `name` is autogenerated from the api so needs to be set post-create name, ok := res["name"] if !ok { - return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + respBody, ok := res["response"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } + + name, ok = respBody.(map[string]interface{})["name"] + if !ok { + return fmt.Errorf("Create response didn't contain critical fields. Create may not have succeeded.") + } } d.Set("name", name.(string)) d.SetId(name.(string)) diff --git a/website/docs/r/cloud_identity_group.html.markdown b/website/docs/r/cloud_identity_group.html.markdown new file mode 100644 index 0000000000..c866166101 --- /dev/null +++ b/website/docs/r/cloud_identity_group.html.markdown @@ -0,0 +1,148 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Cloud Identity" +layout: "google" +page_title: "Google: google_cloud_identity_group" +sidebar_current: "docs-google-cloud-identity-group" +description: |- + A Cloud Identity resource representing a Group. +--- + +# google\_cloud\_identity\_group + +A Cloud Identity resource representing a Group. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + + + +## Example Usage - Cloud Identity Groups Basic + + +```hcl +resource "google_cloud_identity_group" "cloud_identity_group_basic" { + provider = google-beta + display_name = "my-identity-group" + + parent = "customers/A01b123xz" + + group_key { + id = "my-identity-group@example.com" + } + + labels = { + "cloudidentity.googleapis.com/groups.discussion_forum" = "" + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `group_key` - + (Required) + EntityKey of the Group. Structure is documented below. + +* `parent` - + (Required) + The resource name of the entity under which this Group resides in the + Cloud Identity resource hierarchy. + Must be of the form identitysources/{identity_source_id} for external-identity-mapped + groups or customers/{customer_id} for Google Groups. + +* `labels` - + (Required) + The labels that apply to the Group. + Must not contain more than one entry. Must contain the entry + 'cloudidentity.googleapis.com/groups.discussion_forum': '' if the Group is a Google Group or + 'system/groups/external': '' if the Group is an external-identity-mapped group. + + +The `group_key` block supports: + +* `id` - + (Required) + The ID of the entity. + For Google-managed entities, the id must be the email address of an existing + group or user. + For external-identity-mapped entities, the id must be a string conforming + to the Identity Source's requirements. + Must be unique within a namespace. + +* `namespace` - + (Optional) + The namespace in which the entity exists. + If not specified, the EntityKey represents a Google-managed entity + such as a Google user or a Google Group. + If specified, the EntityKey represents an external-identity-mapped group. + The namespace must correspond to an identity source created in Admin Console + and must be in the form of `identitysources/{identity_source_id}`. + +- - - + + +* `display_name` - + (Optional) + The display name of the Group. + +* `description` - + (Optional) + An extended description to help users determine the purpose of a Group. + Must not be longer than 4,096 characters. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `{{name}}` + +* `name` - + Resource name of the Group in the format: groups/{group_id}, where group_id + is the unique ID assigned to the Group. + +* `create_time` - + The time when the Group was created. + +* `update_time` - + The time when the Group was last updated. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +Group can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_cloud_identity_group.default {{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. diff --git a/website/docs/r/cloud_identity_group_membership.html.markdown b/website/docs/r/cloud_identity_group_membership.html.markdown new file mode 100644 index 0000000000..e94ae6dbe6 --- /dev/null +++ b/website/docs/r/cloud_identity_group_membership.html.markdown @@ -0,0 +1,240 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Cloud Identity" +layout: "google" +page_title: "Google: google_cloud_identity_group_membership" +sidebar_current: "docs-google-cloud-identity-group-membership" +description: |- + A Membership defines a relationship between a Group and an entity belonging to that Group, referred to as a "member". +--- + +# google\_cloud\_identity\_group\_membership + +A Membership defines a relationship between a Group and an entity belonging to that Group, referred to as a "member". + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + + + +## Example Usage - Cloud Identity Group Membership + + +```hcl +resource "google_cloud_identity_group" "group" { + provider = google-beta + display_name = "my-identity-group" + + parent = "customers/A01b123xz" + + group_key { + id = "my-identity-group@example.com" + } + + labels = { + "cloudidentity.googleapis.com/groups.discussion_forum" = "" + } +} + +resource "google_cloud_identity_group" "child-group" { + provider = google-beta + display_name = "my-identity-group-child" + + parent = "customers/A01b123xz" + + group_key { + id = "my-identity-group-child@example.com" + } + + labels = { + "cloudidentity.googleapis.com/groups.discussion_forum" = "" + } +} + +resource "google_cloud_identity_group_membership" "cloud_identity_group_membership_basic" { + provider = google-beta + group = google_cloud_identity_group.group.id + + member_key { + id = google_cloud_identity_group.child-group.group_key[0].id + } + + roles { + name = "MEMBER" + } +} +``` + +## Example Usage - Cloud Identity Group Membership User + + +```hcl +resource "google_cloud_identity_group" "group" { + provider = google-beta + display_name = "my-identity-group" + + parent = "customers/A01b123xz" + + group_key { + id = "my-identity-group@example.com" + } + + labels = { + "cloudidentity.googleapis.com/groups.discussion_forum" = "" + } +} + +resource "google_cloud_identity_group_membership" "cloud_identity_group_membership_basic" { + provider = google-beta + group = google_cloud_identity_group.group.id + + member_key { + id = "cloud_identity_user@example.com" + } + + roles { + name = "MEMBER" + } + + roles { + name = "MANAGER" + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `roles` - + (Required) + The MembershipRoles that apply to the Membership. + Must not contain duplicate MembershipRoles with the same name. Structure is documented below. + +* `group` - + (Required) + The name of the Group to create this membership in. + + +The `roles` block supports: + +* `name` - + (Required) + The name of the MembershipRole. Must be one of OWNER, MANAGER, MEMBER. + + Possible values are: + * `OWNER` + * `MANAGER` + * `MEMBER` + +- - - + + +* `member_key` - + (Optional) + EntityKey of the member. Structure is documented below. + +* `preferred_member_key` - + (Optional) + EntityKey of the member. Structure is documented below. + + +The `member_key` block supports: + +* `id` - + (Required) + The ID of the entity. + For Google-managed entities, the id must be the email address of an existing + group or user. + For external-identity-mapped entities, the id must be a string conforming + to the Identity Source's requirements. + Must be unique within a namespace. + +* `namespace` - + (Optional) + The namespace in which the entity exists. + If not specified, the EntityKey represents a Google-managed entity + such as a Google user or a Google Group. + If specified, the EntityKey represents an external-identity-mapped group. + The namespace must correspond to an identity source created in Admin Console + and must be in the form of `identitysources/{identity_source_id}`. + +The `preferred_member_key` block supports: + +* `id` - + (Required) + The ID of the entity. + For Google-managed entities, the id must be the email address of an existing + group or user. + For external-identity-mapped entities, the id must be a string conforming + to the Identity Source's requirements. + Must be unique within a namespace. + +* `namespace` - + (Optional) + The namespace in which the entity exists. + If not specified, the EntityKey represents a Google-managed entity + such as a Google user or a Google Group. + If specified, the EntityKey represents an external-identity-mapped group. + The namespace must correspond to an identity source created in Admin Console + and must be in the form of `identitysources/{identity_source_id}`. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `{{name}}` + +* `name` - + The resource name of the Membership, of the form groups/{group_id}/memberships/{membership_id}. + +* `create_time` - + The time when the Membership was created. + +* `update_time` - + The time when the Membership was last updated. + +* `type` - + The type of the membership. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +GroupMembership can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_cloud_identity_group_membership.default {{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. diff --git a/website/google.erb b/website/google.erb index bfac0d11de..0d81c0a769 100644 --- a/website/google.erb +++ b/website/google.erb @@ -642,6 +642,26 @@ +
  • + Cloud Identity + +
  • +
  • Cloud IoT Core