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