github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/aws/resource_aws_s3_bucket.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  
    13  	"github.com/aws/aws-sdk-go/aws"
    14  	"github.com/aws/aws-sdk-go/aws/awserr"
    15  	"github.com/aws/aws-sdk-go/service/s3"
    16  	"github.com/hashicorp/terraform/helper/hashcode"
    17  )
    18  
    19  func resourceAwsS3Bucket() *schema.Resource {
    20  	return &schema.Resource{
    21  		Create: resourceAwsS3BucketCreate,
    22  		Read:   resourceAwsS3BucketRead,
    23  		Update: resourceAwsS3BucketUpdate,
    24  		Delete: resourceAwsS3BucketDelete,
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"bucket": &schema.Schema{
    28  				Type:     schema.TypeString,
    29  				Required: true,
    30  				ForceNew: true,
    31  			},
    32  
    33  			"arn": &schema.Schema{
    34  				Type:     schema.TypeString,
    35  				Optional: true,
    36  				Computed: true,
    37  			},
    38  
    39  			"acl": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Default:  "private",
    42  				Optional: true,
    43  			},
    44  
    45  			"policy": &schema.Schema{
    46  				Type:      schema.TypeString,
    47  				Optional:  true,
    48  				StateFunc: normalizeJson,
    49  			},
    50  
    51  			"cors_rule": &schema.Schema{
    52  				Type:     schema.TypeList,
    53  				Optional: true,
    54  				Elem: &schema.Resource{
    55  					Schema: map[string]*schema.Schema{
    56  						"allowed_headers": &schema.Schema{
    57  							Type:     schema.TypeList,
    58  							Optional: true,
    59  							Elem:     &schema.Schema{Type: schema.TypeString},
    60  						},
    61  						"allowed_methods": &schema.Schema{
    62  							Type:     schema.TypeList,
    63  							Required: true,
    64  							Elem:     &schema.Schema{Type: schema.TypeString},
    65  						},
    66  						"allowed_origins": &schema.Schema{
    67  							Type:     schema.TypeList,
    68  							Required: true,
    69  							Elem:     &schema.Schema{Type: schema.TypeString},
    70  						},
    71  						"expose_headers": &schema.Schema{
    72  							Type:     schema.TypeList,
    73  							Optional: true,
    74  							Elem:     &schema.Schema{Type: schema.TypeString},
    75  						},
    76  						"max_age_seconds": &schema.Schema{
    77  							Type:     schema.TypeInt,
    78  							Optional: true,
    79  						},
    80  					},
    81  				},
    82  			},
    83  
    84  			"website": &schema.Schema{
    85  				Type:     schema.TypeList,
    86  				Optional: true,
    87  				Elem: &schema.Resource{
    88  					Schema: map[string]*schema.Schema{
    89  						"index_document": &schema.Schema{
    90  							Type:     schema.TypeString,
    91  							Optional: true,
    92  						},
    93  
    94  						"error_document": &schema.Schema{
    95  							Type:     schema.TypeString,
    96  							Optional: true,
    97  						},
    98  
    99  						"redirect_all_requests_to": &schema.Schema{
   100  							Type: schema.TypeString,
   101  							ConflictsWith: []string{
   102  								"website.0.index_document",
   103  								"website.0.error_document",
   104  							},
   105  							Optional: true,
   106  						},
   107  					},
   108  				},
   109  			},
   110  
   111  			"hosted_zone_id": &schema.Schema{
   112  				Type:     schema.TypeString,
   113  				Optional: true,
   114  				Computed: true,
   115  			},
   116  
   117  			"region": &schema.Schema{
   118  				Type:     schema.TypeString,
   119  				Optional: true,
   120  				Computed: true,
   121  			},
   122  			"website_endpoint": &schema.Schema{
   123  				Type:     schema.TypeString,
   124  				Optional: true,
   125  				Computed: true,
   126  			},
   127  			"website_domain": &schema.Schema{
   128  				Type:     schema.TypeString,
   129  				Optional: true,
   130  				Computed: true,
   131  			},
   132  
   133  			"versioning": &schema.Schema{
   134  				Type:     schema.TypeSet,
   135  				Optional: true,
   136  				Elem: &schema.Resource{
   137  					Schema: map[string]*schema.Schema{
   138  						"enabled": &schema.Schema{
   139  							Type:     schema.TypeBool,
   140  							Optional: true,
   141  							Default:  false,
   142  						},
   143  					},
   144  				},
   145  				Set: func(v interface{}) int {
   146  					var buf bytes.Buffer
   147  					m := v.(map[string]interface{})
   148  					buf.WriteString(fmt.Sprintf("%t-", m["enabled"].(bool)))
   149  
   150  					return hashcode.String(buf.String())
   151  				},
   152  			},
   153  
   154  			"logging": &schema.Schema{
   155  				Type:     schema.TypeSet,
   156  				Optional: true,
   157  				Elem: &schema.Resource{
   158  					Schema: map[string]*schema.Schema{
   159  						"target_bucket": &schema.Schema{
   160  							Type:     schema.TypeString,
   161  							Required: true,
   162  						},
   163  						"target_prefix": &schema.Schema{
   164  							Type:     schema.TypeString,
   165  							Optional: true,
   166  						},
   167  					},
   168  				},
   169  				Set: func(v interface{}) int {
   170  					var buf bytes.Buffer
   171  					m := v.(map[string]interface{})
   172  					buf.WriteString(fmt.Sprintf("%s-", m["target_bucket"]))
   173  					buf.WriteString(fmt.Sprintf("%s-", m["target_prefix"]))
   174  					return hashcode.String(buf.String())
   175  				},
   176  			},
   177  
   178  			"tags": tagsSchema(),
   179  
   180  			"force_destroy": &schema.Schema{
   181  				Type:     schema.TypeBool,
   182  				Optional: true,
   183  				Default:  false,
   184  			},
   185  		},
   186  	}
   187  }
   188  
   189  func resourceAwsS3BucketCreate(d *schema.ResourceData, meta interface{}) error {
   190  	s3conn := meta.(*AWSClient).s3conn
   191  	awsRegion := meta.(*AWSClient).region
   192  
   193  	// Get the bucket and acl
   194  	bucket := d.Get("bucket").(string)
   195  	acl := d.Get("acl").(string)
   196  
   197  	log.Printf("[DEBUG] S3 bucket create: %s, ACL: %s", bucket, acl)
   198  
   199  	req := &s3.CreateBucketInput{
   200  		Bucket: aws.String(bucket),
   201  		ACL:    aws.String(acl),
   202  	}
   203  
   204  	// Special case us-east-1 region and do not set the LocationConstraint.
   205  	// See "Request Elements: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUT.html
   206  	if awsRegion != "us-east-1" {
   207  		req.CreateBucketConfiguration = &s3.CreateBucketConfiguration{
   208  			LocationConstraint: aws.String(awsRegion),
   209  		}
   210  	}
   211  
   212  	_, err := s3conn.CreateBucket(req)
   213  	if err != nil {
   214  		return fmt.Errorf("Error creating S3 bucket: %s", err)
   215  	}
   216  
   217  	// Assign the bucket name as the resource ID
   218  	d.SetId(bucket)
   219  
   220  	return resourceAwsS3BucketUpdate(d, meta)
   221  }
   222  
   223  func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error {
   224  	s3conn := meta.(*AWSClient).s3conn
   225  	if err := setTagsS3(s3conn, d); err != nil {
   226  		return err
   227  	}
   228  
   229  	if d.HasChange("policy") {
   230  		if err := resourceAwsS3BucketPolicyUpdate(s3conn, d); err != nil {
   231  			return err
   232  		}
   233  	}
   234  
   235  	if d.HasChange("cors_rule") {
   236  		if err := resourceAwsS3BucketCorsUpdate(s3conn, d); err != nil {
   237  			return err
   238  		}
   239  	}
   240  
   241  	if d.HasChange("website") {
   242  		if err := resourceAwsS3BucketWebsiteUpdate(s3conn, d); err != nil {
   243  			return err
   244  		}
   245  	}
   246  
   247  	if d.HasChange("versioning") {
   248  		if err := resourceAwsS3BucketVersioningUpdate(s3conn, d); err != nil {
   249  			return err
   250  		}
   251  	}
   252  	if d.HasChange("acl") {
   253  		if err := resourceAwsS3BucketAclUpdate(s3conn, d); err != nil {
   254  			return err
   255  		}
   256  	}
   257  
   258  	if d.HasChange("logging") {
   259  		if err := resourceAwsS3BucketLoggingUpdate(s3conn, d); err != nil {
   260  			return err
   261  		}
   262  	}
   263  
   264  	return resourceAwsS3BucketRead(d, meta)
   265  }
   266  
   267  func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error {
   268  	s3conn := meta.(*AWSClient).s3conn
   269  
   270  	var err error
   271  	_, err = s3conn.HeadBucket(&s3.HeadBucketInput{
   272  		Bucket: aws.String(d.Id()),
   273  	})
   274  	if err != nil {
   275  		if awsError, ok := err.(awserr.RequestFailure); ok && awsError.StatusCode() == 404 {
   276  			log.Printf("[WARN] S3 Bucket (%s) not found, error code (404)", d.Id())
   277  			d.SetId("")
   278  			return nil
   279  		} else {
   280  			// some of the AWS SDK's errors can be empty strings, so let's add
   281  			// some additional context.
   282  			return fmt.Errorf("error reading S3 bucket \"%s\": %s", d.Id(), err)
   283  		}
   284  	}
   285  
   286  	// Read the policy
   287  	pol, err := s3conn.GetBucketPolicy(&s3.GetBucketPolicyInput{
   288  		Bucket: aws.String(d.Id()),
   289  	})
   290  	log.Printf("[DEBUG] S3 bucket: %s, read policy: %v", d.Id(), pol)
   291  	if err != nil {
   292  		if err := d.Set("policy", ""); err != nil {
   293  			return err
   294  		}
   295  	} else {
   296  		if v := pol.Policy; v == nil {
   297  			if err := d.Set("policy", ""); err != nil {
   298  				return err
   299  			}
   300  		} else if err := d.Set("policy", normalizeJson(*v)); err != nil {
   301  			return err
   302  		}
   303  	}
   304  
   305  	// Read the CORS
   306  	cors, err := s3conn.GetBucketCors(&s3.GetBucketCorsInput{
   307  		Bucket: aws.String(d.Id()),
   308  	})
   309  	log.Printf("[DEBUG] S3 bucket: %s, read CORS: %v", d.Id(), cors)
   310  	if err != nil {
   311  		rules := make([]map[string]interface{}, 0, len(cors.CORSRules))
   312  		for _, ruleObject := range cors.CORSRules {
   313  			rule := make(map[string]interface{})
   314  			rule["allowed_headers"] = ruleObject.AllowedHeaders
   315  			rule["allowed_methods"] = ruleObject.AllowedMethods
   316  			rule["allowed_origins"] = ruleObject.AllowedOrigins
   317  			rule["expose_headers"] = ruleObject.ExposeHeaders
   318  			rule["max_age_seconds"] = ruleObject.MaxAgeSeconds
   319  			rules = append(rules, rule)
   320  		}
   321  		if err := d.Set("cors_rule", rules); err != nil {
   322  			return fmt.Errorf("error reading S3 bucket \"%s\" CORS rules: %s", d.Id(), err)
   323  		}
   324  	}
   325  
   326  	// Read the website configuration
   327  	ws, err := s3conn.GetBucketWebsite(&s3.GetBucketWebsiteInput{
   328  		Bucket: aws.String(d.Id()),
   329  	})
   330  	var websites []map[string]interface{}
   331  	if err == nil {
   332  		w := make(map[string]interface{})
   333  
   334  		if v := ws.IndexDocument; v != nil {
   335  			w["index_document"] = *v.Suffix
   336  		}
   337  
   338  		if v := ws.ErrorDocument; v != nil {
   339  			w["error_document"] = *v.Key
   340  		}
   341  
   342  		if v := ws.RedirectAllRequestsTo; v != nil {
   343  			w["redirect_all_requests_to"] = *v.HostName
   344  		}
   345  
   346  		websites = append(websites, w)
   347  	}
   348  	if err := d.Set("website", websites); err != nil {
   349  		return err
   350  	}
   351  
   352  	// Read the versioning configuration
   353  	versioning, err := s3conn.GetBucketVersioning(&s3.GetBucketVersioningInput{
   354  		Bucket: aws.String(d.Id()),
   355  	})
   356  	if err != nil {
   357  		return err
   358  	}
   359  	log.Printf("[DEBUG] S3 Bucket: %s, versioning: %v", d.Id(), versioning)
   360  	if versioning.Status != nil && *versioning.Status == s3.BucketVersioningStatusEnabled {
   361  		vcl := make([]map[string]interface{}, 0, 1)
   362  		vc := make(map[string]interface{})
   363  		if *versioning.Status == s3.BucketVersioningStatusEnabled {
   364  			vc["enabled"] = true
   365  		} else {
   366  			vc["enabled"] = false
   367  		}
   368  		vcl = append(vcl, vc)
   369  		if err := d.Set("versioning", vcl); err != nil {
   370  			return err
   371  		}
   372  	}
   373  
   374  	// Read the logging configuration
   375  	logging, err := s3conn.GetBucketLogging(&s3.GetBucketLoggingInput{
   376  		Bucket: aws.String(d.Id()),
   377  	})
   378  	if err != nil {
   379  		return err
   380  	}
   381  	log.Printf("[DEBUG] S3 Bucket: %s, logging: %v", d.Id(), logging)
   382  	if v := logging.LoggingEnabled; v != nil {
   383  		lcl := make([]map[string]interface{}, 0, 1)
   384  		lc := make(map[string]interface{})
   385  		if *v.TargetBucket != "" {
   386  			lc["target_bucket"] = *v.TargetBucket
   387  		}
   388  		if *v.TargetPrefix != "" {
   389  			lc["target_prefix"] = *v.TargetPrefix
   390  		}
   391  		lcl = append(lcl, lc)
   392  		if err := d.Set("logging", lcl); err != nil {
   393  			return err
   394  		}
   395  	}
   396  
   397  	// Add the region as an attribute
   398  	location, err := s3conn.GetBucketLocation(
   399  		&s3.GetBucketLocationInput{
   400  			Bucket: aws.String(d.Id()),
   401  		},
   402  	)
   403  	if err != nil {
   404  		return err
   405  	}
   406  	var region string
   407  	if location.LocationConstraint != nil {
   408  		region = *location.LocationConstraint
   409  	}
   410  	region = normalizeRegion(region)
   411  	if err := d.Set("region", region); err != nil {
   412  		return err
   413  	}
   414  
   415  	// Add the hosted zone ID for this bucket's region as an attribute
   416  	hostedZoneID := HostedZoneIDForRegion(region)
   417  	if err := d.Set("hosted_zone_id", hostedZoneID); err != nil {
   418  		return err
   419  	}
   420  
   421  	// Add website_endpoint as an attribute
   422  	websiteEndpoint, err := websiteEndpoint(s3conn, d)
   423  	if err != nil {
   424  		return err
   425  	}
   426  	if websiteEndpoint != nil {
   427  		if err := d.Set("website_endpoint", websiteEndpoint.Endpoint); err != nil {
   428  			return err
   429  		}
   430  		if err := d.Set("website_domain", websiteEndpoint.Domain); err != nil {
   431  			return err
   432  		}
   433  	}
   434  
   435  	tagSet, err := getTagSetS3(s3conn, d.Id())
   436  	if err != nil {
   437  		return err
   438  	}
   439  
   440  	if err := d.Set("tags", tagsToMapS3(tagSet)); err != nil {
   441  		return err
   442  	}
   443  
   444  	d.Set("arn", fmt.Sprint("arn:aws:s3:::", d.Id()))
   445  
   446  	return nil
   447  }
   448  
   449  func resourceAwsS3BucketDelete(d *schema.ResourceData, meta interface{}) error {
   450  	s3conn := meta.(*AWSClient).s3conn
   451  
   452  	log.Printf("[DEBUG] S3 Delete Bucket: %s", d.Id())
   453  	_, err := s3conn.DeleteBucket(&s3.DeleteBucketInput{
   454  		Bucket: aws.String(d.Id()),
   455  	})
   456  	if err != nil {
   457  		ec2err, ok := err.(awserr.Error)
   458  		if ok && ec2err.Code() == "BucketNotEmpty" {
   459  			if d.Get("force_destroy").(bool) {
   460  				// bucket may have things delete them
   461  				log.Printf("[DEBUG] S3 Bucket attempting to forceDestroy %+v", err)
   462  
   463  				bucket := d.Get("bucket").(string)
   464  				resp, err := s3conn.ListObjectVersions(
   465  					&s3.ListObjectVersionsInput{
   466  						Bucket: aws.String(bucket),
   467  					},
   468  				)
   469  
   470  				if err != nil {
   471  					return fmt.Errorf("Error S3 Bucket list Object Versions err: %s", err)
   472  				}
   473  
   474  				objectsToDelete := make([]*s3.ObjectIdentifier, 0)
   475  
   476  				if len(resp.DeleteMarkers) != 0 {
   477  
   478  					for _, v := range resp.DeleteMarkers {
   479  						objectsToDelete = append(objectsToDelete, &s3.ObjectIdentifier{
   480  							Key:       v.Key,
   481  							VersionId: v.VersionId,
   482  						})
   483  					}
   484  				}
   485  
   486  				if len(resp.Versions) != 0 {
   487  					for _, v := range resp.Versions {
   488  						objectsToDelete = append(objectsToDelete, &s3.ObjectIdentifier{
   489  							Key:       v.Key,
   490  							VersionId: v.VersionId,
   491  						})
   492  					}
   493  				}
   494  
   495  				params := &s3.DeleteObjectsInput{
   496  					Bucket: aws.String(bucket),
   497  					Delete: &s3.Delete{
   498  						Objects: objectsToDelete,
   499  					},
   500  				}
   501  
   502  				_, err = s3conn.DeleteObjects(params)
   503  
   504  				if err != nil {
   505  					return fmt.Errorf("Error S3 Bucket force_destroy error deleting: %s", err)
   506  				}
   507  
   508  				// this line recurses until all objects are deleted or an error is returned
   509  				return resourceAwsS3BucketDelete(d, meta)
   510  			}
   511  		}
   512  		return fmt.Errorf("Error deleting S3 Bucket: %s", err)
   513  	}
   514  	return nil
   515  }
   516  
   517  func resourceAwsS3BucketPolicyUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   518  	bucket := d.Get("bucket").(string)
   519  	policy := d.Get("policy").(string)
   520  
   521  	if policy != "" {
   522  		log.Printf("[DEBUG] S3 bucket: %s, put policy: %s", bucket, policy)
   523  
   524  		params := &s3.PutBucketPolicyInput{
   525  			Bucket: aws.String(bucket),
   526  			Policy: aws.String(policy),
   527  		}
   528  
   529  		err := resource.Retry(1*time.Minute, func() error {
   530  			if _, err := s3conn.PutBucketPolicy(params); err != nil {
   531  				if awserr, ok := err.(awserr.Error); ok {
   532  					if awserr.Code() == "MalformedPolicy" {
   533  						// Retryable
   534  						return awserr
   535  					}
   536  				}
   537  				// Not retryable
   538  				return resource.RetryError{Err: err}
   539  			}
   540  			// No error
   541  			return nil
   542  		})
   543  
   544  		if err != nil {
   545  			return fmt.Errorf("Error putting S3 policy: %s", err)
   546  		}
   547  	} else {
   548  		log.Printf("[DEBUG] S3 bucket: %s, delete policy: %s", bucket, policy)
   549  		_, err := s3conn.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{
   550  			Bucket: aws.String(bucket),
   551  		})
   552  
   553  		if err != nil {
   554  			return fmt.Errorf("Error deleting S3 policy: %s", err)
   555  		}
   556  	}
   557  
   558  	return nil
   559  }
   560  
   561  func resourceAwsS3BucketCorsUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   562  	bucket := d.Get("bucket").(string)
   563  	rawCors := d.Get("cors_rule").([]interface{})
   564  
   565  	if len(rawCors) == 0 {
   566  		// Delete CORS
   567  		log.Printf("[DEBUG] S3 bucket: %s, delete CORS", bucket)
   568  		_, err := s3conn.DeleteBucketCors(&s3.DeleteBucketCorsInput{
   569  			Bucket: aws.String(bucket),
   570  		})
   571  		if err != nil {
   572  			return fmt.Errorf("Error deleting S3 CORS: %s", err)
   573  		}
   574  	} else {
   575  		// Put CORS
   576  		rules := make([]*s3.CORSRule, 0, len(rawCors))
   577  		for _, cors := range rawCors {
   578  			corsMap := cors.(map[string]interface{})
   579  			r := &s3.CORSRule{}
   580  			for k, v := range corsMap {
   581  				log.Printf("[DEBUG] S3 bucket: %s, put CORS: %#v, %#v", bucket, k, v)
   582  				if k == "max_age_seconds" {
   583  					r.MaxAgeSeconds = aws.Int64(int64(v.(int)))
   584  				} else {
   585  					vMap := make([]*string, len(v.([]interface{})))
   586  					for i, vv := range v.([]interface{}) {
   587  						str := vv.(string)
   588  						vMap[i] = aws.String(str)
   589  					}
   590  					switch k {
   591  					case "allowed_headers":
   592  						r.AllowedHeaders = vMap
   593  					case "allowed_methods":
   594  						r.AllowedMethods = vMap
   595  					case "allowed_origins":
   596  						r.AllowedOrigins = vMap
   597  					case "expose_headers":
   598  						r.ExposeHeaders = vMap
   599  					}
   600  				}
   601  			}
   602  			rules = append(rules, r)
   603  		}
   604  		corsInput := &s3.PutBucketCorsInput{
   605  			Bucket: aws.String(bucket),
   606  			CORSConfiguration: &s3.CORSConfiguration{
   607  				CORSRules: rules,
   608  			},
   609  		}
   610  		log.Printf("[DEBUG] S3 bucket: %s, put CORS: %#v", bucket, corsInput)
   611  		_, err := s3conn.PutBucketCors(corsInput)
   612  		if err != nil {
   613  			return fmt.Errorf("Error putting S3 CORS: %s", err)
   614  		}
   615  	}
   616  
   617  	return nil
   618  }
   619  
   620  func resourceAwsS3BucketWebsiteUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   621  	ws := d.Get("website").([]interface{})
   622  
   623  	if len(ws) == 1 {
   624  		w := ws[0].(map[string]interface{})
   625  		return resourceAwsS3BucketWebsitePut(s3conn, d, w)
   626  	} else if len(ws) == 0 {
   627  		return resourceAwsS3BucketWebsiteDelete(s3conn, d)
   628  	} else {
   629  		return fmt.Errorf("Cannot specify more than one website.")
   630  	}
   631  }
   632  
   633  func resourceAwsS3BucketWebsitePut(s3conn *s3.S3, d *schema.ResourceData, website map[string]interface{}) error {
   634  	bucket := d.Get("bucket").(string)
   635  
   636  	indexDocument := website["index_document"].(string)
   637  	errorDocument := website["error_document"].(string)
   638  	redirectAllRequestsTo := website["redirect_all_requests_to"].(string)
   639  
   640  	if indexDocument == "" && redirectAllRequestsTo == "" {
   641  		return fmt.Errorf("Must specify either index_document or redirect_all_requests_to.")
   642  	}
   643  
   644  	websiteConfiguration := &s3.WebsiteConfiguration{}
   645  
   646  	if indexDocument != "" {
   647  		websiteConfiguration.IndexDocument = &s3.IndexDocument{Suffix: aws.String(indexDocument)}
   648  	}
   649  
   650  	if errorDocument != "" {
   651  		websiteConfiguration.ErrorDocument = &s3.ErrorDocument{Key: aws.String(errorDocument)}
   652  	}
   653  
   654  	if redirectAllRequestsTo != "" {
   655  		websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectAllRequestsTo)}
   656  	}
   657  
   658  	putInput := &s3.PutBucketWebsiteInput{
   659  		Bucket:               aws.String(bucket),
   660  		WebsiteConfiguration: websiteConfiguration,
   661  	}
   662  
   663  	log.Printf("[DEBUG] S3 put bucket website: %#v", putInput)
   664  
   665  	_, err := s3conn.PutBucketWebsite(putInput)
   666  	if err != nil {
   667  		return fmt.Errorf("Error putting S3 website: %s", err)
   668  	}
   669  
   670  	return nil
   671  }
   672  
   673  func resourceAwsS3BucketWebsiteDelete(s3conn *s3.S3, d *schema.ResourceData) error {
   674  	bucket := d.Get("bucket").(string)
   675  	deleteInput := &s3.DeleteBucketWebsiteInput{Bucket: aws.String(bucket)}
   676  
   677  	log.Printf("[DEBUG] S3 delete bucket website: %#v", deleteInput)
   678  
   679  	_, err := s3conn.DeleteBucketWebsite(deleteInput)
   680  	if err != nil {
   681  		return fmt.Errorf("Error deleting S3 website: %s", err)
   682  	}
   683  
   684  	d.Set("website_endpoint", "")
   685  	d.Set("website_domain", "")
   686  
   687  	return nil
   688  }
   689  
   690  func websiteEndpoint(s3conn *s3.S3, d *schema.ResourceData) (*S3Website, error) {
   691  	// If the bucket doesn't have a website configuration, return an empty
   692  	// endpoint
   693  	if _, ok := d.GetOk("website"); !ok {
   694  		return nil, nil
   695  	}
   696  
   697  	bucket := d.Get("bucket").(string)
   698  
   699  	// Lookup the region for this bucket
   700  	location, err := s3conn.GetBucketLocation(
   701  		&s3.GetBucketLocationInput{
   702  			Bucket: aws.String(bucket),
   703  		},
   704  	)
   705  	if err != nil {
   706  		return nil, err
   707  	}
   708  	var region string
   709  	if location.LocationConstraint != nil {
   710  		region = *location.LocationConstraint
   711  	}
   712  
   713  	return WebsiteEndpoint(bucket, region), nil
   714  }
   715  
   716  func WebsiteEndpoint(bucket string, region string) *S3Website {
   717  	domain := WebsiteDomainUrl(region)
   718  	return &S3Website{Endpoint: fmt.Sprintf("%s.%s", bucket, domain), Domain: domain}
   719  }
   720  
   721  func WebsiteDomainUrl(region string) string {
   722  	region = normalizeRegion(region)
   723  
   724  	// Frankfurt(and probably future) regions uses different syntax for website endpoints
   725  	// http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteEndpoints.html
   726  	if region == "eu-central-1" {
   727  		return fmt.Sprintf("s3-website.%s.amazonaws.com", region)
   728  	}
   729  
   730  	return fmt.Sprintf("s3-website-%s.amazonaws.com", region)
   731  }
   732  
   733  func resourceAwsS3BucketAclUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   734  	acl := d.Get("acl").(string)
   735  	bucket := d.Get("bucket").(string)
   736  
   737  	i := &s3.PutBucketAclInput{
   738  		Bucket: aws.String(bucket),
   739  		ACL:    aws.String(acl),
   740  	}
   741  	log.Printf("[DEBUG] S3 put bucket ACL: %#v", i)
   742  
   743  	_, err := s3conn.PutBucketAcl(i)
   744  	if err != nil {
   745  		return fmt.Errorf("Error putting S3 ACL: %s", err)
   746  	}
   747  
   748  	return nil
   749  }
   750  
   751  func resourceAwsS3BucketVersioningUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   752  	v := d.Get("versioning").(*schema.Set).List()
   753  	bucket := d.Get("bucket").(string)
   754  	vc := &s3.VersioningConfiguration{}
   755  
   756  	if len(v) > 0 {
   757  		c := v[0].(map[string]interface{})
   758  
   759  		if c["enabled"].(bool) {
   760  			vc.Status = aws.String(s3.BucketVersioningStatusEnabled)
   761  		} else {
   762  			vc.Status = aws.String(s3.BucketVersioningStatusSuspended)
   763  		}
   764  	} else {
   765  		vc.Status = aws.String(s3.BucketVersioningStatusSuspended)
   766  	}
   767  
   768  	i := &s3.PutBucketVersioningInput{
   769  		Bucket:                  aws.String(bucket),
   770  		VersioningConfiguration: vc,
   771  	}
   772  	log.Printf("[DEBUG] S3 put bucket versioning: %#v", i)
   773  
   774  	_, err := s3conn.PutBucketVersioning(i)
   775  	if err != nil {
   776  		return fmt.Errorf("Error putting S3 versioning: %s", err)
   777  	}
   778  
   779  	return nil
   780  }
   781  
   782  func resourceAwsS3BucketLoggingUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   783  	logging := d.Get("logging").(*schema.Set).List()
   784  	bucket := d.Get("bucket").(string)
   785  	loggingStatus := &s3.BucketLoggingStatus{}
   786  
   787  	if len(logging) > 0 {
   788  		c := logging[0].(map[string]interface{})
   789  
   790  		loggingEnabled := &s3.LoggingEnabled{}
   791  		if val, ok := c["target_bucket"]; ok {
   792  			loggingEnabled.TargetBucket = aws.String(val.(string))
   793  		}
   794  		if val, ok := c["target_prefix"]; ok {
   795  			loggingEnabled.TargetPrefix = aws.String(val.(string))
   796  		}
   797  
   798  		loggingStatus.LoggingEnabled = loggingEnabled
   799  	}
   800  
   801  	i := &s3.PutBucketLoggingInput{
   802  		Bucket:              aws.String(bucket),
   803  		BucketLoggingStatus: loggingStatus,
   804  	}
   805  	log.Printf("[DEBUG] S3 put bucket logging: %#v", i)
   806  
   807  	_, err := s3conn.PutBucketLogging(i)
   808  	if err != nil {
   809  		return fmt.Errorf("Error putting S3 logging: %s", err)
   810  	}
   811  
   812  	return nil
   813  }
   814  
   815  func normalizeJson(jsonString interface{}) string {
   816  	if jsonString == nil {
   817  		return ""
   818  	}
   819  	j := make(map[string]interface{})
   820  	err := json.Unmarshal([]byte(jsonString.(string)), &j)
   821  	if err != nil {
   822  		return fmt.Sprintf("Error parsing JSON: %s", err)
   823  	}
   824  	b, _ := json.Marshal(j)
   825  	return string(b[:])
   826  }
   827  
   828  func normalizeRegion(region string) string {
   829  	// Default to us-east-1 if the bucket doesn't have a region:
   830  	// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETlocation.html
   831  	if region == "" {
   832  		region = "us-east-1"
   833  	}
   834  
   835  	return region
   836  }
   837  
   838  type S3Website struct {
   839  	Endpoint, Domain string
   840  }