Add ability to set canned ACL in aws_s3_bucket_object. (#8091)
An S3 Bucket owner may wish to set a canned ACL (as opposite to explicitly set grantees, etc.) for an object. This commit adds an optional "acl" attribute to the aws_s3_bucket_object resource so that the owner of the S3 bucket can specify an appropriate pre-defined ACL to use when creating an object. Signed-off-by: Krzysztof Wilczynski <krzysztof.wilczynski@linux.com>
This commit is contained in:
parent
a82f96f939
commit
f5b46b80e7
|
@ -6,6 +6,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
@ -30,6 +31,13 @@ func resourceAwsS3BucketObject() *schema.Resource {
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"acl": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Default: "private",
|
||||||
|
Optional: true,
|
||||||
|
ValidateFunc: validateS3BucketObjectAclType,
|
||||||
|
},
|
||||||
|
|
||||||
"cache_control": &schema.Schema{
|
"cache_control": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -101,6 +109,7 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro
|
||||||
|
|
||||||
bucket := d.Get("bucket").(string)
|
bucket := d.Get("bucket").(string)
|
||||||
key := d.Get("key").(string)
|
key := d.Get("key").(string)
|
||||||
|
acl := d.Get("acl").(string)
|
||||||
var body io.ReadSeeker
|
var body io.ReadSeeker
|
||||||
|
|
||||||
if v, ok := d.GetOk("source"); ok {
|
if v, ok := d.GetOk("source"); ok {
|
||||||
|
@ -131,6 +140,7 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro
|
||||||
putInput := &s3.PutObjectInput{
|
putInput := &s3.PutObjectInput{
|
||||||
Bucket: aws.String(bucket),
|
Bucket: aws.String(bucket),
|
||||||
Key: aws.String(key),
|
Key: aws.String(key),
|
||||||
|
ACL: aws.String(acl),
|
||||||
Body: body,
|
Body: body,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,3 +261,39 @@ func resourceAwsS3BucketObjectDelete(d *schema.ResourceData, meta interface{}) e
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/acctest"
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
@ -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 {
|
func testAccAWSS3BucketObjectConfigSource(randInt int, source string) string {
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
resource "aws_s3_bucket" "object_bucket" {
|
resource "aws_s3_bucket" "object_bucket" {
|
||||||
|
@ -358,3 +458,17 @@ resource "aws_s3_bucket_object" "object" {
|
||||||
}
|
}
|
||||||
`, randInt)
|
`, 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)
|
||||||
|
}
|
||||||
|
|
|
@ -52,14 +52,15 @@ The following arguments are supported:
|
||||||
* `key` - (Required) The name of the object once it is in the bucket.
|
* `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.
|
* `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.
|
* `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.
|
* `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_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_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_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.
|
* `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`
|
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`,
|
This value is a fully qualified **ARN** of the KMS Key. If using `aws_kms_key`,
|
||||||
use the exported `arn` attribute:
|
use the exported `arn` attribute:
|
||||||
`kms_key_id = "${aws_kms_key.foo.arn}"`
|
`kms_key_id = "${aws_kms_key.foo.arn}"`
|
||||||
|
|
Loading…
Reference in New Issue