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