From ad76fb1111d3eba5fc1377ad97fa3c2a85edcd1c Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Fri, 19 May 2017 16:58:48 -0700 Subject: [PATCH 1/4] Add CORS support for google_storage_bucket. --- .../google/resource_storage_bucket.go | 81 +++++++++++++++++++ .../google/resource_storage_bucket_test.go | 33 ++++++++ .../google/r/storage_bucket.html.markdown | 18 ++++- 3 files changed, 129 insertions(+), 3 deletions(-) diff --git a/builtin/providers/google/resource_storage_bucket.go b/builtin/providers/google/resource_storage_bucket.go index 2640a1cc451f..19b8d952ae77 100644 --- a/builtin/providers/google/resource_storage_bucket.go +++ b/builtin/providers/google/resource_storage_bucket.go @@ -89,6 +89,40 @@ func resourceStorageBucket() *schema.Resource { }, }, }, + + "cors": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "origin": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "method": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "response_header": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "max_age_seconds": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, }, } } @@ -132,6 +166,10 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error } } + if v, ok := d.GetOk("cors"); ok { + sb.Cors = expandCors(v.(*schema.Set).List()) + } + var res *storage.Bucket err = resource.Retry(1*time.Minute, func() *resource.RetryError { @@ -197,6 +235,10 @@ func resourceStorageBucketUpdate(d *schema.ResourceData, meta interface{}) error } } + if v, ok := d.GetOk("cors"); ok { + sb.Cors = expandCors(v.(*schema.Set).List()) + } + res, err := config.clientStorage.Buckets.Patch(d.Get("name").(string), sb).Do() if err != nil { @@ -230,6 +272,7 @@ func resourceStorageBucketRead(d *schema.ResourceData, meta interface{}) error { d.Set("url", fmt.Sprintf("gs://%s", bucket)) d.Set("storage_class", res.StorageClass) d.Set("location", res.Location) + d.Set("cors", flattenCors(res.Cors)) d.SetId(res.Id) return nil } @@ -295,3 +338,41 @@ func resourceStorageBucketStateImporter(d *schema.ResourceData, meta interface{} d.Set("name", d.Id()) return []*schema.ResourceData{d}, nil } + +func expandCors(configured []interface{}) []*storage.BucketCors { + corsRules := make([]*storage.BucketCors, 0, len(configured)) + for _, raw := range configured { + data := raw.(map[string]interface{}) + corsRule := storage.BucketCors{} + + corsRule.Origin = convertSchemaArrayToStringArray(data["origin"].([]interface{})) + corsRule.Method = convertSchemaArrayToStringArray(data["method"].([]interface{})) + corsRule.ResponseHeader = convertSchemaArrayToStringArray(data["response_header"].([]interface{})) + + corsRule.MaxAgeSeconds = int64(data["max_age_seconds"].(int)) + corsRules = append(corsRules, &corsRule) + } + return corsRules +} + +func convertSchemaArrayToStringArray(input []interface{}) []string { + output := make([]string, 0, len(input)) + for _, val := range input { + output = append(output, val.(string)) + } + + return output +} + +func flattenCors(corsRules []*storage.BucketCors) []map[string]interface{} { + corsRulesSchema := make([]map[string]interface{}, 0, len(corsRules)) + for _, corsRule := range corsRules { + data := make(map[string]interface{}) + data["origin"] = corsRule.Origin + data["method"] = corsRule.Method + data["response_header"] = corsRule.ResponseHeader + data["max_age_seconds"] = corsRule.MaxAgeSeconds + corsRulesSchema = append(corsRulesSchema, data) + } + return corsRulesSchema +} diff --git a/builtin/providers/google/resource_storage_bucket_test.go b/builtin/providers/google/resource_storage_bucket_test.go index b40cabdedd3b..2f41e83f7409 100644 --- a/builtin/providers/google/resource_storage_bucket_test.go +++ b/builtin/providers/google/resource_storage_bucket_test.go @@ -188,6 +188,25 @@ func TestAccStorageForceDestroy(t *testing.T) { }) } +func TestAccStorage_cors(t *testing.T) { + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsCors(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStorageBucketExists( + "google_storage_bucket.bucket", bucketName), + ), + }, + }, + }) +} + func testAccCheckCloudStorageBucketExists(n string, bucketName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -302,3 +321,17 @@ resource "google_storage_bucket" "bucket" { } `, bucketName, storageClass, locationBlock) } + +func testGoogleStorageBucketsCors(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + cors { + origin = ["abc", "def"] + method = ["a1a"] + response_header = ["123", "456", "789"] + max_age_seconds = 10 + } +} +`, bucketName) +} diff --git a/website/source/docs/providers/google/r/storage_bucket.html.markdown b/website/source/docs/providers/google/r/storage_bucket.html.markdown index 62ee40366d12..4584df40fb9f 100644 --- a/website/source/docs/providers/google/r/storage_bucket.html.markdown +++ b/website/source/docs/providers/google/r/storage_bucket.html.markdown @@ -8,7 +8,7 @@ description: |- # google\_storage\_bucket -Creates a new bucket in Google cloud storage service(GCS). Currently, it will not change location nor ACL once a bucket has been created with Terraform. For more information see [the official documentation](https://cloud.google.com/storage/docs/overview) and [API](https://cloud.google.com/storage/docs/json_api). +Creates a new bucket in Google cloud storage service(GCS). Currently, it will not change location nor ACL once a bucket has been created with Terraform. For more information see [the official documentation](https://cloud.google.com/storage/docs/overview) and [API](https://cloud.google.com/storage/docs/json_api/v1/buckets). ## Example Usage @@ -50,15 +50,27 @@ to `google_storage_bucket_acl.predefined_acl`. * `storage_class` - (Optional) The [Storage Class](https://cloud.google.com/storage/docs/storage-classes) of the new bucket. Supported values include: `MULTI_REGIONAL`, `REGIONAL`, `NEARLINE`, `COLDLINE`. -* `website` - (Optional) Configuration if the bucket acts as a website. +* `website` - (Optional) Configuration if the bucket acts as a website. Structure is documented below. -The optional `website` block supports: +* `cors` - (Optional) The bucket's [Cross-Origin Resource Sharing (CORS)](https://www.w3.org/TR/cors/) configuration. Multiple blocks of this type are permitted. Structure is documented below. + +The `website` block supports: * `main_page_suffix` - (Optional) Behaves as the bucket's directory index where missing objects are treated as potential directories. * `not_found_page` - (Optional) The custom object to return when a requested resource is not found. + +The `cors` block supports: + +* `origin` - (Optional) The list of [Origins](https://tools.ietf.org/html/rfc6454) eligible to receive CORS response headers. Note: "*" is permitted in the list of origins, and means "any Origin". + +* `method` - (Optional) The list of HTTP methods on which to include CORS response headers, (GET, OPTIONS, POST, etc) Note: "*" is permitted in the list of methods, and means "any method". + +* `response_header` - (Optional) The list of HTTP headers other than the [simple response headers](https://www.w3.org/TR/cors/#simple-response-header) to give permission for the user-agent to share across domains. + +* `max_age_seconds` - (Optional) The value, in seconds, to return in the [Access-Control-Max-Age header](https://www.w3.org/TR/cors/#access-control-max-age-response-header) used in preflight responses. ## Attributes Reference From f0777862a74d14a59db6116174645c31149e5392 Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Mon, 22 May 2017 08:19:45 -0700 Subject: [PATCH 2/4] Ran make fmt on resource_storage_bucket.go --- builtin/providers/google/resource_storage_bucket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/google/resource_storage_bucket.go b/builtin/providers/google/resource_storage_bucket.go index 19b8d952ae77..512ed407053e 100644 --- a/builtin/providers/google/resource_storage_bucket.go +++ b/builtin/providers/google/resource_storage_bucket.go @@ -89,7 +89,7 @@ func resourceStorageBucket() *schema.Resource { }, }, }, - + "cors": &schema.Schema{ Type: schema.TypeSet, Optional: true, From 34be0e2747180aef1096de64aadd5cb3fbd9191a Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Mon, 22 May 2017 16:13:48 -0700 Subject: [PATCH 3/4] Tested CORS block contents, formatting changes. --- .../google/resource_storage_bucket.go | 6 +-- .../google/resource_storage_bucket_test.go | 52 ++++++++++++++++++- .../google/r/storage_bucket.html.markdown | 4 +- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/builtin/providers/google/resource_storage_bucket.go b/builtin/providers/google/resource_storage_bucket.go index 512ed407053e..7f2941e9f1ab 100644 --- a/builtin/providers/google/resource_storage_bucket.go +++ b/builtin/providers/google/resource_storage_bucket.go @@ -91,7 +91,7 @@ func resourceStorageBucket() *schema.Resource { }, "cors": &schema.Schema{ - Type: schema.TypeSet, + Type: schema.TypeList, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -167,7 +167,7 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error } if v, ok := d.GetOk("cors"); ok { - sb.Cors = expandCors(v.(*schema.Set).List()) + sb.Cors = expandCors(v.([]interface{})) } var res *storage.Bucket @@ -236,7 +236,7 @@ func resourceStorageBucketUpdate(d *schema.ResourceData, meta interface{}) error } if v, ok := d.GetOk("cors"); ok { - sb.Cors = expandCors(v.(*schema.Set).List()) + sb.Cors = expandCors(v.([]interface{})) } res, err := config.clientStorage.Buckets.Patch(d.Get("name").(string), sb).Do() diff --git a/builtin/providers/google/resource_storage_bucket_test.go b/builtin/providers/google/resource_storage_bucket_test.go index 87184c25de10..cc05180443f1 100644 --- a/builtin/providers/google/resource_storage_bucket_test.go +++ b/builtin/providers/google/resource_storage_bucket_test.go @@ -191,6 +191,56 @@ func TestAccStorageBucket_cors(t *testing.T) { }, }, }) + + if len(bucket.Cors) != 2 { + t.Errorf("Expected # of cors elements to be 2, got %d", len(bucket.Cors)) + } + + firstArr := bucket.Cors[0] + if firstArr.MaxAgeSeconds != 10 { + t.Errorf("Expected first block's MaxAgeSeconds to be 10, got %d", firstArr.MaxAgeSeconds) + } + + for i, v := range []string{"abc", "def"} { + if firstArr.Origin[i] != v { + t.Errorf("Expected value in first block origin to be to be %v, got %v", v, firstArr.Origin[i]) + } + } + + for i, v := range []string{"a1a"} { + if firstArr.Method[i] != v { + t.Errorf("Expected value in first block method to be to be %v, got %v", v, firstArr.Method[i]) + } + } + + for i, v := range []string{"123", "456", "789"} { + if firstArr.ResponseHeader[i] != v { + t.Errorf("Expected value in first block response headerto be to be %v, got %v", v, firstArr.ResponseHeader[i]) + } + } + + secondArr := bucket.Cors[1] + if secondArr.MaxAgeSeconds != 5 { + t.Errorf("Expected second block's MaxAgeSeconds to be 5, got %d", secondArr.MaxAgeSeconds) + } + + for i, v := range []string{"ghi", "jkl"} { + if secondArr.Origin[i] != v { + t.Errorf("Expected value in second block origin to be to be %v, got %v", v, secondArr.Origin[i]) + } + } + + for i, v := range []string{"z9z"} { + if secondArr.Method[i] != v { + t.Errorf("Expected value in second block method to be to be %v, got %v", v, secondArr.Method[i]) + } + } + + for i, v := range []string{"000"} { + if secondArr.ResponseHeader[i] != v { + t.Errorf("Expected value in second block response headerto be to be %v, got %v", v, secondArr.ResponseHeader[i]) + } + } } func testAccCheckStorageBucketExists(n string, bucketName string, bucket *storage.Bucket) resource.TestCheckFunc { @@ -324,7 +374,7 @@ resource "google_storage_bucket" "bucket" { cors { origin = ["ghi", "jkl"] method = ["z9z"] - response_header = ["789"] + response_header = ["000"] max_age_seconds = 5 } } diff --git a/website/source/docs/providers/google/r/storage_bucket.html.markdown b/website/source/docs/providers/google/r/storage_bucket.html.markdown index 4584df40fb9f..a85ddc8c1af6 100644 --- a/website/source/docs/providers/google/r/storage_bucket.html.markdown +++ b/website/source/docs/providers/google/r/storage_bucket.html.markdown @@ -68,9 +68,9 @@ The `cors` block supports: * `method` - (Optional) The list of HTTP methods on which to include CORS response headers, (GET, OPTIONS, POST, etc) Note: "*" is permitted in the list of methods, and means "any method". -* `response_header` - (Optional) The list of HTTP headers other than the [simple response headers](https://www.w3.org/TR/cors/#simple-response-header) to give permission for the user-agent to share across domains. +* `response_header` - (Optional) The list of HTTP headers other than the [simple response headers](https://www.w3.org/TR/cors/#simple-response-header) to give permission for the user-agent to share across domains. -* `max_age_seconds` - (Optional) The value, in seconds, to return in the [Access-Control-Max-Age header](https://www.w3.org/TR/cors/#access-control-max-age-response-header) used in preflight responses. +* `max_age_seconds` - (Optional) The value, in seconds, to return in the [Access-Control-Max-Age header](https://www.w3.org/TR/cors/#access-control-max-age-response-header) used in preflight responses. ## Attributes Reference From be06e17bab6bbe736608fcf0a62fe33bc926e12a Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Thu, 25 May 2017 14:55:17 -0700 Subject: [PATCH 4/4] Changed structs and maps to be created inline. --- .../google/resource_storage_bucket.go | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/builtin/providers/google/resource_storage_bucket.go b/builtin/providers/google/resource_storage_bucket.go index 7f2941e9f1ab..b60b76acb996 100644 --- a/builtin/providers/google/resource_storage_bucket.go +++ b/builtin/providers/google/resource_storage_bucket.go @@ -343,13 +343,13 @@ func expandCors(configured []interface{}) []*storage.BucketCors { corsRules := make([]*storage.BucketCors, 0, len(configured)) for _, raw := range configured { data := raw.(map[string]interface{}) - corsRule := storage.BucketCors{} - - corsRule.Origin = convertSchemaArrayToStringArray(data["origin"].([]interface{})) - corsRule.Method = convertSchemaArrayToStringArray(data["method"].([]interface{})) - corsRule.ResponseHeader = convertSchemaArrayToStringArray(data["response_header"].([]interface{})) + corsRule := storage.BucketCors{ + Origin: convertSchemaArrayToStringArray(data["origin"].([]interface{})), + Method: convertSchemaArrayToStringArray(data["method"].([]interface{})), + ResponseHeader: convertSchemaArrayToStringArray(data["response_header"].([]interface{})), + MaxAgeSeconds: int64(data["max_age_seconds"].(int)), + } - corsRule.MaxAgeSeconds = int64(data["max_age_seconds"].(int)) corsRules = append(corsRules, &corsRule) } return corsRules @@ -367,11 +367,13 @@ func convertSchemaArrayToStringArray(input []interface{}) []string { func flattenCors(corsRules []*storage.BucketCors) []map[string]interface{} { corsRulesSchema := make([]map[string]interface{}, 0, len(corsRules)) for _, corsRule := range corsRules { - data := make(map[string]interface{}) - data["origin"] = corsRule.Origin - data["method"] = corsRule.Method - data["response_header"] = corsRule.ResponseHeader - data["max_age_seconds"] = corsRule.MaxAgeSeconds + data := map[string]interface{}{ + "origin": corsRule.Origin, + "method": corsRule.Method, + "response_header": corsRule.ResponseHeader, + "max_age_seconds": corsRule.MaxAgeSeconds, + } + corsRulesSchema = append(corsRulesSchema, data) } return corsRulesSchema