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