added routing rules to s3 buckets

This commit is contained in:
Drew Minnear 2016-02-25 10:19:23 -08:00
parent adc0a8b126
commit ef50479e8a
3 changed files with 173 additions and 1 deletions

View File

@ -102,9 +102,16 @@ func resourceAwsS3Bucket() *schema.Resource {
ConflictsWith: []string{ ConflictsWith: []string{
"website.0.index_document", "website.0.index_document",
"website.0.error_document", "website.0.error_document",
"website.0.routing_rules",
}, },
Optional: true, 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) websites = append(websites, w)
} }
if err := d.Set("website", websites); err != nil { 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) indexDocument := website["index_document"].(string)
errorDocument := website["error_document"].(string) errorDocument := website["error_document"].(string)
redirectAllRequestsTo := website["redirect_all_requests_to"].(string) redirectAllRequestsTo := website["redirect_all_requests_to"].(string)
routingRules := website["routing_rules"].(string)
if indexDocument == "" && redirectAllRequestsTo == "" { if indexDocument == "" && redirectAllRequestsTo == "" {
return fmt.Errorf("Must specify either index_document or redirect_all_requests_to.") 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{ putInput := &s3.PutBucketWebsiteInput{
Bucket: aws.String(bucket), Bucket: aws.String(bucket),
WebsiteConfiguration: websiteConfiguration, WebsiteConfiguration: websiteConfiguration,
@ -841,11 +865,52 @@ func resourceAwsS3BucketLoggingUpdate(s3conn *s3.S3, d *schema.ResourceData) err
return nil 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 { func normalizeJson(jsonString interface{}) string {
if jsonString == nil { if jsonString == nil {
return "" return ""
} }
j := make(map[string]interface{}) var j interface{}
err := json.Unmarshal([]byte(jsonString.(string)), &j) err := json.Unmarshal([]byte(jsonString.(string)), &j)
if err != nil { if err != nil {
return fmt.Sprintf("Error parsing JSON: %s", err) return fmt.Sprintf("Error parsing JSON: %s", err)

View File

@ -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 // Test TestAccAWSS3Bucket_shouldFailNotFound is designed to fail with a "plan
// not empty" error in Terraform, to check against regresssions. // not empty" error in Terraform, to check against regresssions.
// See https://github.com/hashicorp/terraform/pull/2925 // See https://github.com/hashicorp/terraform/pull/2925
@ -396,6 +441,7 @@ func testAccCheckAWSS3BucketPolicy(n string, policy string) resource.TestCheckFu
return nil return nil
} }
} }
func testAccCheckAWSS3BucketWebsite(n string, indexDoc string, errorDoc string, redirectProtocol string, redirectTo string) resource.TestCheckFunc { func testAccCheckAWSS3BucketWebsite(n string, indexDoc string, errorDoc string, redirectProtocol string, redirectTo string) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, _ := s.RootModule().Resources[n] 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 { func testAccCheckAWSS3BucketVersioning(n string, versioningStatus string) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, _ := s.RootModule().Resources[n] rs, _ := s.RootModule().Resources[n]
@ -478,6 +548,7 @@ func testAccCheckAWSS3BucketVersioning(n string, versioningStatus string) resour
return nil return nil
} }
} }
func testAccCheckAWSS3BucketCors(n string, corsRules []*s3.CORSRule) resource.TestCheckFunc { func testAccCheckAWSS3BucketCors(n string, corsRules []*s3.CORSRule) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, _ := s.RootModule().Resources[n] rs, _ := s.RootModule().Resources[n]
@ -610,6 +681,30 @@ resource "aws_s3_bucket" "bucket" {
`, randInt) `, 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 { func testAccAWSS3BucketConfigWithPolicy(randInt int) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "aws_s3_bucket" "bucket" { resource "aws_s3_bucket" "bucket" {

View File

@ -37,6 +37,16 @@ resource "aws_s3_bucket" "b" {
website { website {
index_document = "index.html" index_document = "index.html"
error_document = "error.html" error_document = "error.html"
routing_rules = <<EOF
[{
"Condition": {
"KeyPrefixEquals": "docs/"
},
"Redirect": {
"ReplaceKeyPrefixWith": "documents/"
}
}]
EOF
} }
} }
``` ```
@ -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. * `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. * `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. * `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: The `CORS` object supports the following: