From d7a2cf8c8d9dcf3f1e453159b1dcbccfba9b202c Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Sat, 2 May 2015 20:23:45 -0400 Subject: [PATCH] providers/aws: Add support for policy on S3 bucket --- .../providers/aws/resource_aws_s3_bucket.go | 47 +++++++++++-- .../aws/resource_aws_s3_bucket_test.go | 69 +++++++++++++++++++ .../providers/aws/r/s3_bucket.html.markdown | 1 + 3 files changed, 112 insertions(+), 5 deletions(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket.go b/builtin/providers/aws/resource_aws_s3_bucket.go index 170f88970c11..2804ae8dd424 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket.go +++ b/builtin/providers/aws/resource_aws_s3_bucket.go @@ -31,6 +31,11 @@ func resourceAwsS3Bucket() *schema.Resource { ForceNew: true, }, + "policy": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "website": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -121,8 +126,14 @@ func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error { return err } - if err := resourceAwsS3BucketWebsiteUpdate(s3conn, d); err != nil { - return err + if d.HasChange("website") { + if err := resourceAwsS3BucketPolicyUpdate(s3conn, d); err != nil { + return err + } + + if err := resourceAwsS3BucketWebsiteUpdate(s3conn, d); err != nil { + return err + } } return resourceAwsS3BucketRead(d, meta) @@ -228,11 +239,37 @@ func resourceAwsS3BucketDelete(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceAwsS3BucketWebsiteUpdate(s3conn *s3.S3, d *schema.ResourceData) error { - if !d.HasChange("website") { - return nil +func resourceAwsS3BucketPolicyUpdate(s3conn *s3.S3, d *schema.ResourceData) error { + bucket := d.Get("bucket").(string) + policy := d.Get("policy").(string) + + if policy != "" { + input := &s3.PutBucketPolicyInput{ + Bucket: aws.String(bucket), + Policy: aws.String(policy), + } + + log.Printf("[DEBUG] S3 put bucket policy: %s", input) + + _, err := s3conn.PutBucketPolicy(input) + if err != nil { + return fmt.Errorf("Error putting S3 policy: %s", err) + } + } else { + input := &s3.DeleteBucketPolicyInput{ + Bucket: aws.String(bucket), + } + + _, err := s3conn.DeleteBucketPolicy(input) + if err != nil { + return fmt.Errorf("Error deleting S3 policy: %s", err) + } } + return nil +} + +func resourceAwsS3BucketWebsiteUpdate(s3conn *s3.S3, d *schema.ResourceData) error { ws := d.Get("website").([]interface{}) if len(ws) == 1 { diff --git a/builtin/providers/aws/resource_aws_s3_bucket_test.go b/builtin/providers/aws/resource_aws_s3_bucket_test.go index 051ab5474b51..b95b88d9ef09 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_test.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_test.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "math/rand" + "strconv" "testing" "time" @@ -35,6 +36,32 @@ func TestAccAWSS3Bucket_basic(t *testing.T) { }) } +func TestAccAWSS3Bucket_Policy(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketConfigWithPolicy, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"), + testAccCheckAWSS3BucketPolicy( + "aws_s3_bucket.bucket", testAccAWSS3BucketPolicy), + ), + }, + resource.TestStep{ + Config: testAccAWSS3BucketConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"), + testAccCheckAWSS3BucketPolicy( + "aws_s3_bucket.bucket", ""), + ), + }, + }, + }) +} + func TestAccAWSS3Bucket_Website(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -145,6 +172,38 @@ func testAccCheckAWSS3BucketExists(n string) resource.TestCheckFunc { } } +func testAccCheckAWSS3BucketPolicy(n string, policy string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, _ := s.RootModule().Resources[n] + conn := testAccProvider.Meta().(*AWSClient).s3conn + + out, err := conn.GetBucketPolicy(&s3.GetBucketPolicyInput{ + Bucket: aws.String(rs.Primary.ID), + }) + + if err != nil { + if policy == "" { + // If we want to assert that the policy is not there, than + // this error is expected + return nil + } else { + return fmt.Errorf("GetBucketPolicy error: %v", err) + } + } + + if v := out.Policy; v == nil { + if policy != "" { + return fmt.Errorf("bad policy, found nil, expected: %s", policy) + } + } else { + if *v != policy { + return fmt.Errorf("bad policy, expected: %s, got %#v", policy, v) + } + } + + return nil + } +} func testAccCheckAWSS3BucketWebsite(n string, indexDoc string, errorDoc string, redirectTo string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, _ := s.RootModule().Resources[n] @@ -202,6 +261,8 @@ func testAccCheckAWSS3BucketWebsite(n string, indexDoc string, errorDoc string, // within AWS var randInt = rand.New(rand.NewSource(time.Now().UnixNano())).Int() var testAccWebsiteEndpoint = fmt.Sprintf("tf-test-bucket-%d.s3-website-us-west-2.amazonaws.com", randInt) +var testAccAWSS3BucketPolicy = fmt.Sprintf(`{ "Version": "2008-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::tf-test-bucket-%d/*" } ] }`, randInt) + var testAccAWSS3BucketConfig = fmt.Sprintf(` resource "aws_s3_bucket" "bucket" { bucket = "tf-test-bucket-%d" @@ -242,3 +303,11 @@ resource "aws_s3_bucket" "bucket" { } } `, randInt) + +var testAccAWSS3BucketConfigWithPolicy = fmt.Sprintf(` +resource "aws_s3_bucket" "bucket" { + bucket = "tf-test-bucket-%d" + acl = "public-read" + policy = %s +} +`, randInt, strconv.Quote(testAccAWSS3BucketPolicy)) diff --git a/website/source/docs/providers/aws/r/s3_bucket.html.markdown b/website/source/docs/providers/aws/r/s3_bucket.html.markdown index 78e0938f828e..30f6d333b238 100644 --- a/website/source/docs/providers/aws/r/s3_bucket.html.markdown +++ b/website/source/docs/providers/aws/r/s3_bucket.html.markdown @@ -46,6 +46,7 @@ The following arguments are supported: * `bucket` - (Required) The name of the bucket. * `acl` - (Optional) The [canned ACL](http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. Defaults to "private". +* `policy` - (Optional) A valid [bucket policy](http://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html) JSON document. * `tags` - (Optional) A mapping of tags to assign to the bucket. * `website` - (Optional) A website object (documented below).