Merge pull request #5327 from Originate/s3-website-routing-rules
Added routing rules to s3 buckets
This commit is contained in:
commit
5b548e938c
|
@ -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)
|
||||||
|
|
|
@ -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" {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue