From 70f045e46e40235f669dd7397788ce21db7b51ae Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 23 Feb 2016 12:13:19 +0000 Subject: [PATCH 1/7] provider/aws: Breaking change - Trim off quotes for aws_s3_bucket_object.etag --- builtin/providers/aws/resource_aws_s3_bucket_object.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_object.go b/builtin/providers/aws/resource_aws_s3_bucket_object.go index ca10cf4cb..8cb7d277f 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_object.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_object.go @@ -6,6 +6,7 @@ import ( "io" "log" "os" + "strings" "github.com/hashicorp/terraform/helper/schema" "github.com/mitchellh/go-homedir" @@ -144,7 +145,9 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error putting object in S3 bucket (%s): %s", bucket, err) } - d.Set("etag", resp.ETag) + // See https://forums.aws.amazon.com/thread.jspa?threadID=44003 + d.Set("etag", strings.Trim(*resp.ETag, `"`)) + d.SetId(key) return nil } From ebf2fd54e5981d98b2fd988074485b65a693e1c3 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 23 Feb 2016 12:14:33 +0000 Subject: [PATCH 2/7] provider/aws: Clean up whitespaces --- builtin/providers/aws/resource_aws_s3_bucket_object.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_object.go b/builtin/providers/aws/resource_aws_s3_bucket_object.go index 8cb7d277f..11ddc7b7b 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_object.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_object.go @@ -111,9 +111,9 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro content := v.(string) body = bytes.NewReader([]byte(content)) } else { - return fmt.Errorf("Must specify \"source\" or \"content\" field") } + putInput := &s3.PutObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), From 2f8b9edaf832f1a65f633710eeef2b861118815c Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 23 Feb 2016 12:16:10 +0000 Subject: [PATCH 3/7] provider/aws: Add support for s3_bucket_object updates --- .../aws/resource_aws_s3_bucket_object.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_object.go b/builtin/providers/aws/resource_aws_s3_bucket_object.go index 11ddc7b7b..ae1214ab6 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_object.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_object.go @@ -20,6 +20,7 @@ func resourceAwsS3BucketObject() *schema.Resource { return &schema.Resource{ Create: resourceAwsS3BucketObjectPut, Read: resourceAwsS3BucketObjectRead, + Update: resourceAwsS3BucketObjectPut, Delete: resourceAwsS3BucketObjectDelete, Schema: map[string]*schema.Schema{ @@ -32,31 +33,26 @@ func resourceAwsS3BucketObject() *schema.Resource { "cache_control": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, }, "content_disposition": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, }, "content_encoding": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, }, "content_language": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, }, "content_type": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, Computed: true, }, @@ -69,19 +65,21 @@ func resourceAwsS3BucketObject() *schema.Resource { "source": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, ConflictsWith: []string{"content"}, }, "content": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, ConflictsWith: []string{"source"}, }, "etag": &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeString, + // This will conflict with SSE-C and SSE-KMS encryption and multi-part upload + // if/when it's actually implemented. The Etag then won't match raw-file MD5. + // See http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html + Optional: true, Computed: true, }, }, @@ -149,7 +147,7 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro d.Set("etag", strings.Trim(*resp.ETag, `"`)) d.SetId(key) - return nil + return resourceAwsS3BucketObjectRead(d, meta) } func resourceAwsS3BucketObjectRead(d *schema.ResourceData, meta interface{}) error { From 24ac9969637062ccd4cb6e49f98ed01c9b6e1d38 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 23 Feb 2016 12:16:36 +0000 Subject: [PATCH 4/7] provider/aws: Add support for versioned objects --- .../aws/resource_aws_s3_bucket_object.go | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_object.go b/builtin/providers/aws/resource_aws_s3_bucket_object.go index ae1214ab6..f1a4d24aa 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_object.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_object.go @@ -82,6 +82,11 @@ func resourceAwsS3BucketObject() *schema.Resource { Optional: true, Computed: true, }, + + "version_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -146,6 +151,7 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro // See https://forums.aws.amazon.com/thread.jspa?threadID=44003 d.Set("etag", strings.Trim(*resp.ETag, `"`)) + d.Set("version_id", resp.VersionId) d.SetId(key) return resourceAwsS3BucketObjectRead(d, meta) } @@ -179,6 +185,7 @@ func resourceAwsS3BucketObjectRead(d *schema.ResourceData, meta interface{}) err d.Set("content_encoding", resp.ContentEncoding) d.Set("content_language", resp.ContentLanguage) d.Set("content_type", resp.ContentType) + d.Set("version_id", resp.VersionId) log.Printf("[DEBUG] Reading S3 Bucket Object meta: %s", resp) return nil @@ -190,13 +197,40 @@ func resourceAwsS3BucketObjectDelete(d *schema.ResourceData, meta interface{}) e bucket := d.Get("bucket").(string) key := d.Get("key").(string) - _, err := s3conn.DeleteObject( - &s3.DeleteObjectInput{ + if _, ok := d.GetOk("version_id"); ok { + // Bucket is versioned, we need to delete all versions + vInput := s3.ListObjectVersionsInput{ + Bucket: aws.String(bucket), + Prefix: aws.String(key), + } + out, err := s3conn.ListObjectVersions(&vInput) + if err != nil { + return fmt.Errorf("Failed listing S3 object versions: %s", err) + } + + for _, v := range out.Versions { + input := s3.DeleteObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + VersionId: v.VersionId, + } + _, err := s3conn.DeleteObject(&input) + if err != nil { + return fmt.Errorf("Error deleting S3 object version of %s:\n %s:\n %s", + key, v, err) + } + } + } else { + // Just delete the object + input := s3.DeleteObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), - }) - if err != nil { - return fmt.Errorf("Error deleting S3 bucket object: %s", err) + } + _, err := s3conn.DeleteObject(&input) + if err != nil { + return fmt.Errorf("Error deleting S3 bucket object: %s", err) + } } + return nil } From 9377b301ca4a0ab69bfdfc209402800699d9fca4 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 24 Feb 2016 15:04:02 +0000 Subject: [PATCH 5/7] provider/aws: Cleanup s3_bucket_object acceptance tests - the goal was to allow running tests in parallel and to get rid of global variables in the aws package --- .../aws/resource_aws_s3_bucket_object_test.go | 68 ++++++++++--------- ..._aws_security_group_rules_matching_test.go | 3 - 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_object_test.go b/builtin/providers/aws/resource_aws_s3_bucket_object_test.go index dc147a4f1..69c3df286 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_object_test.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_object_test.go @@ -14,24 +14,27 @@ import ( "github.com/aws/aws-sdk-go/service/s3" ) -var tf, err = ioutil.TempFile("", "tf") - func TestAccAWSS3BucketObject_source(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-source") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + rInt := acctest.RandInt() // first write some data to the tempfile just so it's not 0 bytes. - ioutil.WriteFile(tf.Name(), []byte("{anything will do }"), 0644) + err = ioutil.WriteFile(tmpFile.Name(), []byte("{anything will do }"), 0644) + if err != nil { + t.Fatal(err) + } + resource.Test(t, resource.TestCase{ - PreCheck: func() { - if err != nil { - panic(err) - } - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSS3BucketObjectDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSS3BucketObjectConfigSource(rInt), + Config: testAccAWSS3BucketObjectConfigSource(rInt, tmpFile.Name()), Check: testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), }, }, @@ -40,40 +43,42 @@ func TestAccAWSS3BucketObject_source(t *testing.T) { func TestAccAWSS3BucketObject_content(t *testing.T) { rInt := acctest.RandInt() + resource.Test(t, resource.TestCase{ - PreCheck: func() { - if err != nil { - panic(err) - } - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSS3BucketObjectDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSS3BucketObjectConfigContent(rInt), - Check: testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), + PreConfig: func() {}, + Config: testAccAWSS3BucketObjectConfigContent(rInt), + Check: testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), }, }, }) } func TestAccAWSS3BucketObject_withContentCharacteristics(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-content-characteristics") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + rInt := acctest.RandInt() // first write some data to the tempfile just so it's not 0 bytes. - ioutil.WriteFile(tf.Name(), []byte("{anything will do }"), 0644) + err = ioutil.WriteFile(tmpFile.Name(), []byte("{anything will do }"), 0644) + if err != nil { + t.Fatal(err) + } + resource.Test(t, resource.TestCase{ - PreCheck: func() { - if err != nil { - panic(err) - } - testAccPreCheck(t) - }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSS3BucketObjectDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSS3BucketObjectConfig_withContentCharacteristics(rInt), + Config: testAccAWSS3BucketObjectConfig_withContentCharacteristics(rInt, tmpFile.Name()), Check: resource.ComposeTestCheckFunc( testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), resource.TestCheckResourceAttr( @@ -107,9 +112,6 @@ func testAccCheckAWSS3BucketObjectDestroy(s *terraform.State) error { func testAccCheckAWSS3BucketObjectExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { - - defer os.Remove(tf.Name()) - rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not Found: %s", n) @@ -133,7 +135,7 @@ func testAccCheckAWSS3BucketObjectExists(n string) resource.TestCheckFunc { } } -func testAccAWSS3BucketObjectConfigSource(randInt int) string { +func testAccAWSS3BucketObjectConfigSource(randInt int, source string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "object_bucket" { bucket = "tf-object-test-bucket-%d" @@ -144,10 +146,10 @@ resource "aws_s3_bucket_object" "object" { source = "%s" content_type = "binary/octet-stream" } -`, randInt, tf.Name()) +`, randInt, source) } -func testAccAWSS3BucketObjectConfig_withContentCharacteristics(randInt int) string { +func testAccAWSS3BucketObjectConfig_withContentCharacteristics(randInt int, source string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "object_bucket_2" { bucket = "tf-object-test-bucket-%d" @@ -160,7 +162,7 @@ resource "aws_s3_bucket_object" "object" { content_language = "en" content_type = "binary/octet-stream" } -`, randInt, tf.Name()) +`, randInt, source) } func testAccAWSS3BucketObjectConfigContent(randInt int) string { diff --git a/builtin/providers/aws/resource_aws_security_group_rules_matching_test.go b/builtin/providers/aws/resource_aws_security_group_rules_matching_test.go index d3cd721b5..c68c9c5e6 100644 --- a/builtin/providers/aws/resource_aws_security_group_rules_matching_test.go +++ b/builtin/providers/aws/resource_aws_security_group_rules_matching_test.go @@ -565,9 +565,6 @@ func TestRulesMixedMatching(t *testing.T) { for i, c := range cases { saves := matchRules("ingress", c.local, c.remote) log.Printf("\n======\n\nSaves:\n%#v\n\nCS Saves:\n%#v\n\n======\n", saves, c.saves) - if err != nil { - t.Fatal(err) - } log.Printf("\n\tTest %d:\n", i) if len(saves) != len(c.saves) { From ef85147559ed2859d5d5be83a4ed2852dc691f2c Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 24 Feb 2016 15:17:22 +0000 Subject: [PATCH 6/7] provider/aws: Add tests for s3_bucket_object updates --- .../aws/resource_aws_s3_bucket_object_test.go | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_object_test.go b/builtin/providers/aws/resource_aws_s3_bucket_object_test.go index 69c3df286..ad0e932ae 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_object_test.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_object_test.go @@ -89,6 +89,48 @@ func TestAccAWSS3BucketObject_withContentCharacteristics(t *testing.T) { }) } +func TestAccAWSS3BucketObject_updates(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-updates") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + + rInt := acctest.RandInt() + err = ioutil.WriteFile(tmpFile.Name(), []byte("initial object state"), 0644) + if err != nil { + t.Fatal(err) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketObjectConfig_updates(rInt, tmpFile.Name()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), + resource.TestCheckResourceAttr("aws_s3_bucket_object.object", "etag", "647d1d58e1011c743ec67d5e8af87b53"), + ), + }, + resource.TestStep{ + PreConfig: func() { + err = ioutil.WriteFile(tmpFile.Name(), []byte("modified object"), 0644) + if err != nil { + t.Fatal(err) + } + }, + Config: testAccAWSS3BucketObjectConfig_updates(rInt, tmpFile.Name()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), + resource.TestCheckResourceAttr("aws_s3_bucket_object.object", "etag", "1c7fd13df1515c2a13ad9eb068931f09"), + ), + }, + }, + }) +} + func testAccCheckAWSS3BucketObjectDestroy(s *terraform.State) error { s3conn := testAccProvider.Meta().(*AWSClient).s3conn @@ -177,3 +219,18 @@ resource "aws_s3_bucket_object" "object" { } `, randInt) } + +func testAccAWSS3BucketObjectConfig_updates(randInt int, source string) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "object_bucket_3" { + bucket = "tf-object-test-bucket-%d" +} + +resource "aws_s3_bucket_object" "object" { + bucket = "${aws_s3_bucket.object_bucket_3.bucket}" + key = "updateable-key" + source = "%s" + etag = "${md5(file("%s"))}" +} +`, randInt, source, source) +} From 44246ca126b3d341be296860fc133f69302d4cab Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 24 Feb 2016 19:18:22 +0000 Subject: [PATCH 7/7] provider/aws: Add tests for s3_bucket_object w/ versioning enabled --- .../aws/resource_aws_s3_bucket_object_test.go | 102 ++++++++++++++++-- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_object_test.go b/builtin/providers/aws/resource_aws_s3_bucket_object_test.go index ad0e932ae..0ba226cd0 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_object_test.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_object_test.go @@ -27,6 +27,7 @@ func TestAccAWSS3BucketObject_source(t *testing.T) { if err != nil { t.Fatal(err) } + var obj s3.GetObjectOutput resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -35,7 +36,7 @@ func TestAccAWSS3BucketObject_source(t *testing.T) { Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSS3BucketObjectConfigSource(rInt, tmpFile.Name()), - Check: testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), + Check: testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object", &obj), }, }, }) @@ -43,6 +44,7 @@ func TestAccAWSS3BucketObject_source(t *testing.T) { func TestAccAWSS3BucketObject_content(t *testing.T) { rInt := acctest.RandInt() + var obj s3.GetObjectOutput resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -52,7 +54,7 @@ func TestAccAWSS3BucketObject_content(t *testing.T) { resource.TestStep{ PreConfig: func() {}, Config: testAccAWSS3BucketObjectConfigContent(rInt), - Check: testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), + Check: testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object", &obj), }, }, }) @@ -72,6 +74,8 @@ func TestAccAWSS3BucketObject_withContentCharacteristics(t *testing.T) { t.Fatal(err) } + var obj s3.GetObjectOutput + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -80,7 +84,7 @@ func TestAccAWSS3BucketObject_withContentCharacteristics(t *testing.T) { resource.TestStep{ Config: testAccAWSS3BucketObjectConfig_withContentCharacteristics(rInt, tmpFile.Name()), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), + testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object", &obj), resource.TestCheckResourceAttr( "aws_s3_bucket_object.object", "content_type", "binary/octet-stream"), ), @@ -101,6 +105,7 @@ func TestAccAWSS3BucketObject_updates(t *testing.T) { if err != nil { t.Fatal(err) } + var obj s3.GetObjectOutput resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -110,7 +115,7 @@ func TestAccAWSS3BucketObject_updates(t *testing.T) { resource.TestStep{ Config: testAccAWSS3BucketObjectConfig_updates(rInt, tmpFile.Name()), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), + testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object", &obj), resource.TestCheckResourceAttr("aws_s3_bucket_object.object", "etag", "647d1d58e1011c743ec67d5e8af87b53"), ), }, @@ -123,7 +128,7 @@ func TestAccAWSS3BucketObject_updates(t *testing.T) { }, Config: testAccAWSS3BucketObjectConfig_updates(rInt, tmpFile.Name()), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"), + testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object", &obj), resource.TestCheckResourceAttr("aws_s3_bucket_object.object", "etag", "1c7fd13df1515c2a13ad9eb068931f09"), ), }, @@ -131,6 +136,68 @@ func TestAccAWSS3BucketObject_updates(t *testing.T) { }) } +func TestAccAWSS3BucketObject_updatesWithVersioning(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-updates-w-versions") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + + rInt := acctest.RandInt() + err = ioutil.WriteFile(tmpFile.Name(), []byte("initial versioned object state"), 0644) + if err != nil { + t.Fatal(err) + } + + var originalObj, modifiedObj s3.GetObjectOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketObjectConfig_updatesWithVersioning(rInt, tmpFile.Name()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object", &originalObj), + resource.TestCheckResourceAttr("aws_s3_bucket_object.object", "etag", "cee4407fa91906284e2a5e5e03e86b1b"), + ), + }, + resource.TestStep{ + PreConfig: func() { + err = ioutil.WriteFile(tmpFile.Name(), []byte("modified versioned object"), 0644) + if err != nil { + t.Fatal(err) + } + }, + Config: testAccAWSS3BucketObjectConfig_updatesWithVersioning(rInt, tmpFile.Name()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object", &modifiedObj), + resource.TestCheckResourceAttr("aws_s3_bucket_object.object", "etag", "00b8c73b1b50e7cc932362c7225b8e29"), + testAccCheckAWSS3BucketObjectVersionIdDiffers(&originalObj, &modifiedObj), + ), + }, + }, + }) +} + +func testAccCheckAWSS3BucketObjectVersionIdDiffers(first, second *s3.GetObjectOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if first.VersionId == nil { + return fmt.Errorf("Expected first object to have VersionId: %s", first) + } + if second.VersionId == nil { + return fmt.Errorf("Expected second object to have VersionId: %s", second) + } + + if *first.VersionId == *second.VersionId { + return fmt.Errorf("Expected Version IDs to differ, but they are equal (%s)", *first.VersionId) + } + + return nil + } +} + func testAccCheckAWSS3BucketObjectDestroy(s *terraform.State) error { s3conn := testAccProvider.Meta().(*AWSClient).s3conn @@ -152,7 +219,7 @@ func testAccCheckAWSS3BucketObjectDestroy(s *terraform.State) error { return nil } -func testAccCheckAWSS3BucketObjectExists(n string) resource.TestCheckFunc { +func testAccCheckAWSS3BucketObjectExists(n string, obj *s3.GetObjectOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -164,7 +231,7 @@ func testAccCheckAWSS3BucketObjectExists(n string) resource.TestCheckFunc { } s3conn := testAccProvider.Meta().(*AWSClient).s3conn - _, err := s3conn.GetObject( + out, err := s3conn.GetObject( &s3.GetObjectInput{ Bucket: aws.String(rs.Primary.Attributes["bucket"]), Key: aws.String(rs.Primary.Attributes["key"]), @@ -173,6 +240,9 @@ func testAccCheckAWSS3BucketObjectExists(n string) resource.TestCheckFunc { if err != nil { return fmt.Errorf("S3Bucket Object error: %s", err) } + + *obj = *out + return nil } } @@ -234,3 +304,21 @@ resource "aws_s3_bucket_object" "object" { } `, randInt, source, source) } + +func testAccAWSS3BucketObjectConfig_updatesWithVersioning(randInt int, source string) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "object_bucket_3" { + bucket = "tf-object-test-bucket-%d" + versioning { + enabled = true + } +} + +resource "aws_s3_bucket_object" "object" { + bucket = "${aws_s3_bucket.object_bucket_3.bucket}" + key = "updateable-key" + source = "%s" + etag = "${md5(file("%s"))}" +} +`, randInt, source, source) +}