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