2014-07-21 20:34:47 +02:00
|
|
|
package aws
|
|
|
|
|
|
|
|
import (
|
2015-09-05 10:19:52 +02:00
|
|
|
"bytes"
|
2015-05-16 12:11:23 +02:00
|
|
|
"encoding/json"
|
2014-07-21 20:34:47 +02:00
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
2015-02-19 22:38:56 +01:00
|
|
|
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
|
|
|
"github.com/aws/aws-sdk-go/service/s3"
|
2015-09-05 10:19:52 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
2014-07-21 20:34:47 +02:00
|
|
|
)
|
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
func resourceAwsS3Bucket() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourceAwsS3BucketCreate,
|
|
|
|
Read: resourceAwsS3BucketRead,
|
2015-03-26 15:17:27 +01:00
|
|
|
Update: resourceAwsS3BucketUpdate,
|
2014-11-21 17:58:34 +01:00
|
|
|
Delete: resourceAwsS3BucketDelete,
|
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"bucket": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2015-10-29 14:33:35 +01:00
|
|
|
"arn": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
"acl": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Default: "private",
|
|
|
|
Optional: true,
|
|
|
|
},
|
2015-03-26 15:17:27 +01:00
|
|
|
|
2015-05-03 02:23:45 +02:00
|
|
|
"policy": &schema.Schema{
|
2015-05-16 12:11:23 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
StateFunc: normalizeJson,
|
2015-05-03 02:23:45 +02:00
|
|
|
},
|
|
|
|
|
2015-10-01 18:49:32 +02:00
|
|
|
"cors_rule": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"allowed_headers": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
"allowed_methods": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
"allowed_origins": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
"expose_headers": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
"max_age_seconds": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-04-26 01:36:00 +02:00
|
|
|
"website": &schema.Schema{
|
2015-05-01 15:19:54 +02:00
|
|
|
Type: schema.TypeList,
|
2015-04-29 18:16:01 +02:00
|
|
|
Optional: true,
|
2015-05-01 15:19:54 +02:00
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"index_document": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
2015-05-11 16:53:33 +02:00
|
|
|
Optional: true,
|
2015-05-01 15:19:54 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"error_document": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2015-05-11 16:53:33 +02:00
|
|
|
|
|
|
|
"redirect_all_requests_to": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
ConflictsWith: []string{
|
|
|
|
"website.0.index_document",
|
|
|
|
"website.0.error_document",
|
|
|
|
},
|
|
|
|
Optional: true,
|
|
|
|
},
|
2015-05-01 15:19:54 +02:00
|
|
|
},
|
|
|
|
},
|
2015-04-29 21:47:21 +02:00
|
|
|
},
|
|
|
|
|
2015-05-07 18:00:39 +02:00
|
|
|
"hosted_zone_id": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
2015-05-07 18:09:19 +02:00
|
|
|
"region": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
2015-04-29 21:47:21 +02:00
|
|
|
"website_endpoint": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
2015-04-29 18:16:01 +02:00
|
|
|
},
|
2015-06-03 17:10:17 +02:00
|
|
|
"website_domain": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
2015-04-29 18:16:01 +02:00
|
|
|
|
2015-08-05 04:09:31 +02:00
|
|
|
"versioning": &schema.Schema{
|
2015-09-05 10:19:52 +02:00
|
|
|
Type: schema.TypeSet,
|
2015-08-05 04:09:31 +02:00
|
|
|
Optional: true,
|
2015-09-05 10:19:52 +02:00
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"enabled": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
m := v.(map[string]interface{})
|
2015-09-06 04:06:29 +02:00
|
|
|
buf.WriteString(fmt.Sprintf("%t-", m["enabled"].(bool)))
|
2015-09-05 10:19:52 +02:00
|
|
|
|
|
|
|
return hashcode.String(buf.String())
|
|
|
|
},
|
2015-08-05 04:09:31 +02:00
|
|
|
},
|
|
|
|
|
2015-03-26 15:17:27 +01:00
|
|
|
"tags": tagsSchema(),
|
2015-05-21 01:06:27 +02:00
|
|
|
|
|
|
|
"force_destroy": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: false,
|
|
|
|
},
|
2014-07-21 20:34:47 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
func resourceAwsS3BucketCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
s3conn := meta.(*AWSClient).s3conn
|
2015-02-19 22:38:56 +01:00
|
|
|
awsRegion := meta.(*AWSClient).region
|
2014-07-21 20:34:47 +02:00
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
// Get the bucket and acl
|
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
acl := d.Get("acl").(string)
|
2014-07-21 20:34:47 +02:00
|
|
|
|
|
|
|
log.Printf("[DEBUG] S3 bucket create: %s, ACL: %s", bucket, acl)
|
2015-02-19 22:38:56 +01:00
|
|
|
|
2015-04-14 18:07:05 +02:00
|
|
|
req := &s3.CreateBucketInput{
|
2015-02-19 22:38:56 +01:00
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
ACL: aws.String(acl),
|
2015-03-02 18:13:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Special case us-east-1 region and do not set the LocationConstraint.
|
|
|
|
// See "Request Elements: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUT.html
|
|
|
|
if awsRegion != "us-east-1" {
|
|
|
|
req.CreateBucketConfiguration = &s3.CreateBucketConfiguration{
|
2015-02-19 22:38:56 +01:00
|
|
|
LocationConstraint: aws.String(awsRegion),
|
2015-03-02 18:13:19 +01:00
|
|
|
}
|
2015-02-19 22:38:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_, err := s3conn.CreateBucket(req)
|
2014-07-21 20:34:47 +02:00
|
|
|
if err != nil {
|
2014-11-21 17:58:34 +01:00
|
|
|
return fmt.Errorf("Error creating S3 bucket: %s", err)
|
2014-07-21 20:34:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Assign the bucket name as the resource ID
|
2014-11-21 17:58:34 +01:00
|
|
|
d.SetId(bucket)
|
2014-07-21 20:34:47 +02:00
|
|
|
|
2015-03-26 15:17:27 +01:00
|
|
|
return resourceAwsS3BucketUpdate(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
s3conn := meta.(*AWSClient).s3conn
|
|
|
|
if err := setTagsS3(s3conn, d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-26 01:36:00 +02:00
|
|
|
|
2015-05-03 02:23:45 +02:00
|
|
|
if d.HasChange("policy") {
|
|
|
|
if err := resourceAwsS3BucketPolicyUpdate(s3conn, d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:49:32 +02:00
|
|
|
if d.HasChange("cors_rule") {
|
|
|
|
if err := resourceAwsS3BucketCorsUpdate(s3conn, d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-03 02:23:45 +02:00
|
|
|
if d.HasChange("website") {
|
|
|
|
if err := resourceAwsS3BucketWebsiteUpdate(s3conn, d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-26 01:36:00 +02:00
|
|
|
}
|
|
|
|
|
2015-08-05 04:09:31 +02:00
|
|
|
if d.HasChange("versioning") {
|
|
|
|
if err := resourceAwsS3BucketVersioningUpdate(s3conn, d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2015-11-26 22:45:48 +01:00
|
|
|
if d.HasChange("acl") {
|
|
|
|
if err := resourceAwsS3BucketAclUpdate(s3conn, d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2015-08-05 04:09:31 +02:00
|
|
|
|
2015-03-26 15:17:27 +01:00
|
|
|
return resourceAwsS3BucketRead(d, meta)
|
2014-07-21 20:34:47 +02:00
|
|
|
}
|
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
s3conn := meta.(*AWSClient).s3conn
|
2014-07-21 20:34:47 +02:00
|
|
|
|
2015-05-20 13:21:23 +02:00
|
|
|
var err error
|
|
|
|
_, err = s3conn.HeadBucket(&s3.HeadBucketInput{
|
2015-02-19 22:38:56 +01:00
|
|
|
Bucket: aws.String(d.Id()),
|
|
|
|
})
|
2014-07-21 20:34:47 +02:00
|
|
|
if err != nil {
|
2015-05-20 13:21:23 +02:00
|
|
|
if awsError, ok := err.(awserr.RequestFailure); ok && awsError.StatusCode() == 404 {
|
2015-08-03 23:17:01 +02:00
|
|
|
log.Printf("[WARN] S3 Bucket (%s) not found, error code (404)", d.Id())
|
2015-04-17 19:30:31 +02:00
|
|
|
d.SetId("")
|
2015-08-03 23:17:01 +02:00
|
|
|
return nil
|
2015-04-17 19:30:31 +02:00
|
|
|
} else {
|
|
|
|
// some of the AWS SDK's errors can be empty strings, so let's add
|
|
|
|
// some additional context.
|
2015-04-20 03:13:04 +02:00
|
|
|
return fmt.Errorf("error reading S3 bucket \"%s\": %s", d.Id(), err)
|
2015-04-17 19:30:31 +02:00
|
|
|
}
|
2014-07-21 20:34:47 +02:00
|
|
|
}
|
2015-03-26 17:45:16 +01:00
|
|
|
|
2015-05-03 02:23:45 +02:00
|
|
|
// Read the policy
|
|
|
|
pol, err := s3conn.GetBucketPolicy(&s3.GetBucketPolicyInput{
|
|
|
|
Bucket: aws.String(d.Id()),
|
|
|
|
})
|
2015-05-16 12:11:23 +02:00
|
|
|
log.Printf("[DEBUG] S3 bucket: %s, read policy: %v", d.Id(), pol)
|
2015-05-03 02:23:45 +02:00
|
|
|
if err != nil {
|
|
|
|
if err := d.Set("policy", ""); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if v := pol.Policy; v == nil {
|
|
|
|
if err := d.Set("policy", ""); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-05-16 12:11:23 +02:00
|
|
|
} else if err := d.Set("policy", normalizeJson(*v)); err != nil {
|
2015-05-03 02:23:45 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:49:32 +02:00
|
|
|
// Read the CORS
|
|
|
|
cors, err := s3conn.GetBucketCors(&s3.GetBucketCorsInput{
|
|
|
|
Bucket: aws.String(d.Id()),
|
|
|
|
})
|
|
|
|
log.Printf("[DEBUG] S3 bucket: %s, read CORS: %v", d.Id(), cors)
|
|
|
|
if err != nil {
|
|
|
|
rules := make([]map[string]interface{}, 0, len(cors.CORSRules))
|
|
|
|
for _, ruleObject := range cors.CORSRules {
|
|
|
|
rule := make(map[string]interface{})
|
|
|
|
rule["allowed_headers"] = ruleObject.AllowedHeaders
|
|
|
|
rule["allowed_methods"] = ruleObject.AllowedMethods
|
|
|
|
rule["allowed_origins"] = ruleObject.AllowedOrigins
|
|
|
|
rule["expose_headers"] = ruleObject.ExposeHeaders
|
|
|
|
rule["max_age_seconds"] = ruleObject.MaxAgeSeconds
|
|
|
|
rules = append(rules, rule)
|
|
|
|
}
|
2015-10-28 01:19:37 +01:00
|
|
|
if err := d.Set("cors_rule", rules); err != nil {
|
|
|
|
return fmt.Errorf("error reading S3 bucket \"%s\" CORS rules: %s", d.Id(), err)
|
|
|
|
}
|
2015-10-01 18:49:32 +02:00
|
|
|
}
|
|
|
|
|
2015-05-06 14:12:40 +02:00
|
|
|
// Read the website configuration
|
|
|
|
ws, err := s3conn.GetBucketWebsite(&s3.GetBucketWebsiteInput{
|
|
|
|
Bucket: aws.String(d.Id()),
|
|
|
|
})
|
|
|
|
var websites []map[string]interface{}
|
|
|
|
if err == nil {
|
2015-05-06 14:40:32 +02:00
|
|
|
w := make(map[string]interface{})
|
|
|
|
|
2015-05-11 16:53:33 +02:00
|
|
|
if v := ws.IndexDocument; v != nil {
|
|
|
|
w["index_document"] = *v.Suffix
|
|
|
|
}
|
2015-05-06 14:40:32 +02:00
|
|
|
|
|
|
|
if v := ws.ErrorDocument; v != nil {
|
|
|
|
w["error_document"] = *v.Key
|
|
|
|
}
|
|
|
|
|
2015-05-11 16:53:33 +02:00
|
|
|
if v := ws.RedirectAllRequestsTo; v != nil {
|
|
|
|
w["redirect_all_requests_to"] = *v.HostName
|
|
|
|
}
|
|
|
|
|
2015-05-06 14:40:32 +02:00
|
|
|
websites = append(websites, w)
|
2015-05-06 14:12:40 +02:00
|
|
|
}
|
|
|
|
if err := d.Set("website", websites); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-08-05 04:09:31 +02:00
|
|
|
// Read the versioning configuration
|
|
|
|
versioning, err := s3conn.GetBucketVersioning(&s3.GetBucketVersioningInput{
|
|
|
|
Bucket: aws.String(d.Id()),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Printf("[DEBUG] S3 Bucket: %s, versioning: %v", d.Id(), versioning)
|
2015-09-05 10:19:52 +02:00
|
|
|
if versioning.Status != nil && *versioning.Status == s3.BucketVersioningStatusEnabled {
|
|
|
|
vcl := make([]map[string]interface{}, 0, 1)
|
|
|
|
vc := make(map[string]interface{})
|
2015-08-05 04:09:31 +02:00
|
|
|
if *versioning.Status == s3.BucketVersioningStatusEnabled {
|
2015-09-05 10:19:52 +02:00
|
|
|
vc["enabled"] = true
|
2015-08-05 04:09:31 +02:00
|
|
|
} else {
|
2015-09-05 10:19:52 +02:00
|
|
|
vc["enabled"] = false
|
2015-08-05 04:09:31 +02:00
|
|
|
}
|
2015-09-05 10:19:52 +02:00
|
|
|
vcl = append(vcl, vc)
|
|
|
|
if err := d.Set("versioning", vcl); err != nil {
|
2015-08-05 04:09:31 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-07 18:09:19 +02:00
|
|
|
// Add the region as an attribute
|
2015-05-07 18:00:39 +02:00
|
|
|
location, err := s3conn.GetBucketLocation(
|
|
|
|
&s3.GetBucketLocationInput{
|
|
|
|
Bucket: aws.String(d.Id()),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var region string
|
|
|
|
if location.LocationConstraint != nil {
|
|
|
|
region = *location.LocationConstraint
|
|
|
|
}
|
2015-05-08 16:29:47 +02:00
|
|
|
region = normalizeRegion(region)
|
2015-05-07 18:09:19 +02:00
|
|
|
if err := d.Set("region", region); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-05-07 18:00:39 +02:00
|
|
|
|
|
|
|
// Add the hosted zone ID for this bucket's region as an attribute
|
2015-05-08 16:29:26 +02:00
|
|
|
hostedZoneID := HostedZoneIDForRegion(region)
|
2015-05-07 18:00:39 +02:00
|
|
|
if err := d.Set("hosted_zone_id", hostedZoneID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add website_endpoint as an attribute
|
2015-06-03 17:10:17 +02:00
|
|
|
websiteEndpoint, err := websiteEndpoint(s3conn, d)
|
2015-04-29 21:47:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-06-03 17:10:17 +02:00
|
|
|
if websiteEndpoint != nil {
|
|
|
|
if err := d.Set("website_endpoint", websiteEndpoint.Endpoint); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := d.Set("website_domain", websiteEndpoint.Domain); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-29 21:47:21 +02:00
|
|
|
}
|
|
|
|
|
2015-04-01 14:57:50 +02:00
|
|
|
tagSet, err := getTagSetS3(s3conn, d.Id())
|
2015-03-26 17:45:16 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-04-01 14:57:50 +02:00
|
|
|
if err := d.Set("tags", tagsToMapS3(tagSet)); err != nil {
|
2015-03-26 17:45:16 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-10-29 14:33:35 +01:00
|
|
|
d.Set("arn", fmt.Sprint("arn:aws:s3:::", d.Id()))
|
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
return nil
|
2014-07-21 20:34:47 +02:00
|
|
|
}
|
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
func resourceAwsS3BucketDelete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
s3conn := meta.(*AWSClient).s3conn
|
2014-07-21 20:34:47 +02:00
|
|
|
|
2014-11-21 17:58:34 +01:00
|
|
|
log.Printf("[DEBUG] S3 Delete Bucket: %s", d.Id())
|
2015-04-14 18:07:05 +02:00
|
|
|
_, err := s3conn.DeleteBucket(&s3.DeleteBucketInput{
|
2015-02-19 22:38:56 +01:00
|
|
|
Bucket: aws.String(d.Id()),
|
|
|
|
})
|
|
|
|
if err != nil {
|
2015-05-21 01:06:27 +02:00
|
|
|
ec2err, ok := err.(awserr.Error)
|
|
|
|
if ok && ec2err.Code() == "BucketNotEmpty" {
|
|
|
|
if d.Get("force_destroy").(bool) {
|
|
|
|
// bucket may have things delete them
|
|
|
|
log.Printf("[DEBUG] S3 Bucket attempting to forceDestroy %+v", err)
|
|
|
|
|
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
resp, err := s3conn.ListObjects(
|
|
|
|
&s3.ListObjectsInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error S3 Bucket list Objects err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
objectsToDelete := make([]*s3.ObjectIdentifier, len(resp.Contents))
|
|
|
|
for i, v := range resp.Contents {
|
|
|
|
objectsToDelete[i] = &s3.ObjectIdentifier{
|
|
|
|
Key: v.Key,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_, err = s3conn.DeleteObjects(
|
|
|
|
&s3.DeleteObjectsInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
Delete: &s3.Delete{
|
|
|
|
Objects: objectsToDelete,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error S3 Bucket force_destroy error deleting: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// this line recurses until all objects are deleted or an error is returned
|
|
|
|
return resourceAwsS3BucketDelete(d, meta)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Errorf("Error deleting S3 Bucket: %s", err)
|
2015-02-19 22:38:56 +01:00
|
|
|
}
|
|
|
|
return nil
|
2014-07-21 20:34:47 +02:00
|
|
|
}
|
2015-04-26 01:36:00 +02:00
|
|
|
|
2015-05-03 02:23:45 +02:00
|
|
|
func resourceAwsS3BucketPolicyUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
|
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
policy := d.Get("policy").(string)
|
|
|
|
|
|
|
|
if policy != "" {
|
|
|
|
log.Printf("[DEBUG] S3 bucket: %s, put policy: %s", bucket, policy)
|
|
|
|
|
|
|
|
_, err := s3conn.PutBucketPolicy(&s3.PutBucketPolicyInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
Policy: aws.String(policy),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error putting S3 policy: %s", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Printf("[DEBUG] S3 bucket: %s, delete policy: %s", bucket, policy)
|
|
|
|
_, err := s3conn.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error deleting S3 policy: %s", err)
|
|
|
|
}
|
2015-05-01 15:19:54 +02:00
|
|
|
}
|
|
|
|
|
2015-05-03 02:23:45 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:49:32 +02:00
|
|
|
func resourceAwsS3BucketCorsUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
|
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
rawCors := d.Get("cors_rule").([]interface{})
|
|
|
|
|
|
|
|
if len(rawCors) == 0 {
|
|
|
|
// Delete CORS
|
|
|
|
log.Printf("[DEBUG] S3 bucket: %s, delete CORS", bucket)
|
|
|
|
_, err := s3conn.DeleteBucketCors(&s3.DeleteBucketCorsInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error deleting S3 CORS: %s", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Put CORS
|
|
|
|
rules := make([]*s3.CORSRule, 0, len(rawCors))
|
|
|
|
for _, cors := range rawCors {
|
|
|
|
corsMap := cors.(map[string]interface{})
|
|
|
|
r := &s3.CORSRule{}
|
|
|
|
for k, v := range corsMap {
|
|
|
|
log.Printf("[DEBUG] S3 bucket: %s, put CORS: %#v, %#v", bucket, k, v)
|
|
|
|
if k == "max_age_seconds" {
|
|
|
|
r.MaxAgeSeconds = aws.Int64(int64(v.(int)))
|
|
|
|
} else {
|
|
|
|
vMap := make([]*string, len(v.([]interface{})))
|
|
|
|
for i, vv := range v.([]interface{}) {
|
|
|
|
str := vv.(string)
|
|
|
|
vMap[i] = aws.String(str)
|
|
|
|
}
|
|
|
|
switch k {
|
|
|
|
case "allowed_headers":
|
|
|
|
r.AllowedHeaders = vMap
|
|
|
|
case "allowed_methods":
|
|
|
|
r.AllowedMethods = vMap
|
|
|
|
case "allowed_origins":
|
|
|
|
r.AllowedOrigins = vMap
|
|
|
|
case "expose_headers":
|
|
|
|
r.ExposeHeaders = vMap
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rules = append(rules, r)
|
|
|
|
}
|
|
|
|
corsInput := &s3.PutBucketCorsInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
CORSConfiguration: &s3.CORSConfiguration{
|
|
|
|
CORSRules: rules,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
log.Printf("[DEBUG] S3 bucket: %s, put CORS: %#v", bucket, corsInput)
|
|
|
|
_, err := s3conn.PutBucketCors(corsInput)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error putting S3 CORS: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-05-03 02:23:45 +02:00
|
|
|
func resourceAwsS3BucketWebsiteUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
|
2015-05-01 15:19:54 +02:00
|
|
|
ws := d.Get("website").([]interface{})
|
|
|
|
|
|
|
|
if len(ws) == 1 {
|
|
|
|
w := ws[0].(map[string]interface{})
|
|
|
|
return resourceAwsS3BucketWebsitePut(s3conn, d, w)
|
|
|
|
} else if len(ws) == 0 {
|
|
|
|
return resourceAwsS3BucketWebsiteDelete(s3conn, d)
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("Cannot specify more than one website.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsS3BucketWebsitePut(s3conn *s3.S3, d *schema.ResourceData, website map[string]interface{}) error {
|
2015-04-26 01:36:00 +02:00
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
|
2015-05-01 15:19:54 +02:00
|
|
|
indexDocument := website["index_document"].(string)
|
|
|
|
errorDocument := website["error_document"].(string)
|
2015-05-11 16:53:33 +02:00
|
|
|
redirectAllRequestsTo := website["redirect_all_requests_to"].(string)
|
|
|
|
|
|
|
|
if indexDocument == "" && redirectAllRequestsTo == "" {
|
|
|
|
return fmt.Errorf("Must specify either index_document or redirect_all_requests_to.")
|
|
|
|
}
|
2015-04-29 18:16:01 +02:00
|
|
|
|
2015-05-01 15:19:54 +02:00
|
|
|
websiteConfiguration := &s3.WebsiteConfiguration{}
|
2015-04-29 18:16:01 +02:00
|
|
|
|
2015-05-11 16:53:33 +02:00
|
|
|
if indexDocument != "" {
|
|
|
|
websiteConfiguration.IndexDocument = &s3.IndexDocument{Suffix: aws.String(indexDocument)}
|
|
|
|
}
|
2015-04-29 18:16:01 +02:00
|
|
|
|
2015-05-01 15:19:54 +02:00
|
|
|
if errorDocument != "" {
|
|
|
|
websiteConfiguration.ErrorDocument = &s3.ErrorDocument{Key: aws.String(errorDocument)}
|
|
|
|
}
|
2015-04-26 01:36:00 +02:00
|
|
|
|
2015-05-11 16:53:33 +02:00
|
|
|
if redirectAllRequestsTo != "" {
|
|
|
|
websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectAllRequestsTo)}
|
|
|
|
}
|
|
|
|
|
2015-05-01 15:19:54 +02:00
|
|
|
putInput := &s3.PutBucketWebsiteInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
WebsiteConfiguration: websiteConfiguration,
|
|
|
|
}
|
2015-04-26 01:36:00 +02:00
|
|
|
|
2015-05-07 17:03:28 +02:00
|
|
|
log.Printf("[DEBUG] S3 put bucket website: %#v", putInput)
|
2015-04-26 01:36:00 +02:00
|
|
|
|
2015-05-01 15:19:54 +02:00
|
|
|
_, err := s3conn.PutBucketWebsite(putInput)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error putting S3 website: %s", err)
|
|
|
|
}
|
2015-04-26 01:36:00 +02:00
|
|
|
|
2015-05-01 15:19:54 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsS3BucketWebsiteDelete(s3conn *s3.S3, d *schema.ResourceData) error {
|
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
deleteInput := &s3.DeleteBucketWebsiteInput{Bucket: aws.String(bucket)}
|
|
|
|
|
2015-05-07 17:03:28 +02:00
|
|
|
log.Printf("[DEBUG] S3 delete bucket website: %#v", deleteInput)
|
2015-05-01 15:19:54 +02:00
|
|
|
|
|
|
|
_, err := s3conn.DeleteBucketWebsite(deleteInput)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error deleting S3 website: %s", err)
|
2015-04-26 01:36:00 +02:00
|
|
|
}
|
|
|
|
|
2015-10-14 21:49:33 +02:00
|
|
|
d.Set("website_endpoint", "")
|
|
|
|
d.Set("website_domain", "")
|
|
|
|
|
2015-04-26 01:36:00 +02:00
|
|
|
return nil
|
|
|
|
}
|
2015-04-29 21:47:21 +02:00
|
|
|
|
2015-06-03 17:10:17 +02:00
|
|
|
func websiteEndpoint(s3conn *s3.S3, d *schema.ResourceData) (*S3Website, error) {
|
2015-05-01 15:19:54 +02:00
|
|
|
// If the bucket doesn't have a website configuration, return an empty
|
|
|
|
// endpoint
|
2015-05-01 15:38:11 +02:00
|
|
|
if _, ok := d.GetOk("website"); !ok {
|
2015-06-03 17:10:17 +02:00
|
|
|
return nil, nil
|
2015-04-29 21:47:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
|
|
|
|
// Lookup the region for this bucket
|
2015-05-01 15:19:54 +02:00
|
|
|
location, err := s3conn.GetBucketLocation(
|
|
|
|
&s3.GetBucketLocationInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
},
|
|
|
|
)
|
2015-04-29 21:47:21 +02:00
|
|
|
if err != nil {
|
2015-06-03 17:10:17 +02:00
|
|
|
return nil, err
|
2015-04-29 21:47:21 +02:00
|
|
|
}
|
|
|
|
var region string
|
|
|
|
if location.LocationConstraint != nil {
|
|
|
|
region = *location.LocationConstraint
|
|
|
|
}
|
|
|
|
|
2015-06-03 17:10:17 +02:00
|
|
|
return WebsiteEndpoint(bucket, region), nil
|
2015-05-06 17:12:16 +02:00
|
|
|
}
|
|
|
|
|
2015-06-03 17:10:17 +02:00
|
|
|
func WebsiteEndpoint(bucket string, region string) *S3Website {
|
|
|
|
domain := WebsiteDomainUrl(region)
|
|
|
|
return &S3Website{Endpoint: fmt.Sprintf("%s.%s", bucket, domain), Domain: domain}
|
|
|
|
}
|
|
|
|
|
|
|
|
func WebsiteDomainUrl(region string) string {
|
2015-05-08 16:29:47 +02:00
|
|
|
region = normalizeRegion(region)
|
2015-06-07 11:39:18 +02:00
|
|
|
|
|
|
|
// Frankfurt(and probably future) regions uses different syntax for website endpoints
|
|
|
|
// http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteEndpoints.html
|
|
|
|
if region == "eu-central-1" {
|
2015-06-03 17:10:17 +02:00
|
|
|
return fmt.Sprintf("s3-website.%s.amazonaws.com", region)
|
2015-06-07 11:39:18 +02:00
|
|
|
}
|
|
|
|
|
2015-06-03 17:10:17 +02:00
|
|
|
return fmt.Sprintf("s3-website-%s.amazonaws.com", region)
|
2015-05-08 16:29:47 +02:00
|
|
|
}
|
|
|
|
|
2015-11-26 22:45:48 +01:00
|
|
|
func resourceAwsS3BucketAclUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
|
|
|
|
acl := d.Get("acl").(string)
|
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
|
|
|
|
i := &s3.PutBucketAclInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
ACL: aws.String(acl),
|
|
|
|
}
|
|
|
|
log.Printf("[DEBUG] S3 put bucket ACL: %#v", i)
|
|
|
|
|
|
|
|
_, err := s3conn.PutBucketAcl(i)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error putting S3 ACL: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-05 04:09:31 +02:00
|
|
|
func resourceAwsS3BucketVersioningUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
|
2015-09-05 10:19:52 +02:00
|
|
|
v := d.Get("versioning").(*schema.Set).List()
|
2015-08-05 04:09:31 +02:00
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
vc := &s3.VersioningConfiguration{}
|
2015-09-05 10:19:52 +02:00
|
|
|
|
|
|
|
if len(v) > 0 {
|
|
|
|
c := v[0].(map[string]interface{})
|
|
|
|
|
|
|
|
if c["enabled"].(bool) {
|
|
|
|
vc.Status = aws.String(s3.BucketVersioningStatusEnabled)
|
|
|
|
} else {
|
|
|
|
vc.Status = aws.String(s3.BucketVersioningStatusSuspended)
|
|
|
|
}
|
2015-08-05 04:09:31 +02:00
|
|
|
} else {
|
|
|
|
vc.Status = aws.String(s3.BucketVersioningStatusSuspended)
|
|
|
|
}
|
2015-09-05 10:19:52 +02:00
|
|
|
|
2015-08-05 04:09:31 +02:00
|
|
|
i := &s3.PutBucketVersioningInput{
|
|
|
|
Bucket: aws.String(bucket),
|
|
|
|
VersioningConfiguration: vc,
|
|
|
|
}
|
|
|
|
log.Printf("[DEBUG] S3 put bucket versioning: %#v", i)
|
|
|
|
|
|
|
|
_, err := s3conn.PutBucketVersioning(i)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error putting S3 versioning: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-05-16 12:11:23 +02:00
|
|
|
func normalizeJson(jsonString interface{}) string {
|
|
|
|
if jsonString == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
j := make(map[string]interface{})
|
|
|
|
err := json.Unmarshal([]byte(jsonString.(string)), &j)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Sprintf("Error parsing JSON: %s", err)
|
|
|
|
}
|
|
|
|
b, _ := json.Marshal(j)
|
|
|
|
return string(b[:])
|
|
|
|
}
|
|
|
|
|
2015-05-08 16:29:47 +02:00
|
|
|
func normalizeRegion(region string) string {
|
2015-05-01 15:50:49 +02:00
|
|
|
// Default to us-east-1 if the bucket doesn't have a region:
|
|
|
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETlocation.html
|
2015-04-29 21:47:21 +02:00
|
|
|
if region == "" {
|
|
|
|
region = "us-east-1"
|
|
|
|
}
|
|
|
|
|
2015-05-08 16:29:47 +02:00
|
|
|
return region
|
2015-04-29 21:47:21 +02:00
|
|
|
}
|
2015-06-03 17:10:17 +02:00
|
|
|
|
|
|
|
type S3Website struct {
|
|
|
|
Endpoint, Domain string
|
|
|
|
}
|