Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added routing rules to s3 buckets #5327

Merged
merged 1 commit into from
Feb 25, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion builtin/providers/aws/resource_aws_s3_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
},
},
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.")
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
95 changes: 95 additions & 0 deletions builtin/providers/aws/resource_aws_s3_bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down Expand Up @@ -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 = <<EOF
[{
"Condition": {
"KeyPrefixEquals": "docs/"
},
"Redirect": {
"ReplaceKeyPrefixWith": "documents/"
}
}]
EOF
}
}
`, randInt)
}

func testAccAWSS3BucketConfigWithPolicy(randInt int) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "bucket" {
Expand Down
12 changes: 12 additions & 0 deletions website/source/docs/providers/aws/r/s3_bucket.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ resource "aws_s3_bucket" "b" {
website {
index_document = "index.html"
error_document = "error.html"
routing_rules = <<EOF
[{
"Condition": {
"KeyPrefixEquals": "docs/"
},
"Redirect": {
"ReplaceKeyPrefixWith": "documents/"
}
}]
EOF
}
}
```
Expand Down Expand Up @@ -107,6 +117,8 @@ The `website` object supports the following:
* `index_document` - (Required, unless using `redirect_all_requests_to`) Amazon S3 returns this index document when requests are made to the root domain or any of the subfolders.
* `error_document` - (Optional) An absolute path to the document to return in case of a 4XX error.
* `redirect_all_requests_to` - (Optional) A hostname to redirect all website requests for this bucket to. Hostname can optionally be prefixed with a protocol (`http://` or `https://`) to use when redirecting requests. The default is the protocol that is used in the original request.
* `routing_rules` - (Optional) A json array containing [routing rules](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-websiteconfiguration-routingrules.html)
describing redirect behavior and when redirects are applied.

The `CORS` object supports the following:

Expand Down