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