From 0633131e282f4a1f3dff87e79b2c5bb77d43e50f Mon Sep 17 00:00:00 2001 From: The Magician Date: Wed, 5 Oct 2022 09:19:39 -0700 Subject: [PATCH] Added custom_placement_config field in google_storage_bucket resource (#6619) (#12723) Signed-off-by: Modular Magician Signed-off-by: Modular Magician --- .changelog/6619.txt | 3 + google/resource_storage_bucket.go | 64 +++++++++++++++++++++ google/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 00000000000..6322e7c24ad --- /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/resource_storage_bucket.go b/google/resource_storage_bucket.go index d8cb3211145..eb37c15052c 100644 --- a/google/resource_storage_bucket.go +++ b/google/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.`, + }, }, UseJSONNumber: true, } @@ -482,6 +503,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 { @@ -894,6 +919,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 { @@ -1430,6 +1491,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/resource_storage_bucket_test.go b/google/resource_storage_bucket_test.go index 48526eae188..ba63fb90c98 100644 --- a/google/resource_storage_bucket_test.go +++ b/google/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() @@ -1287,6 +1310,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 154b1312d2a..46d34633d69 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