From ef50479e8a5dc25a926988e97ccdb73e997ad87f Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Thu, 25 Feb 2016 10:19:23 -0800 Subject: [PATCH] added routing rules to s3 buckets --- .../providers/aws/resource_aws_s3_bucket.go | 67 ++++++++++++- .../aws/resource_aws_s3_bucket_test.go | 95 +++++++++++++++++++ .../providers/aws/r/s3_bucket.html.markdown | 12 +++ 3 files changed, 173 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket.go b/builtin/providers/aws/resource_aws_s3_bucket.go index d1ace0a49..6bd9c451f 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket.go +++ b/builtin/providers/aws/resource_aws_s3_bucket.go @@ -102,9 +102,16 @@ func resourceAwsS3Bucket() *schema.Resource { ConflictsWith: []string{ "website.0.index_document", "website.0.error_document", + "website.0.routing_rules", }, Optional: true, }, + + "routing_rules": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + StateFunc: normalizeJson, + }, }, }, }, @@ -367,6 +374,14 @@ func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error { } } + if v := ws.RoutingRules; v != nil { + rr, err := normalizeRoutingRules(v) + if err != nil { + return fmt.Errorf("Error while marshaling routing rules: %s", err) + } + w["routing_rules"] = rr + } + websites = append(websites, w) } if err := d.Set("website", websites); err != nil { @@ -660,6 +675,7 @@ func resourceAwsS3BucketWebsitePut(s3conn *s3.S3, d *schema.ResourceData, websit indexDocument := website["index_document"].(string) errorDocument := website["error_document"].(string) redirectAllRequestsTo := website["redirect_all_requests_to"].(string) + routingRules := website["routing_rules"].(string) if indexDocument == "" && redirectAllRequestsTo == "" { return fmt.Errorf("Must specify either index_document or redirect_all_requests_to.") @@ -684,6 +700,14 @@ func resourceAwsS3BucketWebsitePut(s3conn *s3.S3, d *schema.ResourceData, websit } } + if routingRules != "" { + var unmarshaledRules []*s3.RoutingRule + if err := json.Unmarshal([]byte(routingRules), &unmarshaledRules); err != nil { + return err + } + websiteConfiguration.RoutingRules = unmarshaledRules + } + putInput := &s3.PutBucketWebsiteInput{ Bucket: aws.String(bucket), WebsiteConfiguration: websiteConfiguration, @@ -841,11 +865,52 @@ func resourceAwsS3BucketLoggingUpdate(s3conn *s3.S3, d *schema.ResourceData) err return nil } +func normalizeRoutingRules(w []*s3.RoutingRule) (string, error) { + withNulls, err := json.Marshal(w) + if err != nil { + return "", err + } + + var rules []map[string]interface{} + json.Unmarshal(withNulls, &rules) + + var cleanRules []map[string]interface{} + for _, rule := range rules { + cleanRules = append(cleanRules, removeNil(rule)) + } + + withoutNulls, err := json.Marshal(cleanRules) + if err != nil { + return "", err + } + + return string(withoutNulls), nil +} + +func removeNil(data map[string]interface{}) map[string]interface{} { + withoutNil := make(map[string]interface{}) + + for k, v := range data { + if v == nil { + continue + } + + switch v.(type) { + case map[string]interface{}: + withoutNil[k] = removeNil(v.(map[string]interface{})) + default: + withoutNil[k] = v + } + } + + return withoutNil +} + func normalizeJson(jsonString interface{}) string { if jsonString == nil { return "" } - j := make(map[string]interface{}) + var j interface{} err := json.Unmarshal([]byte(jsonString.(string)), &j) if err != nil { return fmt.Sprintf("Error parsing JSON: %s", err) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_test.go b/builtin/providers/aws/resource_aws_s3_bucket_test.go index 8a591c4bc..d306f5437 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_test.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_test.go @@ -185,6 +185,51 @@ func TestAccAWSS3Bucket_WebsiteRedirect(t *testing.T) { }) } +func TestAccAWSS3Bucket_WebsiteRoutingRules(t *testing.T) { + rInt := acctest.RandInt() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketWebsiteConfigWithRoutingRules(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"), + testAccCheckAWSS3BucketWebsite( + "aws_s3_bucket.bucket", "index.html", "error.html", "", ""), + testAccCheckAWSS3BucketWebsiteRoutingRules( + "aws_s3_bucket.bucket", + []*s3.RoutingRule{ + &s3.RoutingRule{ + Condition: &s3.Condition{ + KeyPrefixEquals: aws.String("docs/"), + }, + Redirect: &s3.Redirect{ + ReplaceKeyPrefixWith: aws.String("documents/"), + }, + }, + }, + ), + resource.TestCheckResourceAttr( + "aws_s3_bucket.bucket", "website_endpoint", testAccWebsiteEndpoint(rInt)), + ), + }, + resource.TestStep{ + Config: testAccAWSS3BucketConfig(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"), + testAccCheckAWSS3BucketWebsite( + "aws_s3_bucket.bucket", "", "", "", ""), + testAccCheckAWSS3BucketWebsiteRoutingRules("aws_s3_bucket.bucket", nil), + resource.TestCheckResourceAttr( + "aws_s3_bucket.bucket", "website_endpoint", ""), + ), + }, + }, + }) +} + // Test TestAccAWSS3Bucket_shouldFailNotFound is designed to fail with a "plan // not empty" error in Terraform, to check against regresssions. // See https://github.com/hashicorp/terraform/pull/2925 @@ -396,6 +441,7 @@ func testAccCheckAWSS3BucketPolicy(n string, policy string) resource.TestCheckFu return nil } } + func testAccCheckAWSS3BucketWebsite(n string, indexDoc string, errorDoc string, redirectProtocol string, redirectTo string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, _ := s.RootModule().Resources[n] @@ -452,6 +498,30 @@ func testAccCheckAWSS3BucketWebsite(n string, indexDoc string, errorDoc string, } } +func testAccCheckAWSS3BucketWebsiteRoutingRules(n string, routingRules []*s3.RoutingRule) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, _ := s.RootModule().Resources[n] + conn := testAccProvider.Meta().(*AWSClient).s3conn + + out, err := conn.GetBucketWebsite(&s3.GetBucketWebsiteInput{ + Bucket: aws.String(rs.Primary.ID), + }) + + if err != nil { + if routingRules == nil { + return nil + } + return fmt.Errorf("GetBucketWebsite error: %v", err) + } + + if !reflect.DeepEqual(out.RoutingRules, routingRules) { + return fmt.Errorf("bad routing rule, expected: %v, got %v", routingRules, out.RoutingRules) + } + + return nil + } +} + func testAccCheckAWSS3BucketVersioning(n string, versioningStatus string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, _ := s.RootModule().Resources[n] @@ -478,6 +548,7 @@ func testAccCheckAWSS3BucketVersioning(n string, versioningStatus string) resour return nil } } + func testAccCheckAWSS3BucketCors(n string, corsRules []*s3.CORSRule) resource.TestCheckFunc { return func(s *terraform.State) error { rs, _ := s.RootModule().Resources[n] @@ -610,6 +681,30 @@ resource "aws_s3_bucket" "bucket" { `, randInt) } +func testAccAWSS3BucketWebsiteConfigWithRoutingRules(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "bucket" { + bucket = "tf-test-bucket-%d" + acl = "public-read" + + website { + index_document = "index.html" + error_document = "error.html" + routing_rules = <