-
Notifications
You must be signed in to change notification settings - Fork 9.7k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, should |
||
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 | ||
} |
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()) |
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. |
There was a problem hiding this comment.
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), theETag
will not be themd5
... 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?