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

provider/AWS: Add new resource for creating objects in an S3 bucket #2079

Closed
Closed
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
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func Provider() terraform.ResourceProvider {
"aws_route_table_association": resourceAwsRouteTableAssociation(),
"aws_route_table": resourceAwsRouteTable(),
"aws_s3_bucket": resourceAwsS3Bucket(),
"aws_s3_bucket_object": resourceAwsS3BucketObject(),
"aws_security_group": resourceAwsSecurityGroup(),
"aws_security_group_rule": resourceAwsSecurityGroupRule(),
"aws_sqs_queue": resourceAwsSqsQueue(),
Expand Down
112 changes: 112 additions & 0 deletions builtin/providers/aws/resource_aws_s3_bucket_object.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package aws

import (
"fmt"
"log"
"os"

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

"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/aws/awsutil"
"github.com/awslabs/aws-sdk-go/service/s3"
)

func resourceAwsS3BucketObject() *schema.Resource {
return &schema.Resource{
Create: resourceAwsS3BucketObjectPut,
Read: resourceAwsS3BucketObjectRead,
Update: resourceAwsS3BucketObjectPut,
Delete: resourceAwsS3BucketObjectDelete,

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

"key": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"source": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

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

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

file, err := os.Open(source)

if err != nil {
d.SetId("")
return fmt.Errorf("Error opening S3 bucket object source(%s): %s", source, err)
}

resp, err := s3conn.PutObject(
&s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
Body: file,
})

if err != nil {
d.SetId("")
return fmt.Errorf("Error putting object in S3 bucket (%s): %s", bucket, err)
}

d.SetId(*resp.ETag)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hesitant on using the ETag here, simply because the API mentions that if a user supplies their own key to encrypt the file(s), the ETag will not be the md5... it doesn't clearly say what it will be in that case though. I worry that down the road we expand on this and then get into a situation where we're losing objects :/

Can we simply use key here, instead?

return nil
}

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

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

resp, err := s3conn.HeadObject(
&s3.HeadObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
IfMatch: aws.String(d.Id()),
})

if err != nil {
// if there is an error reading the object we assume it's not there.
d.SetId("")
log.Printf("Error Reading Object (%s): %s", key, err)
}

log.Printf(awsutil.StringValue(resp))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need to log here, it's helpful to log out with [DEBUG] and a message. This looks like debugging code though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, should resourceAwsS3BucketObjectRead do anything, other than remove a resource from local state if it's not found? Seems right now that may be the case, which if so I guess that's OK

return nil
}

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

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

_, err := s3conn.DeleteObject(
&s3.DeleteObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
if err != nil {
return fmt.Errorf("Error deleting S3 bucket object: %s", err)
}
return nil
}
98 changes: 98 additions & 0 deletions builtin/providers/aws/resource_aws_s3_bucket_object_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package aws

import (
"fmt"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"io/ioutil"
"os"
"testing"

"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/s3"
)

var tf, err = ioutil.TempFile("", "tf")

func TestAccAWSS3BucketObject_basic(t *testing.T) {
// first write some data to the tempfile just so it's not 0 bytes.
ioutil.WriteFile(tf.Name(), []byte("{anything will do }"), 0644)
resource.Test(t, resource.TestCase{
PreCheck: func() {
if err != nil {
panic(err)
}
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketObjectDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSS3BucketObjectConfig,
Check: testAccCheckAWSS3BucketObjectExists("aws_s3_bucket_object.object"),
},
},
})
}

func testAccCheckAWSS3BucketObjectDestroy(s *terraform.State) error {
s3conn := testAccProvider.Meta().(*AWSClient).s3conn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_s3_bucket_object" {
continue
}

_, err := s3conn.HeadObject(
&s3.HeadObjectInput{
Bucket: aws.String(rs.Primary.Attributes["bucket"]),
Key: aws.String(rs.Primary.Attributes["key"]),
IfMatch: aws.String(rs.Primary.ID),
})
if err == nil {
return fmt.Errorf("AWS S3 Object still exists: %s", rs.Primary.ID)
}
}
return nil
}

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)
}

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

s3conn := testAccProvider.Meta().(*AWSClient).s3conn
_, err := s3conn.GetObject(
&s3.GetObjectInput{
Bucket: aws.String(rs.Primary.Attributes["bucket"]),
Key: aws.String(rs.Primary.Attributes["key"]),
IfMatch: aws.String(rs.Primary.ID),
})
if err != nil {
return fmt.Errorf("S3Bucket Object error: %s", err)
}
return nil
}
}

var randomBucket = randInt
var testAccAWSS3BucketObjectConfig = fmt.Sprintf(`
resource "aws_s3_bucket" "object_bucket" {
bucket = "tf-object-test-bucket-%d"
}
resource "aws_s3_bucket_object" "object" {
depends_on = "aws_s3_bucket.object_bucket"
bucket = "tf-object-test-bucket-%d"
key = "test-key"
source = "%s"
}
`, randomBucket, randomBucket, tf.Name())
36 changes: 36 additions & 0 deletions website/source/docs/providers/aws/r/s3_bucket_object.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
layout: "aws"
page_title: "AWS: aws_s3_bucket_object"
side_bar_current: "docs-aws-resource-s3-bucket-object"
description: |-
Provides a S3 bucket object resource.
---

# aws\_s3\_bucket\_object

Provides a S3 bucket object resource.

## Example Usage

### Uploading a file to a bucket

```
resource "aws_s3_bucket_object" "object" {
bucket = "your_bucket_name"
key = "new_object_key"
source = "path/to/file"
}
```

## Argument Reference

The following arguments are supported:
* `bucket` - (Required) The name of the bucket to put the file in.
* `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.

## Attributes Reference

The following attributes are exported

* `id` - the id of the resource corresponds to the ETag of the bucket object on aws.