github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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() *resource.RetryError {
   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 resource.RetryableError(
   227  					fmt.Errorf("[WARN] Error creating S3 bucket %s, retrying: %s",
   228  						bucket, err))
   229  			}
   230  		}
   231  		if err != nil {
   232  			return resource.NonRetryableError(err)
   233  		}
   234  
   235  		return nil
   236  	})
   237  
   238  	if err != nil {
   239  		return fmt.Errorf("Error creating S3 bucket: %s", err)
   240  	}
   241  
   242  	// Assign the bucket name as the resource ID
   243  	d.SetId(bucket)
   244  
   245  	return resourceAwsS3BucketUpdate(d, meta)
   246  }
   247  
   248  func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error {
   249  	s3conn := meta.(*AWSClient).s3conn
   250  	if err := setTagsS3(s3conn, d); err != nil {
   251  		return err
   252  	}
   253  
   254  	if d.HasChange("policy") {
   255  		if err := resourceAwsS3BucketPolicyUpdate(s3conn, d); err != nil {
   256  			return err
   257  		}
   258  	}
   259  
   260  	if d.HasChange("cors_rule") {
   261  		if err := resourceAwsS3BucketCorsUpdate(s3conn, d); err != nil {
   262  			return err
   263  		}
   264  	}
   265  
   266  	if d.HasChange("website") {
   267  		if err := resourceAwsS3BucketWebsiteUpdate(s3conn, d); err != nil {
   268  			return err
   269  		}
   270  	}
   271  
   272  	if d.HasChange("versioning") {
   273  		if err := resourceAwsS3BucketVersioningUpdate(s3conn, d); err != nil {
   274  			return err
   275  		}
   276  	}
   277  	if d.HasChange("acl") {
   278  		if err := resourceAwsS3BucketAclUpdate(s3conn, d); err != nil {
   279  			return err
   280  		}
   281  	}
   282  
   283  	if d.HasChange("logging") {
   284  		if err := resourceAwsS3BucketLoggingUpdate(s3conn, d); err != nil {
   285  			return err
   286  		}
   287  	}
   288  
   289  	return resourceAwsS3BucketRead(d, meta)
   290  }
   291  
   292  func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error {
   293  	s3conn := meta.(*AWSClient).s3conn
   294  
   295  	var err error
   296  	_, err = s3conn.HeadBucket(&s3.HeadBucketInput{
   297  		Bucket: aws.String(d.Id()),
   298  	})
   299  	if err != nil {
   300  		if awsError, ok := err.(awserr.RequestFailure); ok && awsError.StatusCode() == 404 {
   301  			log.Printf("[WARN] S3 Bucket (%s) not found, error code (404)", d.Id())
   302  			d.SetId("")
   303  			return nil
   304  		} else {
   305  			// some of the AWS SDK's errors can be empty strings, so let's add
   306  			// some additional context.
   307  			return fmt.Errorf("error reading S3 bucket \"%s\": %s", d.Id(), err)
   308  		}
   309  	}
   310  
   311  	// Read the policy
   312  	pol, err := s3conn.GetBucketPolicy(&s3.GetBucketPolicyInput{
   313  		Bucket: aws.String(d.Id()),
   314  	})
   315  	log.Printf("[DEBUG] S3 bucket: %s, read policy: %v", d.Id(), pol)
   316  	if err != nil {
   317  		if err := d.Set("policy", ""); err != nil {
   318  			return err
   319  		}
   320  	} else {
   321  		if v := pol.Policy; v == nil {
   322  			if err := d.Set("policy", ""); err != nil {
   323  				return err
   324  			}
   325  		} else if err := d.Set("policy", normalizeJson(*v)); err != nil {
   326  			return err
   327  		}
   328  	}
   329  
   330  	// Read the CORS
   331  	cors, err := s3conn.GetBucketCors(&s3.GetBucketCorsInput{
   332  		Bucket: aws.String(d.Id()),
   333  	})
   334  	log.Printf("[DEBUG] S3 bucket: %s, read CORS: %v", d.Id(), cors)
   335  	if err != nil {
   336  		rules := make([]map[string]interface{}, 0, len(cors.CORSRules))
   337  		for _, ruleObject := range cors.CORSRules {
   338  			rule := make(map[string]interface{})
   339  			rule["allowed_headers"] = ruleObject.AllowedHeaders
   340  			rule["allowed_methods"] = ruleObject.AllowedMethods
   341  			rule["allowed_origins"] = ruleObject.AllowedOrigins
   342  			rule["expose_headers"] = ruleObject.ExposeHeaders
   343  			rule["max_age_seconds"] = ruleObject.MaxAgeSeconds
   344  			rules = append(rules, rule)
   345  		}
   346  		if err := d.Set("cors_rule", rules); err != nil {
   347  			return fmt.Errorf("error reading S3 bucket \"%s\" CORS rules: %s", d.Id(), err)
   348  		}
   349  	}
   350  
   351  	// Read the website configuration
   352  	ws, err := s3conn.GetBucketWebsite(&s3.GetBucketWebsiteInput{
   353  		Bucket: aws.String(d.Id()),
   354  	})
   355  	var websites []map[string]interface{}
   356  	if err == nil {
   357  		w := make(map[string]interface{})
   358  
   359  		if v := ws.IndexDocument; v != nil {
   360  			w["index_document"] = *v.Suffix
   361  		}
   362  
   363  		if v := ws.ErrorDocument; v != nil {
   364  			w["error_document"] = *v.Key
   365  		}
   366  
   367  		if v := ws.RedirectAllRequestsTo; v != nil {
   368  			if v.Protocol == nil {
   369  				w["redirect_all_requests_to"] = *v.HostName
   370  			} else {
   371  				w["redirect_all_requests_to"] = (&url.URL{
   372  					Host:   *v.HostName,
   373  					Scheme: *v.Protocol,
   374  				}).String()
   375  			}
   376  		}
   377  
   378  		if v := ws.RoutingRules; v != nil {
   379  			rr, err := normalizeRoutingRules(v)
   380  			if err != nil {
   381  				return fmt.Errorf("Error while marshaling routing rules: %s", err)
   382  			}
   383  			w["routing_rules"] = rr
   384  		}
   385  
   386  		websites = append(websites, w)
   387  	}
   388  	if err := d.Set("website", websites); err != nil {
   389  		return err
   390  	}
   391  
   392  	// Read the versioning configuration
   393  	versioning, err := s3conn.GetBucketVersioning(&s3.GetBucketVersioningInput{
   394  		Bucket: aws.String(d.Id()),
   395  	})
   396  	if err != nil {
   397  		return err
   398  	}
   399  	log.Printf("[DEBUG] S3 Bucket: %s, versioning: %v", d.Id(), versioning)
   400  	if versioning.Status != nil && *versioning.Status == s3.BucketVersioningStatusEnabled {
   401  		vcl := make([]map[string]interface{}, 0, 1)
   402  		vc := make(map[string]interface{})
   403  		if *versioning.Status == s3.BucketVersioningStatusEnabled {
   404  			vc["enabled"] = true
   405  		} else {
   406  			vc["enabled"] = false
   407  		}
   408  		vcl = append(vcl, vc)
   409  		if err := d.Set("versioning", vcl); err != nil {
   410  			return err
   411  		}
   412  	}
   413  
   414  	// Read the logging configuration
   415  	logging, err := s3conn.GetBucketLogging(&s3.GetBucketLoggingInput{
   416  		Bucket: aws.String(d.Id()),
   417  	})
   418  	if err != nil {
   419  		return err
   420  	}
   421  	log.Printf("[DEBUG] S3 Bucket: %s, logging: %v", d.Id(), logging)
   422  	if v := logging.LoggingEnabled; v != nil {
   423  		lcl := make([]map[string]interface{}, 0, 1)
   424  		lc := make(map[string]interface{})
   425  		if *v.TargetBucket != "" {
   426  			lc["target_bucket"] = *v.TargetBucket
   427  		}
   428  		if *v.TargetPrefix != "" {
   429  			lc["target_prefix"] = *v.TargetPrefix
   430  		}
   431  		lcl = append(lcl, lc)
   432  		if err := d.Set("logging", lcl); err != nil {
   433  			return err
   434  		}
   435  	}
   436  
   437  	// Add the region as an attribute
   438  	location, err := s3conn.GetBucketLocation(
   439  		&s3.GetBucketLocationInput{
   440  			Bucket: aws.String(d.Id()),
   441  		},
   442  	)
   443  	if err != nil {
   444  		return err
   445  	}
   446  	var region string
   447  	if location.LocationConstraint != nil {
   448  		region = *location.LocationConstraint
   449  	}
   450  	region = normalizeRegion(region)
   451  	if err := d.Set("region", region); err != nil {
   452  		return err
   453  	}
   454  
   455  	// Add the hosted zone ID for this bucket's region as an attribute
   456  	hostedZoneID := HostedZoneIDForRegion(region)
   457  	if err := d.Set("hosted_zone_id", hostedZoneID); err != nil {
   458  		return err
   459  	}
   460  
   461  	// Add website_endpoint as an attribute
   462  	websiteEndpoint, err := websiteEndpoint(s3conn, d)
   463  	if err != nil {
   464  		return err
   465  	}
   466  	if websiteEndpoint != nil {
   467  		if err := d.Set("website_endpoint", websiteEndpoint.Endpoint); err != nil {
   468  			return err
   469  		}
   470  		if err := d.Set("website_domain", websiteEndpoint.Domain); err != nil {
   471  			return err
   472  		}
   473  	}
   474  
   475  	tagSet, err := getTagSetS3(s3conn, d.Id())
   476  	if err != nil {
   477  		return err
   478  	}
   479  
   480  	if err := d.Set("tags", tagsToMapS3(tagSet)); err != nil {
   481  		return err
   482  	}
   483  
   484  	d.Set("arn", fmt.Sprint("arn:aws:s3:::", d.Id()))
   485  
   486  	return nil
   487  }
   488  
   489  func resourceAwsS3BucketDelete(d *schema.ResourceData, meta interface{}) error {
   490  	s3conn := meta.(*AWSClient).s3conn
   491  
   492  	log.Printf("[DEBUG] S3 Delete Bucket: %s", d.Id())
   493  	_, err := s3conn.DeleteBucket(&s3.DeleteBucketInput{
   494  		Bucket: aws.String(d.Id()),
   495  	})
   496  	if err != nil {
   497  		ec2err, ok := err.(awserr.Error)
   498  		if ok && ec2err.Code() == "BucketNotEmpty" {
   499  			if d.Get("force_destroy").(bool) {
   500  				// bucket may have things delete them
   501  				log.Printf("[DEBUG] S3 Bucket attempting to forceDestroy %+v", err)
   502  
   503  				bucket := d.Get("bucket").(string)
   504  				resp, err := s3conn.ListObjectVersions(
   505  					&s3.ListObjectVersionsInput{
   506  						Bucket: aws.String(bucket),
   507  					},
   508  				)
   509  
   510  				if err != nil {
   511  					return fmt.Errorf("Error S3 Bucket list Object Versions err: %s", err)
   512  				}
   513  
   514  				objectsToDelete := make([]*s3.ObjectIdentifier, 0)
   515  
   516  				if len(resp.DeleteMarkers) != 0 {
   517  
   518  					for _, v := range resp.DeleteMarkers {
   519  						objectsToDelete = append(objectsToDelete, &s3.ObjectIdentifier{
   520  							Key:       v.Key,
   521  							VersionId: v.VersionId,
   522  						})
   523  					}
   524  				}
   525  
   526  				if len(resp.Versions) != 0 {
   527  					for _, v := range resp.Versions {
   528  						objectsToDelete = append(objectsToDelete, &s3.ObjectIdentifier{
   529  							Key:       v.Key,
   530  							VersionId: v.VersionId,
   531  						})
   532  					}
   533  				}
   534  
   535  				params := &s3.DeleteObjectsInput{
   536  					Bucket: aws.String(bucket),
   537  					Delete: &s3.Delete{
   538  						Objects: objectsToDelete,
   539  					},
   540  				}
   541  
   542  				_, err = s3conn.DeleteObjects(params)
   543  
   544  				if err != nil {
   545  					return fmt.Errorf("Error S3 Bucket force_destroy error deleting: %s", err)
   546  				}
   547  
   548  				// this line recurses until all objects are deleted or an error is returned
   549  				return resourceAwsS3BucketDelete(d, meta)
   550  			}
   551  		}
   552  		return fmt.Errorf("Error deleting S3 Bucket: %s", err)
   553  	}
   554  	return nil
   555  }
   556  
   557  func resourceAwsS3BucketPolicyUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   558  	bucket := d.Get("bucket").(string)
   559  	policy := d.Get("policy").(string)
   560  
   561  	if policy != "" {
   562  		log.Printf("[DEBUG] S3 bucket: %s, put policy: %s", bucket, policy)
   563  
   564  		params := &s3.PutBucketPolicyInput{
   565  			Bucket: aws.String(bucket),
   566  			Policy: aws.String(policy),
   567  		}
   568  
   569  		err := resource.Retry(1*time.Minute, func() *resource.RetryError {
   570  			if _, err := s3conn.PutBucketPolicy(params); err != nil {
   571  				if awserr, ok := err.(awserr.Error); ok {
   572  					if awserr.Code() == "MalformedPolicy" {
   573  						return resource.RetryableError(awserr)
   574  					}
   575  				}
   576  				return resource.NonRetryableError(err)
   577  			}
   578  			return nil
   579  		})
   580  
   581  		if err != nil {
   582  			return fmt.Errorf("Error putting S3 policy: %s", err)
   583  		}
   584  	} else {
   585  		log.Printf("[DEBUG] S3 bucket: %s, delete policy: %s", bucket, policy)
   586  		_, err := s3conn.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{
   587  			Bucket: aws.String(bucket),
   588  		})
   589  
   590  		if err != nil {
   591  			return fmt.Errorf("Error deleting S3 policy: %s", err)
   592  		}
   593  	}
   594  
   595  	return nil
   596  }
   597  
   598  func resourceAwsS3BucketCorsUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   599  	bucket := d.Get("bucket").(string)
   600  	rawCors := d.Get("cors_rule").([]interface{})
   601  
   602  	if len(rawCors) == 0 {
   603  		// Delete CORS
   604  		log.Printf("[DEBUG] S3 bucket: %s, delete CORS", bucket)
   605  		_, err := s3conn.DeleteBucketCors(&s3.DeleteBucketCorsInput{
   606  			Bucket: aws.String(bucket),
   607  		})
   608  		if err != nil {
   609  			return fmt.Errorf("Error deleting S3 CORS: %s", err)
   610  		}
   611  	} else {
   612  		// Put CORS
   613  		rules := make([]*s3.CORSRule, 0, len(rawCors))
   614  		for _, cors := range rawCors {
   615  			corsMap := cors.(map[string]interface{})
   616  			r := &s3.CORSRule{}
   617  			for k, v := range corsMap {
   618  				log.Printf("[DEBUG] S3 bucket: %s, put CORS: %#v, %#v", bucket, k, v)
   619  				if k == "max_age_seconds" {
   620  					r.MaxAgeSeconds = aws.Int64(int64(v.(int)))
   621  				} else {
   622  					vMap := make([]*string, len(v.([]interface{})))
   623  					for i, vv := range v.([]interface{}) {
   624  						str := vv.(string)
   625  						vMap[i] = aws.String(str)
   626  					}
   627  					switch k {
   628  					case "allowed_headers":
   629  						r.AllowedHeaders = vMap
   630  					case "allowed_methods":
   631  						r.AllowedMethods = vMap
   632  					case "allowed_origins":
   633  						r.AllowedOrigins = vMap
   634  					case "expose_headers":
   635  						r.ExposeHeaders = vMap
   636  					}
   637  				}
   638  			}
   639  			rules = append(rules, r)
   640  		}
   641  		corsInput := &s3.PutBucketCorsInput{
   642  			Bucket: aws.String(bucket),
   643  			CORSConfiguration: &s3.CORSConfiguration{
   644  				CORSRules: rules,
   645  			},
   646  		}
   647  		log.Printf("[DEBUG] S3 bucket: %s, put CORS: %#v", bucket, corsInput)
   648  		_, err := s3conn.PutBucketCors(corsInput)
   649  		if err != nil {
   650  			return fmt.Errorf("Error putting S3 CORS: %s", err)
   651  		}
   652  	}
   653  
   654  	return nil
   655  }
   656  
   657  func resourceAwsS3BucketWebsiteUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   658  	ws := d.Get("website").([]interface{})
   659  
   660  	if len(ws) == 1 {
   661  		w := ws[0].(map[string]interface{})
   662  		return resourceAwsS3BucketWebsitePut(s3conn, d, w)
   663  	} else if len(ws) == 0 {
   664  		return resourceAwsS3BucketWebsiteDelete(s3conn, d)
   665  	} else {
   666  		return fmt.Errorf("Cannot specify more than one website.")
   667  	}
   668  }
   669  
   670  func resourceAwsS3BucketWebsitePut(s3conn *s3.S3, d *schema.ResourceData, website map[string]interface{}) error {
   671  	bucket := d.Get("bucket").(string)
   672  
   673  	indexDocument := website["index_document"].(string)
   674  	errorDocument := website["error_document"].(string)
   675  	redirectAllRequestsTo := website["redirect_all_requests_to"].(string)
   676  	routingRules := website["routing_rules"].(string)
   677  
   678  	if indexDocument == "" && redirectAllRequestsTo == "" {
   679  		return fmt.Errorf("Must specify either index_document or redirect_all_requests_to.")
   680  	}
   681  
   682  	websiteConfiguration := &s3.WebsiteConfiguration{}
   683  
   684  	if indexDocument != "" {
   685  		websiteConfiguration.IndexDocument = &s3.IndexDocument{Suffix: aws.String(indexDocument)}
   686  	}
   687  
   688  	if errorDocument != "" {
   689  		websiteConfiguration.ErrorDocument = &s3.ErrorDocument{Key: aws.String(errorDocument)}
   690  	}
   691  
   692  	if redirectAllRequestsTo != "" {
   693  		redirect, err := url.Parse(redirectAllRequestsTo)
   694  		if err == nil && redirect.Scheme != "" {
   695  			websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirect.Host), Protocol: aws.String(redirect.Scheme)}
   696  		} else {
   697  			websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectAllRequestsTo)}
   698  		}
   699  	}
   700  
   701  	if routingRules != "" {
   702  		var unmarshaledRules []*s3.RoutingRule
   703  		if err := json.Unmarshal([]byte(routingRules), &unmarshaledRules); err != nil {
   704  			return err
   705  		}
   706  		websiteConfiguration.RoutingRules = unmarshaledRules
   707  	}
   708  
   709  	putInput := &s3.PutBucketWebsiteInput{
   710  		Bucket:               aws.String(bucket),
   711  		WebsiteConfiguration: websiteConfiguration,
   712  	}
   713  
   714  	log.Printf("[DEBUG] S3 put bucket website: %#v", putInput)
   715  
   716  	_, err := s3conn.PutBucketWebsite(putInput)
   717  	if err != nil {
   718  		return fmt.Errorf("Error putting S3 website: %s", err)
   719  	}
   720  
   721  	return nil
   722  }
   723  
   724  func resourceAwsS3BucketWebsiteDelete(s3conn *s3.S3, d *schema.ResourceData) error {
   725  	bucket := d.Get("bucket").(string)
   726  	deleteInput := &s3.DeleteBucketWebsiteInput{Bucket: aws.String(bucket)}
   727  
   728  	log.Printf("[DEBUG] S3 delete bucket website: %#v", deleteInput)
   729  
   730  	_, err := s3conn.DeleteBucketWebsite(deleteInput)
   731  	if err != nil {
   732  		return fmt.Errorf("Error deleting S3 website: %s", err)
   733  	}
   734  
   735  	d.Set("website_endpoint", "")
   736  	d.Set("website_domain", "")
   737  
   738  	return nil
   739  }
   740  
   741  func websiteEndpoint(s3conn *s3.S3, d *schema.ResourceData) (*S3Website, error) {
   742  	// If the bucket doesn't have a website configuration, return an empty
   743  	// endpoint
   744  	if _, ok := d.GetOk("website"); !ok {
   745  		return nil, nil
   746  	}
   747  
   748  	bucket := d.Get("bucket").(string)
   749  
   750  	// Lookup the region for this bucket
   751  	location, err := s3conn.GetBucketLocation(
   752  		&s3.GetBucketLocationInput{
   753  			Bucket: aws.String(bucket),
   754  		},
   755  	)
   756  	if err != nil {
   757  		return nil, err
   758  	}
   759  	var region string
   760  	if location.LocationConstraint != nil {
   761  		region = *location.LocationConstraint
   762  	}
   763  
   764  	return WebsiteEndpoint(bucket, region), nil
   765  }
   766  
   767  func WebsiteEndpoint(bucket string, region string) *S3Website {
   768  	domain := WebsiteDomainUrl(region)
   769  	return &S3Website{Endpoint: fmt.Sprintf("%s.%s", bucket, domain), Domain: domain}
   770  }
   771  
   772  func WebsiteDomainUrl(region string) string {
   773  	region = normalizeRegion(region)
   774  
   775  	// Frankfurt(and probably future) regions uses different syntax for website endpoints
   776  	// http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteEndpoints.html
   777  	if region == "eu-central-1" {
   778  		return fmt.Sprintf("s3-website.%s.amazonaws.com", region)
   779  	}
   780  
   781  	return fmt.Sprintf("s3-website-%s.amazonaws.com", region)
   782  }
   783  
   784  func resourceAwsS3BucketAclUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   785  	acl := d.Get("acl").(string)
   786  	bucket := d.Get("bucket").(string)
   787  
   788  	i := &s3.PutBucketAclInput{
   789  		Bucket: aws.String(bucket),
   790  		ACL:    aws.String(acl),
   791  	}
   792  	log.Printf("[DEBUG] S3 put bucket ACL: %#v", i)
   793  
   794  	_, err := s3conn.PutBucketAcl(i)
   795  	if err != nil {
   796  		return fmt.Errorf("Error putting S3 ACL: %s", err)
   797  	}
   798  
   799  	return nil
   800  }
   801  
   802  func resourceAwsS3BucketVersioningUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   803  	v := d.Get("versioning").(*schema.Set).List()
   804  	bucket := d.Get("bucket").(string)
   805  	vc := &s3.VersioningConfiguration{}
   806  
   807  	if len(v) > 0 {
   808  		c := v[0].(map[string]interface{})
   809  
   810  		if c["enabled"].(bool) {
   811  			vc.Status = aws.String(s3.BucketVersioningStatusEnabled)
   812  		} else {
   813  			vc.Status = aws.String(s3.BucketVersioningStatusSuspended)
   814  		}
   815  	} else {
   816  		vc.Status = aws.String(s3.BucketVersioningStatusSuspended)
   817  	}
   818  
   819  	i := &s3.PutBucketVersioningInput{
   820  		Bucket:                  aws.String(bucket),
   821  		VersioningConfiguration: vc,
   822  	}
   823  	log.Printf("[DEBUG] S3 put bucket versioning: %#v", i)
   824  
   825  	_, err := s3conn.PutBucketVersioning(i)
   826  	if err != nil {
   827  		return fmt.Errorf("Error putting S3 versioning: %s", err)
   828  	}
   829  
   830  	return nil
   831  }
   832  
   833  func resourceAwsS3BucketLoggingUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
   834  	logging := d.Get("logging").(*schema.Set).List()
   835  	bucket := d.Get("bucket").(string)
   836  	loggingStatus := &s3.BucketLoggingStatus{}
   837  
   838  	if len(logging) > 0 {
   839  		c := logging[0].(map[string]interface{})
   840  
   841  		loggingEnabled := &s3.LoggingEnabled{}
   842  		if val, ok := c["target_bucket"]; ok {
   843  			loggingEnabled.TargetBucket = aws.String(val.(string))
   844  		}
   845  		if val, ok := c["target_prefix"]; ok {
   846  			loggingEnabled.TargetPrefix = aws.String(val.(string))
   847  		}
   848  
   849  		loggingStatus.LoggingEnabled = loggingEnabled
   850  	}
   851  
   852  	i := &s3.PutBucketLoggingInput{
   853  		Bucket:              aws.String(bucket),
   854  		BucketLoggingStatus: loggingStatus,
   855  	}
   856  	log.Printf("[DEBUG] S3 put bucket logging: %#v", i)
   857  
   858  	_, err := s3conn.PutBucketLogging(i)
   859  	if err != nil {
   860  		return fmt.Errorf("Error putting S3 logging: %s", err)
   861  	}
   862  
   863  	return nil
   864  }
   865  
   866  func normalizeRoutingRules(w []*s3.RoutingRule) (string, error) {
   867  	withNulls, err := json.Marshal(w)
   868  	if err != nil {
   869  		return "", err
   870  	}
   871  
   872  	var rules []map[string]interface{}
   873  	json.Unmarshal(withNulls, &rules)
   874  
   875  	var cleanRules []map[string]interface{}
   876  	for _, rule := range rules {
   877  		cleanRules = append(cleanRules, removeNil(rule))
   878  	}
   879  
   880  	withoutNulls, err := json.Marshal(cleanRules)
   881  	if err != nil {
   882  		return "", err
   883  	}
   884  
   885  	return string(withoutNulls), nil
   886  }
   887  
   888  func removeNil(data map[string]interface{}) map[string]interface{} {
   889  	withoutNil := make(map[string]interface{})
   890  
   891  	for k, v := range data {
   892  		if v == nil {
   893  			continue
   894  		}
   895  
   896  		switch v.(type) {
   897  		case map[string]interface{}:
   898  			withoutNil[k] = removeNil(v.(map[string]interface{}))
   899  		default:
   900  			withoutNil[k] = v
   901  		}
   902  	}
   903  
   904  	return withoutNil
   905  }
   906  
   907  func normalizeJson(jsonString interface{}) string {
   908  	if jsonString == nil || jsonString == "" {
   909  		return ""
   910  	}
   911  	var j interface{}
   912  	err := json.Unmarshal([]byte(jsonString.(string)), &j)
   913  	if err != nil {
   914  		return fmt.Sprintf("Error parsing JSON: %s", err)
   915  	}
   916  	b, _ := json.Marshal(j)
   917  	return string(b[:])
   918  }
   919  
   920  func normalizeRegion(region string) string {
   921  	// Default to us-east-1 if the bucket doesn't have a region:
   922  	// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETlocation.html
   923  	if region == "" {
   924  		region = "us-east-1"
   925  	}
   926  
   927  	return region
   928  }
   929  
   930  type S3Website struct {
   931  	Endpoint, Domain string
   932  }