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