From aff37b3d2dea72fc048d08c455b072f3617f2d30 Mon Sep 17 00:00:00 2001 From: Guru Sai Rama Subbarao Voleti Date: Mon, 3 Feb 2025 05:45:06 +0000 Subject: [PATCH 1/6] fix: notification 412 error --- mmv1/products/storage/Bucket.yaml | 16 -- .../services/storage/iam_storage_bucket.go | 199 ++++++++++++++++++ 2 files changed, 199 insertions(+), 16 deletions(-) create mode 100644 mmv1/third_party/terraform/services/storage/iam_storage_bucket.go diff --git a/mmv1/products/storage/Bucket.yaml b/mmv1/products/storage/Bucket.yaml index d19113caeebf..f4bef03a27bc 100644 --- a/mmv1/products/storage/Bucket.yaml +++ b/mmv1/products/storage/Bucket.yaml @@ -35,22 +35,6 @@ timeouts: update_minutes: 20 delete_minutes: 20 collection_url_key: 'items' -iam_policy: - exclude_tgc: true - fetch_iam_policy_method: 'iam' - set_iam_policy_verb: 'PUT' - set_iam_policy_method: 'iam' - wrapped_policy_obj: false - allowed_iam_role: 'roles/storage.objectViewer' - admin_iam_role: 'roles/storage.admin' - parent_resource_attribute: 'bucket' - iam_conditions_request_type: 'QUERY_PARAM' - base_url: 'b/{{name}}' - example_config_body: 'templates/terraform/iam/iam_attributes.go.tmpl' - custom_diff_suppress: 'templates/terraform/iam/storage_bucket_diff_suppress.go.tmpl' - import_format: - - 'b/{{name}}' - - '{{name}}' custom_code: examples: - name: 'storage_bucket_basic' diff --git a/mmv1/third_party/terraform/services/storage/iam_storage_bucket.go b/mmv1/third_party/terraform/services/storage/iam_storage_bucket.go new file mode 100644 index 000000000000..e744b92eb256 --- /dev/null +++ b/mmv1/third_party/terraform/services/storage/iam_storage_bucket.go @@ -0,0 +1,199 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// 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 storage + +import ( + "fmt" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" + + "github.com/hashicorp/terraform-provider-google/google/tpgiamresource" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +var StorageBucketIamSchema = map[string]*schema.Schema{ + "bucket": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: StorageBucketDiffSuppress, + }, +} + +func StorageBucketDiffSuppress(_, old, new string, _ *schema.ResourceData) bool { + return tpgresource.CompareResourceNames("", old, new, nil) +} + +type StorageBucketIamUpdater struct { + bucket string + d tpgresource.TerraformResourceData + Config *transport_tpg.Config +} + +func StorageBucketIamUpdaterProducer(d tpgresource.TerraformResourceData, config *transport_tpg.Config) (tpgiamresource.ResourceIamUpdater, error) { + values := make(map[string]string) + + if v, ok := d.GetOk("bucket"); ok { + values["bucket"] = v.(string) + } + + // We may have gotten either a long or short name, so attempt to parse long name if possible + m, err := tpgresource.GetImportIdQualifiers([]string{"b/(?P[^/]+)", "(?P[^/]+)"}, d, config, d.Get("bucket").(string)) + if err != nil { + return nil, err + } + + for k, v := range m { + values[k] = v + } + + u := &StorageBucketIamUpdater{ + bucket: values["bucket"], + d: d, + Config: config, + } + + if err := d.Set("bucket", u.GetResourceId()); err != nil { + return nil, fmt.Errorf("Error setting bucket: %s", err) + } + + return u, nil +} + +func StorageBucketIdParseFunc(d *schema.ResourceData, config *transport_tpg.Config) error { + values := make(map[string]string) + + m, err := tpgresource.GetImportIdQualifiers([]string{"b/(?P[^/]+)", "(?P[^/]+)"}, d, config, d.Id()) + if err != nil { + return err + } + + for k, v := range m { + values[k] = v + } + + u := &StorageBucketIamUpdater{ + bucket: values["bucket"], + d: d, + Config: config, + } + if err := d.Set("bucket", u.GetResourceId()); err != nil { + return fmt.Errorf("Error setting bucket: %s", err) + } + d.SetId(u.GetResourceId()) + return nil +} + +func (u *StorageBucketIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + url, err := u.qualifyBucketUrl("iam") + if err != nil { + return nil, err + } + + var obj map[string]interface{} + url, err = transport_tpg.AddQueryParams(url, map[string]string{"optionsRequestedPolicyVersion": fmt.Sprintf("%d", tpgiamresource.IamPolicyVersion)}) + if err != nil { + return nil, err + } + + userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) + if err != nil { + return nil, err + } + + policy, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: u.Config, + Method: "GET", + RawURL: url, + UserAgent: userAgent, + Body: obj, + }) + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + out := &cloudresourcemanager.Policy{} + err = tpgresource.Convert(policy, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a policy to a resource manager policy: {{err}}", err) + } + + return out, nil +} + +func (u *StorageBucketIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + + if policy.Bindings == nil || len(policy.Bindings) == 0 { + policy.Etag = "" + } + + json, err := tpgresource.ConvertToMap(policy) + if err != nil { + return err + } + + obj := json + + url, err := u.qualifyBucketUrl("iam") + if err != nil { + return err + } + + userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) + if err != nil { + return err + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: u.Config, + Method: "PUT", + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: u.d.Timeout(schema.TimeoutCreate), + }) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *StorageBucketIamUpdater) qualifyBucketUrl(methodIdentifier string) (string, error) { + urlTemplate := fmt.Sprintf("{{StorageBasePath}}%s/%s", fmt.Sprintf("b/%s", u.bucket), methodIdentifier) + url, err := tpgresource.ReplaceVars(u.d, u.Config, urlTemplate) + if err != nil { + return "", err + } + return url, nil +} + +func (u *StorageBucketIamUpdater) GetResourceId() string { + return fmt.Sprintf("b/%s", u.bucket) +} + +func (u *StorageBucketIamUpdater) GetMutexKey() string { + return fmt.Sprintf("iam-storage-bucket-%s", u.GetResourceId()) +} + +func (u *StorageBucketIamUpdater) DescribeResource() string { + return fmt.Sprintf("storage bucket %q", u.GetResourceId()) +} From 34cfd16e77b58764a378b8e8e96b9620adeaa2aa Mon Sep 17 00:00:00 2001 From: Guru Sai Rama Subbarao Voleti Date: Mon, 3 Feb 2025 07:25:28 +0000 Subject: [PATCH 2/6] auto-generated to handwritten --- .../terraform/provider/provider_mmv1_resources.go.tmpl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl index 55a3ec96f49c..36fd26de9e3a 100644 --- a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl +++ b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl @@ -299,6 +299,7 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{ "google_project_iam_policy": tpgiamresource.DataSourceIamPolicy(resourcemanager.IamProjectSchema, resourcemanager.NewProjectIamUpdater), "google_pubsub_subscription_iam_policy": tpgiamresource.DataSourceIamPolicy(pubsub.IamPubsubSubscriptionSchema, pubsub.NewPubsubSubscriptionIamUpdater), "google_service_account_iam_policy": tpgiamresource.DataSourceIamPolicy(resourcemanager.IamServiceAccountSchema, resourcemanager.NewServiceAccountIamUpdater), + "google_storage_bucket_iam_policy": tpgiamresource.DataSourceIamPolicy(storage.StorageBucketIamSchema, storage.StorageBucketIamUpdaterProducer), // ####### END non-generated IAM datasources ########### } @@ -472,6 +473,9 @@ var handwrittenIAMResources = map[string]*schema.Resource{ "google_storage_managed_folder_iam_binding": tpgiamresource.ResourceIamBinding(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer, storage.StorageManagedFolderIdParseFunc), "google_storage_managed_folder_iam_member": tpgiamresource.ResourceIamMember(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer, storage.StorageManagedFolderIdParseFunc), "google_storage_managed_folder_iam_policy": tpgiamresource.ResourceIamPolicy(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer, storage.StorageManagedFolderIdParseFunc), + "google_storage_bucket_iam_binding": tpgiamresource.ResourceIamBinding(storage.StorageBucketIamSchema, storage.StorageBucketIamUpdaterProducer, storage.StorageBucketIdParseFunc), + "google_storage_bucket_iam_member": tpgiamresource.ResourceIamMember(storage.StorageBucketIamSchema, storage.StorageBucketIamUpdaterProducer, storage.StorageBucketIdParseFunc), + "google_storage_bucket_iam_policy": tpgiamresource.ResourceIamPolicy(storage.StorageBucketIamSchema, storage.StorageBucketIamUpdaterProducer, storage.StorageBucketIdParseFunc), "google_organization_iam_binding": tpgiamresource.ResourceIamBinding(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater, resourcemanager.OrgIdParseFunc), "google_organization_iam_member": tpgiamresource.ResourceIamMember(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater, resourcemanager.OrgIdParseFunc), "google_organization_iam_policy": tpgiamresource.ResourceIamPolicy(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater, resourcemanager.OrgIdParseFunc), From 5d4b1c13812a303bcf61183f19b02a4ebe30a0a9 Mon Sep 17 00:00:00 2001 From: gurusai-voleti Date: Tue, 4 Feb 2025 12:31:47 +0530 Subject: [PATCH 3/6] Update iam_storage_bucket.go --- .../services/storage/iam_storage_bucket.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/mmv1/third_party/terraform/services/storage/iam_storage_bucket.go b/mmv1/third_party/terraform/services/storage/iam_storage_bucket.go index e744b92eb256..74eb4abdc4af 100644 --- a/mmv1/third_party/terraform/services/storage/iam_storage_bucket.go +++ b/mmv1/third_party/terraform/services/storage/iam_storage_bucket.go @@ -1,20 +1,5 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 - -// ---------------------------------------------------------------------------- -// -// *** AUTO GENERATED CODE *** Type: MMv1 *** -// -// ---------------------------------------------------------------------------- -// -// 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 storage import ( From f5bf6a664c027834884714ce4ff79eef5763b45c Mon Sep 17 00:00:00 2001 From: Guru Sai Rama Subbarao Voleti Date: Tue, 4 Feb 2025 10:54:26 +0000 Subject: [PATCH 4/6] add test case --- .../storage/iam_storage_bucket_test.go | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go diff --git a/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go b/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go new file mode 100644 index 000000000000..96afabc4762f --- /dev/null +++ b/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go @@ -0,0 +1,84 @@ +package storage_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccStorageBucketIamPolicy_destroy(t *testing.T) { + t.Parallel() + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucketIamPolicy_destroy(), + }, + }, + }) +} + +func testAccStorageBucketIamPolicy_destroy() string { + return fmt.Sprintf(` +resource "google_service_account" "accessor" { + account_id = "pub-sub-test-service-account" +} + +resource "google_storage_bucket" "test_bucket" { + name = "sdk-pubsub-test-bucket" + location = "US" + storage_class = "STANDARD" + + uniform_bucket_level_access = true + public_access_prevention = "enforced" + + force_destroy = true +} + +data "google_iam_policy" "bucket_policy_data" { + binding { + role = "roles/storage.admin" + + members = ["serviceAccount:${google_service_account.accessor.email}"] + } +} + +resource "google_storage_bucket_iam_policy" "bucket_policy" { + bucket = google_storage_bucket.test_bucket.name + policy_data = data.google_iam_policy.bucket_policy_data.policy_data +} + +resource "google_pubsub_topic" "topic" { + name = "sdk-pubsub-test-bucket-topic" +} + +resource "google_storage_notification" "storage_notification" { + bucket = google_storage_bucket.test_bucket.name + payload_format = "JSON_API_V1" + topic = google_pubsub_topic.topic.id + + depends_on = [google_pubsub_topic_iam_policy.topic_policy] +} + +data "google_storage_project_service_account" "gcs_account" {} + +data "google_iam_policy" "topic_policy_data" { + binding { + role = "roles/pubsub.publisher" + members = [ + "serviceAccount:${data.google_storage_project_service_account.gcs_account.email_address}" + ] + } +} + +resource "google_pubsub_topic_iam_policy" "topic_policy" { + topic = google_pubsub_topic.topic.name + policy_data = data.google_iam_policy.topic_policy_data.policy_data +} +`) +} From e58d8694d4776b23efd02b808597d95cd100064e Mon Sep 17 00:00:00 2001 From: Guru Sai Rama Subbarao Voleti Date: Tue, 4 Feb 2025 11:05:13 +0000 Subject: [PATCH 5/6] add test case --- .../terraform/services/storage/iam_storage_bucket_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go b/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go index 96afabc4762f..c3e79f27e472 100644 --- a/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go +++ b/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go @@ -30,7 +30,7 @@ resource "google_service_account" "accessor" { } resource "google_storage_bucket" "test_bucket" { - name = "sdk-pubsub-test-bucket" + name = "sd-pubsub-test-bucket" location = "US" storage_class = "STANDARD" @@ -54,7 +54,7 @@ resource "google_storage_bucket_iam_policy" "bucket_policy" { } resource "google_pubsub_topic" "topic" { - name = "sdk-pubsub-test-bucket-topic" + name = "sd-pubsub-test-bucket-topic" } resource "google_storage_notification" "storage_notification" { From a3e2e868f084c74369fb59f10c772dffe1fd4e0e Mon Sep 17 00:00:00 2001 From: Guru Sai Rama Subbarao Voleti Date: Fri, 7 Feb 2025 07:36:35 +0000 Subject: [PATCH 6/6] added test cases and documentation handwritten --- .../storage/iam_storage_bucket_test.go | 578 ++++++++++++++++++ .../docs/r/storage_bucket_iam.html.markdown | 200 ++++++ 2 files changed, 778 insertions(+) create mode 100644 mmv1/third_party/terraform/website/docs/r/storage_bucket_iam.html.markdown diff --git a/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go b/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go index c3e79f27e472..f6412c64071c 100644 --- a/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go +++ b/mmv1/third_party/terraform/services/storage/iam_storage_bucket_test.go @@ -1,12 +1,17 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 package storage_test import ( "fmt" + "strings" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" ) func TestAccStorageBucketIamPolicy_destroy(t *testing.T) { @@ -82,3 +87,576 @@ resource "google_pubsub_topic_iam_policy" "topic_policy" { } `) } + +func TestAccStorageBucket_basicIamBinding(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_basicIamBinding(context), + }, + { + ResourceName: "google_storage_bucket_iam_binding.foo", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccStorageBucket_updatedIamBinding(context), + }, + { + ResourceName: "google_storage_bucket_iam_binding.foo", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_basicIamMemeber(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccStorageBucket_basicIamMember(context), + }, + { + ResourceName: "google_storage_bucket_iam_member.foo", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer user:admin@hashicorptest.com", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_basicIamPolicy(t *testing.T) { + t.Parallel() + + // This may skip test, so do it first + sa := envvar.GetTestServiceAccountFromEnv(t) + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + context["service_account"] = sa + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_basicIamPolicy(context), + Check: resource.TestCheckResourceAttrSet("data.google_storage_bucket_iam_policy.foo", "policy_data"), + }, + { + ResourceName: "google_storage_bucket_iam_policy.foo", + ImportStateId: fmt.Sprintf("b/%s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccStorageBucket_emptyBindingIamPolicy(context), + }, + { + ResourceName: "google_storage_bucket_iam_policy.foo", + ImportStateId: fmt.Sprintf("b/%s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_iamBindingWithCondition(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_withConditionIamBinding(context), + }, + { + ResourceName: "google_storage_bucket_iam_binding.foo", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_iamBindingWithAndWithoutCondition(t *testing.T) { + // Multiple fine-grained resources + acctest.SkipIfVcr(t) + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_withAndWithoutConditionIamBinding(context), + }, + { + ResourceName: "google_storage_bucket_iam_binding.foo", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_storage_bucket_iam_binding.foo2", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_storage_bucket_iam_binding.foo3", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title_no_desc"]), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_iamMemberWithCondition(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_withConditionIamMember(context), + }, + { + ResourceName: "google_storage_bucket_iam_member.foo", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer user:admin@hashicorptest.com %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_iamMemberWithAndWithoutCondition(t *testing.T) { + // Multiple fine-grained resources + acctest.SkipIfVcr(t) + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_withAndWithoutConditionIamMember(context), + }, + { + ResourceName: "google_storage_bucket_iam_member.foo", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer user:admin@hashicorptest.com", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_storage_bucket_iam_member.foo2", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer user:admin@hashicorptest.com %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_storage_bucket_iam_member.foo3", + ImportStateId: fmt.Sprintf("b/%s roles/storage.objectViewer user:admin@hashicorptest.com %s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"]), context["condition_title_no_desc"]), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_iamPolicyGeneratedWithCondition(t *testing.T) { + t.Parallel() + + // This may skip test, so do it first + sa := envvar.GetTestServiceAccountFromEnv(t) + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "role": "roles/storage.objectViewer", + "admin_role": "roles/storage.admin", + "condition_title": "expires_after_2019_12_31", + "condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + "condition_desc": "Expiring at midnight of 2019-12-31", + "condition_title_no_desc": "expires_after_2019_12_31-no-description", + "condition_expr_no_desc": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`, + } + context["service_account"] = sa + + // Test should have 3 bindings: one with a description and one without, and a third for an admin role. Any < chars are converted to a unicode character by the API. + expectedPolicyData := acctest.Nprintf(`{"bindings":[{"members":["serviceAccount:%{service_account}"],"role":"%{admin_role}"},{"condition":{"description":"%{condition_desc}","expression":"%{condition_expr}","title":"%{condition_title}"},"members":["user:admin@hashicorptest.com"],"role":"%{role}"},{"condition":{"expression":"%{condition_expr}","title":"%{condition_title}-no-description"},"members":["user:admin@hashicorptest.com"],"role":"%{role}"}]}`, context) + expectedPolicyData = strings.Replace(expectedPolicyData, "<", "\\u003c", -1) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_withConditionIamPolicy(context), + Check: resource.ComposeAggregateTestCheckFunc( + // TODO(SarahFrench) - uncomment once https://github.com/GoogleCloudPlatform/magic-modules/pull/6466 merged + // resource.TestCheckResourceAttr("data.google_iam_policy.foo", "policy_data", expectedPolicyData), + resource.TestCheckResourceAttr("google_storage_bucket_iam_policy.foo", "policy_data", expectedPolicyData), + resource.TestCheckResourceAttrWith("data.google_iam_policy.foo", "policy_data", tpgresource.CheckGoogleIamPolicy), + ), + }, + { + ResourceName: "google_storage_bucket_iam_policy.foo", + ImportStateId: fmt.Sprintf("b/%s", fmt.Sprintf("tf-test-my-bucket%s", context["random_suffix"])), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccStorageBucket_basicIamMember(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_member" "foo" { + bucket = google_storage_bucket.default.name + role = "%{role}" + member = "user:admin@hashicorptest.com" +} +`, context) +} + +func testAccStorageBucket_basicIamPolicy(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +data "google_iam_policy" "foo" { + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + } + binding { + role = "%{admin_role}" + members = ["serviceAccount:%{service_account}"] + } +} + +resource "google_storage_bucket_iam_policy" "foo" { + bucket = google_storage_bucket.default.name + policy_data = data.google_iam_policy.foo.policy_data +} + +data "google_storage_bucket_iam_policy" "foo" { + bucket = google_storage_bucket.default.name + depends_on = [ + google_storage_bucket_iam_policy.foo + ] +} +`, context) +} + +func testAccStorageBucket_emptyBindingIamPolicy(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +data "google_iam_policy" "foo" { +} + +resource "google_storage_bucket_iam_policy" "foo" { + bucket = google_storage_bucket.default.name + policy_data = data.google_iam_policy.foo.policy_data +} +`, context) +} + +func testAccStorageBucket_basicIamBinding(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_binding" "foo" { + bucket = google_storage_bucket.default.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] +} +`, context) +} + +func testAccStorageBucket_updatedIamBinding(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_binding" "foo" { + bucket = google_storage_bucket.default.name + role = "%{role}" + members = ["user:admin@hashicorptest.com", "user:gterraformtest1@gmail.com"] +} +`, context) +} + +func testAccStorageBucket_withConditionIamBinding(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_binding" "foo" { + bucket = google_storage_bucket.default.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} +`, context) +} + +func testAccStorageBucket_withAndWithoutConditionIamBinding(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_binding" "foo" { + bucket = google_storage_bucket.default.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] +} + +resource "google_storage_bucket_iam_binding" "foo2" { + bucket = google_storage_bucket.default.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} + +resource "google_storage_bucket_iam_binding" "foo3" { + bucket = google_storage_bucket.default.name + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + # Check that lack of description doesn't cause any issues + # Relates to issue : https://github.com/hashicorp/terraform-provider-google/issues/8701 + title = "%{condition_title_no_desc}" + expression = "%{condition_expr_no_desc}" + } +} +`, context) +} + +func testAccStorageBucket_withConditionIamMember(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_member" "foo" { + bucket = google_storage_bucket.default.name + role = "%{role}" + member = "user:admin@hashicorptest.com" + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} +`, context) +} + +func testAccStorageBucket_withAndWithoutConditionIamMember(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_member" "foo" { + bucket = google_storage_bucket.default.name + role = "%{role}" + member = "user:admin@hashicorptest.com" +} + +resource "google_storage_bucket_iam_member" "foo2" { + bucket = google_storage_bucket.default.name + role = "%{role}" + member = "user:admin@hashicorptest.com" + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} + +resource "google_storage_bucket_iam_member" "foo3" { + bucket = google_storage_bucket.default.name + role = "%{role}" + member = "user:admin@hashicorptest.com" + condition { + # Check that lack of description doesn't cause any issues + # Relates to issue : https://github.com/hashicorp/terraform-provider-google/issues/8701 + title = "%{condition_title_no_desc}" + expression = "%{condition_expr_no_desc}" + } +} +`, context) +} + +func testAccStorageBucket_withConditionIamPolicy(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_storage_bucket" "default" { + name = "tf-test-my-bucket%{random_suffix}" + location = "US" + uniform_bucket_level_access = true +} + +data "google_iam_policy" "foo" { + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + # Check that lack of description doesn't cause any issues + # Relates to issue : https://github.com/hashicorp/terraform-provider-google/issues/8701 + title = "%{condition_title_no_desc}" + expression = "%{condition_expr_no_desc}" + } + } + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } + } + binding { + role = "%{admin_role}" + members = ["serviceAccount:%{service_account}"] + } +} + +resource "google_storage_bucket_iam_policy" "foo" { + bucket = google_storage_bucket.default.name + policy_data = data.google_iam_policy.foo.policy_data +} +`, context) +} diff --git a/mmv1/third_party/terraform/website/docs/r/storage_bucket_iam.html.markdown b/mmv1/third_party/terraform/website/docs/r/storage_bucket_iam.html.markdown new file mode 100644 index 000000000000..a86ae68df360 --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/r/storage_bucket_iam.html.markdown @@ -0,0 +1,200 @@ +--- +subcategory: "Cloud Storage" +description: |- + Collection of resources to manage IAM policy for Cloud Storage Bucket +--- + +# IAM policy for Cloud Storage Bucket +Three different resources help you manage your IAM policy for Cloud Storage Bucket. Each of these resources serves a different use case: + +* `google_storage_bucket_iam_policy`: Authoritative. Sets the IAM policy for the bucket and replaces any existing policy already attached. +* `google_storage_bucket_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the bucket are preserved. +* `google_storage_bucket_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the bucket are preserved. + +A data source can be used to retrieve policy data in advent you do not need creation + +* `google_storage_bucket_iam_policy`: Retrieves the IAM policy for the bucket + +~> **Note:** `google_storage_bucket_iam_policy` **cannot** be used in conjunction with `google_storage_bucket_iam_binding` and `google_storage_bucket_iam_member` or they will fight over what your policy should be. + +~> **Note:** `google_storage_bucket_iam_binding` resources **can be** used in conjunction with `google_storage_bucket_iam_member` resources **only if** they do not grant privilege to the same role. + +~> **Note:** This resource supports IAM Conditions but they have some known limitations which can be found [here](https://cloud.google.com/iam/docs/conditions-overview#limitations). Please review this article if you are having issues with IAM Conditions. + + +## google_storage_bucket_iam_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/storage.admin" + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_storage_bucket_iam_policy" "policy" { + bucket = google_storage_bucket.default.name + policy_data = data.google_iam_policy.admin.policy_data +} +``` + +With IAM Conditions: + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/storage.admin" + members = [ + "user:jane@example.com", + ] + + condition { + title = "expires_after_2019_12_31" + description = "Expiring at midnight of 2019-12-31" + expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")" + } + } +} + +resource "google_storage_bucket_iam_policy" "policy" { + bucket = google_storage_bucket.default.name + policy_data = data.google_iam_policy.admin.policy_data +} +``` +## google_storage_bucket_iam_binding + +```hcl +resource "google_storage_bucket_iam_binding" "binding" { + bucket = google_storage_bucket.default.name + role = "roles/storage.admin" + members = [ + "user:jane@example.com", + ] +} +``` + +With IAM Conditions: + +```hcl +resource "google_storage_bucket_iam_binding" "binding" { + bucket = google_storage_bucket.default.name + role = "roles/storage.admin" + members = [ + "user:jane@example.com", + ] + + condition { + title = "expires_after_2019_12_31" + description = "Expiring at midnight of 2019-12-31" + expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")" + } +} +``` +## google_storage_bucket_iam_member + +```hcl +resource "google_storage_bucket_iam_member" "member" { + bucket = google_storage_bucket.default.name + role = "roles/storage.admin" + member = "user:jane@example.com" +} +``` + +With IAM Conditions: + +```hcl +resource "google_storage_bucket_iam_member" "member" { + bucket = google_storage_bucket.default.name + role = "roles/storage.admin" + member = "user:jane@example.com" + + condition { + title = "expires_after_2019_12_31" + description = "Expiring at midnight of 2019-12-31" + expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `bucket` - (Required) Used to find the parent resource to bind the IAM policy to + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. + * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + * **projectOwner:projectid**: Owners of the given project. For example, "projectOwner:my-example-project" + * **projectEditor:projectid**: Editors of the given project. For example, "projectEditor:my-example-project" + * **projectViewer:projectid**: Viewers of the given project. For example, "projectViewer:my-example-project" + +* `role` - (Required) The role that should be applied. Only one + `google_storage_bucket_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. + +* `policy_data` - (Required only by `google_storage_bucket_iam_policy`) The policy data generated by + a `google_iam_policy` data source. + +* `condition` - (Optional) An [IAM Condition](https://cloud.google.com/iam/docs/conditions-overview) for a given binding. + Structure is documented below. + +--- + +The `condition` block supports: + +* `expression` - (Required) Textual representation of an expression in Common Expression Language syntax. + +* `title` - (Required) A title for the expression, i.e. a short string describing its purpose. + +* `description` - (Optional) An optional description of the expression. This is a longer text which describes the expression, e.g. when hovered over it in a UI. + +~> **Warning:** Terraform considers the `role` and condition contents (`title`+`description`+`expression`) as the + identifier for the binding. This means that if any part of the condition is changed out-of-band, Terraform will + consider it to be an entirely different resource and will treat it as such. +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the IAM policy. + +## Import + +For all import syntaxes, the "resource in question" can take any of the following forms: + +* b/{{name}} +* {{name}} + +Any variables not passed in the import command will be taken from the provider configuration. + +Cloud Storage bucket IAM resources can be imported using the resource identifiers, role, and member. + +IAM member imports use space-delimited identifiers: the resource in question, the role, and the member identity, e.g. +``` +$ terraform import google_storage_bucket_iam_member.editor "b/{{bucket}} roles/storage.objectViewer user:jane@example.com" +``` + +IAM binding imports use space-delimited identifiers: the resource in question and the role, e.g. +``` +$ terraform import google_storage_bucket_iam_binding.editor "b/{{bucket}} roles/storage.objectViewer" +``` + +IAM policy imports use the identifier of the resource in question, e.g. +``` +$ terraform import google_storage_bucket_iam_policy.editor b/{{bucket}} +``` + +-> **Custom Roles** If you're importing a IAM resource with a custom role, make sure to use the + full name of the custom role, e.g. `[projects/my-project|organizations/my-org]/roles/my-custom-role`. + +## User Project Overrides + +This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override).