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