Skip to content

Commit

Permalink
Merge pull request #8615 from hashicorp/f-aws-s3-bucket-policy
Browse files Browse the repository at this point in the history
provider/aws: Add aws_s3_bucket_policy resource
  • Loading branch information
jen20 authored Sep 2, 2016
2 parents 4e7edb5 + 93f31fc commit bb3a896
Show file tree
Hide file tree
Showing 10 changed files with 695 additions and 6 deletions.
15 changes: 15 additions & 0 deletions builtin/providers/aws/diff_suppress_funcs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package aws

import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/jen20/awspolicyequivalence"
)

func suppressEquivalentAwsPolicyDiffs(k, old, new string, d *schema.ResourceData) bool {
equivalent, err := awspolicy.PoliciesAreEquivalent(old, new)
if err != nil {
return false
}

return equivalent
}
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ func Provider() terraform.ResourceProvider {
"aws_ses_receipt_rule": resourceAwsSesReceiptRule(),
"aws_ses_receipt_rule_set": resourceAwsSesReceiptRuleSet(),
"aws_s3_bucket": resourceAwsS3Bucket(),
"aws_s3_bucket_policy": resourceAwsS3BucketPolicy(),
"aws_s3_bucket_object": resourceAwsS3BucketObject(),
"aws_s3_bucket_notification": resourceAwsS3BucketNotification(),
"aws_security_group": resourceAwsSecurityGroup(),
Expand Down
12 changes: 6 additions & 6 deletions builtin/providers/aws/resource_aws_s3_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import (
"net/url"
"time"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsS3Bucket() *schema.Resource {
Expand Down Expand Up @@ -47,9 +46,10 @@ func resourceAwsS3Bucket() *schema.Resource {
},

"policy": &schema.Schema{
Type: schema.TypeString,
Optional: true,
StateFunc: normalizeJson,
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs,
},

"cors_rule": &schema.Schema{
Expand Down
106 changes: 106 additions & 0 deletions builtin/providers/aws/resource_aws_s3_bucket_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package aws

import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsS3BucketPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceAwsS3BucketPolicyPut,
Read: resourceAwsS3BucketPolicyRead,
Update: resourceAwsS3BucketPolicyPut,
Delete: resourceAwsS3BucketPolicyDelete,

Schema: map[string]*schema.Schema{
"bucket": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"policy": {
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs,
},
},
}
}

func resourceAwsS3BucketPolicyPut(d *schema.ResourceData, meta interface{}) error {
s3conn := meta.(*AWSClient).s3conn

bucket := d.Get("bucket").(string)
policy := d.Get("policy").(string)

d.SetId(bucket)

log.Printf("[DEBUG] S3 bucket: %s, put policy: %s", bucket, policy)

params := &s3.PutBucketPolicyInput{
Bucket: aws.String(bucket),
Policy: aws.String(policy),
}

err := resource.Retry(1*time.Minute, func() *resource.RetryError {
if _, err := s3conn.PutBucketPolicy(params); err != nil {
if awserr, ok := err.(awserr.Error); ok {
if awserr.Code() == "MalformedPolicy" {
return resource.RetryableError(awserr)
}
}
return resource.NonRetryableError(err)
}
return nil
})

if err != nil {
return fmt.Errorf("Error putting S3 policy: %s", err)
}

return nil
}

func resourceAwsS3BucketPolicyRead(d *schema.ResourceData, meta interface{}) error {
s3conn := meta.(*AWSClient).s3conn

log.Printf("[DEBUG] S3 bucket policy, read for bucket: %s", d.Id())
pol, err := s3conn.GetBucketPolicy(&s3.GetBucketPolicyInput{
Bucket: aws.String(d.Id()),
})

v := ""
if err == nil && pol.Policy != nil {
v = *pol.Policy
}
if err := d.Set("policy", v); err != nil {
return err
}

return nil
}

func resourceAwsS3BucketPolicyDelete(d *schema.ResourceData, meta interface{}) error {
s3conn := meta.(*AWSClient).s3conn

bucket := d.Get("bucket").(string)

log.Printf("[DEBUG] S3 bucket: %s, delete policy", bucket)
_, err := s3conn.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{
Bucket: aws.String(bucket),
})

if err != nil {
return fmt.Errorf("Error deleting S3 policy: %s", err)
}

return nil
}
180 changes: 180 additions & 0 deletions builtin/providers/aws/resource_aws_s3_bucket_policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package aws

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/jen20/awspolicyequivalence"
)

func TestAccAWSS3BucketPolicy_basic(t *testing.T) {
name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())

expectedPolicyText := fmt.Sprintf(
`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"*"},"Action":"s3:*","Resource":["arn:aws:s3:::%s","arn:aws:s3:::%s/*"]}]}`,
name, name)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSS3BucketPolicyConfig(name),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"),
testAccCheckAWSS3BucketHasPolicy("aws_s3_bucket.bucket", expectedPolicyText),
),
},
},
})
}

func TestAccAWSS3BucketPolicy_policyUpdate(t *testing.T) {
name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())

expectedPolicyText1 := fmt.Sprintf(
`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"*"},"Action":"s3:*","Resource":["arn:aws:s3:::%s","arn:aws:s3:::%s/*"]}]}`,
name, name)

expectedPolicyText2 := fmt.Sprintf(
`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"*"},"Action":["s3:DeleteBucket", "s3:ListBucket", "s3:ListBucketVersions"], "Resource":["arn:aws:s3:::%s","arn:aws:s3:::%s/*"]}]}`,
name, name)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSS3BucketPolicyConfig(name),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"),
testAccCheckAWSS3BucketHasPolicy("aws_s3_bucket.bucket", expectedPolicyText1),
),
},

{
Config: testAccAWSS3BucketPolicyConfig_updated(name),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"),
testAccCheckAWSS3BucketHasPolicy("aws_s3_bucket.bucket", expectedPolicyText2),
),
},
},
})
}

func testAccCheckAWSS3BucketHasPolicy(n string, expectedPolicyText string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No S3 Bucket ID is set")
}

conn := testAccProvider.Meta().(*AWSClient).s3conn

policy, err := conn.GetBucketPolicy(&s3.GetBucketPolicyInput{
Bucket: aws.String(rs.Primary.ID),
})
if err != nil {
return fmt.Errorf("GetBucketPolicy error: %v", err)
}

actualPolicyText := *policy.Policy

equivalent, err := awspolicy.PoliciesAreEquivalent(actualPolicyText, expectedPolicyText)
if err != nil {
return fmt.Errorf("Error testing policy equivalence: %s", err)
}
if !equivalent {
return fmt.Errorf("Non-equivalent policy error:\n\nexpected: %s\n\n got: %s\n",
expectedPolicyText, actualPolicyText)
}

return nil
}
}

func testAccAWSS3BucketPolicyConfig(bucketName string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "bucket" {
bucket = "%s"
tags {
TestName = "TestAccAWSS3BucketPolicy_basic"
}
}
resource "aws_s3_bucket_policy" "bucket" {
bucket = "${aws_s3_bucket.bucket.bucket}"
policy = "${data.aws_iam_policy_document.policy.json}"
}
data "aws_iam_policy_document" "policy" {
statement {
effect = "Allow"
actions = [
"s3:*",
]
resources = [
"${aws_s3_bucket.bucket.arn}",
"${aws_s3_bucket.bucket.arn}/*",
]
principals {
type = "AWS"
identifiers = ["*"]
}
}
}
`, bucketName)
}

func testAccAWSS3BucketPolicyConfig_updated(bucketName string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "bucket" {
bucket = "%s"
tags {
TestName = "TestAccAWSS3BucketPolicy_basic"
}
}
resource "aws_s3_bucket_policy" "bucket" {
bucket = "${aws_s3_bucket.bucket.bucket}"
policy = "${data.aws_iam_policy_document.policy.json}"
}
data "aws_iam_policy_document" "policy" {
statement {
effect = "Allow"
actions = [
"s3:DeleteBucket",
"s3:ListBucket",
"s3:ListBucketVersions"
]
resources = [
"${aws_s3_bucket.bucket.arn}",
"${aws_s3_bucket.bucket.arn}/*",
]
principals {
type = "AWS"
identifiers = ["*"]
}
}
}
`, bucketName)
}
7 changes: 7 additions & 0 deletions vendor/github.com/jen20/awspolicyequivalence/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bb3a896

Please sign in to comment.