From 16571e0c35d7fb0d88ea58cd596917b9ed253709 Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Wed, 5 Oct 2022 16:19:15 +0000 Subject: [PATCH] Added custom_placement_config field in google_storage_bucket resource (#6619) Signed-off-by: Modular Magician --- .changelog/6619.txt | 3 + google-beta/resource_storage_bucket.go | 64 +++++++++++++++++++++ google-beta/resource_storage_bucket_test.go | 36 ++++++++++++ website/docs/r/storage_bucket.html.markdown | 6 ++ 4 files changed, 109 insertions(+) create mode 100644 .changelog/6619.txt diff --git a/.changelog/6619.txt b/.changelog/6619.txt new file mode 100644 index 0000000000..6322e7c24a --- /dev/null +++ b/.changelog/6619.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +storage: added `custom_placement_config` field to `google_storage_bucket` resource to support custom dual-region GCS buckets +``` diff --git a/google-beta/resource_storage_bucket.go b/google-beta/resource_storage_bucket.go index 4c3552f31b..5e767da3c1 100644 --- a/google-beta/resource_storage_bucket.go +++ b/google-beta/resource_storage_bucket.go @@ -365,6 +365,27 @@ func resourceStorageBucket() *schema.Resource { Computed: true, Description: `Enables uniform bucket-level access on a bucket.`, }, + "custom_placement_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "data_locations": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + MaxItems: 2, + MinItems: 2, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: `The list of individual regions that comprise a dual-region bucket. See the docs for a list of acceptable regions. Note: If any of the data_locations changes, it will recreate the bucket.`, + }, + }, + }, + Description: `The bucket's custom location configuration, which specifies the individual regions that comprise a dual-region bucket. If the bucket is designated a single or multi-region, the parameters are empty.`, + }, "public_access_prevention": { Type: schema.TypeString, Optional: true, @@ -488,6 +509,10 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error } } + if v, ok := d.GetOk("custom_placement_config"); ok { + sb.CustomPlacementConfig = expandBucketCustomPlacementConfig(v.([]interface{})) + } + var res *storage.Bucket err = retry(func() error { @@ -900,6 +925,42 @@ func flattenBucketEncryption(enc *storage.BucketEncryption) []map[string]interfa return encryption } +func expandBucketCustomPlacementConfig(configured interface{}) *storage.BucketCustomPlacementConfig { + cfcs := configured.([]interface{}) + if len(cfcs) == 0 || cfcs[0] == nil { + return nil + } + cfc := cfcs[0].(map[string]interface{}) + bucketcfc := &storage.BucketCustomPlacementConfig{ + DataLocations: expandBucketDataLocations(cfc["data_locations"]), + } + return bucketcfc +} + +func flattenBucketCustomPlacementConfig(cfc *storage.BucketCustomPlacementConfig) []map[string]interface{} { + customPlacementConfig := make([]map[string]interface{}, 0, 1) + + if cfc == nil { + return customPlacementConfig + } + + customPlacementConfig = append(customPlacementConfig, map[string]interface{}{ + "data_locations": cfc.DataLocations, + }) + + return customPlacementConfig +} + +func expandBucketDataLocations(configured interface{}) []string { + l := configured.(*schema.Set).List() + + req := make([]string, 0, len(l)) + for _, raw := range l { + req = append(req, raw.(string)) + } + return req +} + func expandBucketLogging(configured interface{}) *storage.BucketLogging { loggings := configured.([]interface{}) if len(loggings) == 0 { @@ -1440,6 +1501,9 @@ func setStorageBucket(d *schema.ResourceData, config *Config, res *storage.Bucke if err := d.Set("retention_policy", flattenBucketRetentionPolicy(res.RetentionPolicy)); err != nil { return fmt.Errorf("Error setting retention_policy: %s", err) } + if err := d.Set("custom_placement_config", flattenBucketCustomPlacementConfig(res.CustomPlacementConfig)); err != nil { + return fmt.Errorf("Error setting custom_placement_config: %s", err) + } if res.IamConfiguration != nil && res.IamConfiguration.UniformBucketLevelAccess != nil { if err := d.Set("uniform_bucket_level_access", res.IamConfiguration.UniformBucketLevelAccess.Enabled); err != nil { diff --git a/google-beta/resource_storage_bucket_test.go b/google-beta/resource_storage_bucket_test.go index 58ec66229d..51a0d58584 100644 --- a/google-beta/resource_storage_bucket_test.go +++ b/google-beta/resource_storage_bucket_test.go @@ -103,6 +103,29 @@ func TestAccStorageBucket_lowercaseLocation(t *testing.T) { }) } +func TestAccStorageBucket_dualLocation(t *testing.T) { + t.Parallel() + + bucketName := testBucketName(t) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_dualLocation(bucketName), + }, + { + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"force_destroy"}, + }, + }, + }) +} + func TestAccStorageBucket_customAttributes(t *testing.T) { t.Parallel() @@ -1309,6 +1332,19 @@ resource "google_storage_bucket" "bucket" { `, bucketName) } +func testAccStorageBucket_dualLocation(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + location = "ASIA" + force_destroy = true + custom_placement_config { + data_locations = ["ASIA-EAST1", "ASIA-SOUTHEAST1"] + } +} +`, bucketName) +} + func testAccStorageBucket_customAttributes(bucketName string) string { return fmt.Sprintf(` resource "google_storage_bucket" "bucket" { diff --git a/website/docs/r/storage_bucket.html.markdown b/website/docs/r/storage_bucket.html.markdown index 154b1312d2..46d34633d6 100644 --- a/website/docs/r/storage_bucket.html.markdown +++ b/website/docs/r/storage_bucket.html.markdown @@ -101,6 +101,8 @@ The following arguments are supported: * `uniform_bucket_level_access` - (Optional, Default: false) Enables [Uniform bucket-level access](https://cloud.google.com/storage/docs/uniform-bucket-level-access) access to a bucket. +* `custom_placement_config` - (Optional) The bucket's custom location configuration, which specifies the individual regions that comprise a dual-region bucket. If the bucket is designated a single or multi-region, the parameters are empty. Structure is [documented below](#nested_custom_placement_config). + The `lifecycle_rule` block supports: * `action` - (Required) The Lifecycle Rule's action configuration. A single block of this type is supported. Structure is [documented below](#nested_action). @@ -189,6 +191,10 @@ The following arguments are supported: state of the project. You should take care for race conditions when the same Terraform manages IAM policy on the Cloud KMS crypto key. See the data source page for more details. +The `custom_placement_config` block supports: + +* `data_locations` - (Required) The list of individual regions that comprise a dual-region bucket. See [Cloud Storage bucket locations](https://cloud.google.com/storage/docs/dual-regions#availability) for a list of acceptable regions. **Note**: If any of the data_locations changes, it will [recreate the bucket](https://cloud.google.com/storage/docs/locations#key-concepts). + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are