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 }