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

Add ability to set canned ACL in aws_s3_bucket_object. #8091

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
46 changes: 46 additions & 0 deletions builtin/providers/aws/resource_aws_s3_bucket_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"log"
"os"
"sort"
"strings"

"github.com/hashicorp/terraform/helper/schema"
Expand All @@ -30,6 +31,13 @@ func resourceAwsS3BucketObject() *schema.Resource {
ForceNew: true,
},

"acl": &schema.Schema{
Type: schema.TypeString,
Default: "private",
Optional: true,
ValidateFunc: validateS3BucketObjectAclType,
},

"cache_control": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -101,6 +109,7 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro

bucket := d.Get("bucket").(string)
key := d.Get("key").(string)
acl := d.Get("acl").(string)
var body io.ReadSeeker

if v, ok := d.GetOk("source"); ok {
Expand Down Expand Up @@ -131,6 +140,7 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro
putInput := &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
ACL: aws.String(acl),
Body: body,
}

Expand Down Expand Up @@ -251,3 +261,39 @@ func resourceAwsS3BucketObjectDelete(d *schema.ResourceData, meta interface{}) e

return nil
}

func validateS3BucketObjectAclType(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)

cannedAcls := map[string]bool{
s3.ObjectCannedACLPrivate: true,
s3.ObjectCannedACLPublicRead: true,
s3.ObjectCannedACLPublicReadWrite: true,
s3.ObjectCannedACLAuthenticatedRead: true,
s3.ObjectCannedACLAwsExecRead: true,
s3.ObjectCannedACLBucketOwnerRead: true,
s3.ObjectCannedACLBucketOwnerFullControl: true,
}

sentenceJoin := func(m map[string]bool) string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, fmt.Sprintf("%q", k))
}
sort.Strings(keys)

length := len(keys)
words := make([]string, length)
copy(words, keys)

words[length-1] = fmt.Sprintf("or %s", words[length-1])
return strings.Join(words, ", ")
}

if _, ok := cannedAcls[value]; !ok {
errors = append(errors, fmt.Errorf(
"%q contains an invalid canned ACL type %q. Valid types are either %s",
k, value, sentenceJoin(cannedAcls)))
}
return
}
114 changes: 114 additions & 0 deletions builtin/providers/aws/resource_aws_s3_bucket_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"io/ioutil"
"os"
"reflect"
"sort"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
Expand Down Expand Up @@ -265,6 +267,104 @@ func TestAccAWSS3BucketObject_kms(t *testing.T) {
})
}

func TestAccAWSS3BucketObject_acl(t *testing.T) {
rInt := acctest.RandInt()
var obj s3.GetObjectOutput

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketObjectDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSS3BucketObjectConfig_acl(rInt, "private"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketObjectExists(
"aws_s3_bucket_object.object", &obj),
resource.TestCheckResourceAttr(
"aws_s3_bucket_object.object",
"acl",
"private"),
testAccCheckAWSS3BucketObjectAcl(
"aws_s3_bucket_object.object",
[]string{"FULL_CONTROL"}),
),
},
resource.TestStep{
Config: testAccAWSS3BucketObjectConfig_acl(rInt, "public-read"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketObjectExists(
"aws_s3_bucket_object.object",
&obj),
resource.TestCheckResourceAttr(
"aws_s3_bucket_object.object",
"acl",
"public-read"),
testAccCheckAWSS3BucketObjectAcl(
"aws_s3_bucket_object.object",
[]string{"FULL_CONTROL", "READ"}),
),
},
},
})
}

func TestResourceAWSS3BucketObjectAcl_validation(t *testing.T) {
_, errors := validateS3BucketObjectAclType("incorrect", "acl")
if len(errors) == 0 {
t.Fatalf("Expected to trigger a validation error")
}

var testCases = []struct {
Value string
ErrCount int
}{
{
Value: "public-read",
ErrCount: 0,
},
{
Value: "public-read-write",
ErrCount: 0,
},
}

for _, tc := range testCases {
_, errors := validateS3BucketObjectAclType(tc.Value, "acl")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected not to trigger a validation error")
}
}
}

func testAccCheckAWSS3BucketObjectAcl(n string, expectedPerms []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, _ := s.RootModule().Resources[n]
s3conn := testAccProvider.Meta().(*AWSClient).s3conn

out, err := s3conn.GetObjectAcl(&s3.GetObjectAclInput{
Bucket: aws.String(rs.Primary.Attributes["bucket"]),
Key: aws.String(rs.Primary.Attributes["key"]),
})

if err != nil {
return fmt.Errorf("GetObjectAcl error: %v", err)
}

var perms []string
for _, v := range out.Grants {
perms = append(perms, *v.Permission)
}
sort.Strings(perms)

if !reflect.DeepEqual(perms, expectedPerms) {
return fmt.Errorf("Expected ACL permissions to be %v, got %v", expectedPerms, perms)
}

return nil
}
}

func testAccAWSS3BucketObjectConfigSource(randInt int, source string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "object_bucket" {
Expand Down Expand Up @@ -358,3 +458,17 @@ resource "aws_s3_bucket_object" "object" {
}
`, randInt)
}

func testAccAWSS3BucketObjectConfig_acl(randInt int, acl string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "object_bucket" {
bucket = "tf-object-test-bucket-%d"
}
resource "aws_s3_bucket_object" "object" {
bucket = "${aws_s3_bucket.object_bucket.bucket}"
key = "test-key"
content = "some_bucket_content"
acl = "%s"
}
`, randInt, acl)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ The following arguments are supported:
* `key` - (Required) The name of the object once it is in the bucket.
* `source` - (Required) The path to the source file being uploaded to the bucket.
* `content` - (Required unless `source` given) The literal content being uploaded to the bucket.
* `acl` - (Optional) The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. Defaults to "private".
* `cache_control` - (Optional) Specifies caching behavior along the request/reply chain Read [w3c cache_control](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) for further details.
* `content_disposition` - (Optional) Specifies presentational information for the object. Read [wc3 content_disposition](http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1) for further information.
* `content_encoding` - (Optional) Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied to obtain the media-type referenced by the Content-Type header field. Read [w3c content encoding](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11) for further information.
* `content_language` - (Optional) The language the content is in e.g. en-US or en-GB.
* `content_type` - (Optional) A standard MIME type describing the format of the object data, e.g. application/octet-stream. All Valid MIME Types are valid for this input.
* `etag` - (Optional) Used to trigger updates. The only meaningful value is `${md5(file("path/to/file"))}`.
* `etag` - (Optional) Used to trigger updates. The only meaningful value is `${md5(file("path/to/file"))}`.
This attribute is not compatible with `kms_key_id`
* `kms_key_id` - (Optional) Specifies the AWS KMS Key ID to use for object encryption.
* `kms_key_id` - (Optional) Specifies the AWS KMS Key ID to use for object encryption.
This value is a fully qualified **ARN** of the KMS Key. If using `aws_kms_key`,
use the exported `arn` attribute:
`kms_key_id = "${aws_kms_key.foo.arn}"`
Expand Down