github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/builtin/providers/aws/resource_aws_elb.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "regexp" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/aws/aws-sdk-go/aws" 13 "github.com/aws/aws-sdk-go/aws/awserr" 14 "github.com/aws/aws-sdk-go/service/ec2" 15 "github.com/aws/aws-sdk-go/service/elb" 16 "github.com/hashicorp/terraform/helper/hashcode" 17 "github.com/hashicorp/terraform/helper/resource" 18 "github.com/hashicorp/terraform/helper/schema" 19 ) 20 21 func resourceAwsElb() *schema.Resource { 22 return &schema.Resource{ 23 Create: resourceAwsElbCreate, 24 Read: resourceAwsElbRead, 25 Update: resourceAwsElbUpdate, 26 Delete: resourceAwsElbDelete, 27 Importer: &schema.ResourceImporter{ 28 State: schema.ImportStatePassthrough, 29 }, 30 31 Schema: map[string]*schema.Schema{ 32 "name": &schema.Schema{ 33 Type: schema.TypeString, 34 Optional: true, 35 Computed: true, 36 ForceNew: true, 37 ValidateFunc: validateElbName, 38 }, 39 40 "internal": &schema.Schema{ 41 Type: schema.TypeBool, 42 Optional: true, 43 ForceNew: true, 44 Computed: true, 45 }, 46 47 "cross_zone_load_balancing": &schema.Schema{ 48 Type: schema.TypeBool, 49 Optional: true, 50 Default: true, 51 }, 52 53 "availability_zones": &schema.Schema{ 54 Type: schema.TypeSet, 55 Elem: &schema.Schema{Type: schema.TypeString}, 56 Optional: true, 57 Computed: true, 58 Set: schema.HashString, 59 }, 60 61 "instances": &schema.Schema{ 62 Type: schema.TypeSet, 63 Elem: &schema.Schema{Type: schema.TypeString}, 64 Optional: true, 65 Computed: true, 66 Set: schema.HashString, 67 }, 68 69 "security_groups": &schema.Schema{ 70 Type: schema.TypeSet, 71 Elem: &schema.Schema{Type: schema.TypeString}, 72 Optional: true, 73 Computed: true, 74 Set: schema.HashString, 75 }, 76 77 "source_security_group": &schema.Schema{ 78 Type: schema.TypeString, 79 Optional: true, 80 Computed: true, 81 }, 82 83 "source_security_group_id": &schema.Schema{ 84 Type: schema.TypeString, 85 Computed: true, 86 }, 87 88 "subnets": &schema.Schema{ 89 Type: schema.TypeSet, 90 Elem: &schema.Schema{Type: schema.TypeString}, 91 Optional: true, 92 Computed: true, 93 Set: schema.HashString, 94 }, 95 96 "idle_timeout": &schema.Schema{ 97 Type: schema.TypeInt, 98 Optional: true, 99 Default: 60, 100 ValidateFunc: validateIntegerInRange(1, 3600), 101 }, 102 103 "connection_draining": &schema.Schema{ 104 Type: schema.TypeBool, 105 Optional: true, 106 Default: false, 107 }, 108 109 "connection_draining_timeout": &schema.Schema{ 110 Type: schema.TypeInt, 111 Optional: true, 112 Default: 300, 113 }, 114 115 "access_logs": &schema.Schema{ 116 Type: schema.TypeList, 117 Optional: true, 118 Elem: &schema.Resource{ 119 Schema: map[string]*schema.Schema{ 120 "interval": &schema.Schema{ 121 Type: schema.TypeInt, 122 Optional: true, 123 Default: 60, 124 ValidateFunc: validateAccessLogsInterval, 125 }, 126 "bucket": &schema.Schema{ 127 Type: schema.TypeString, 128 Required: true, 129 }, 130 "bucket_prefix": &schema.Schema{ 131 Type: schema.TypeString, 132 Optional: true, 133 }, 134 "enabled": &schema.Schema{ 135 Type: schema.TypeBool, 136 Optional: true, 137 Default: true, 138 }, 139 }, 140 }, 141 }, 142 143 "listener": &schema.Schema{ 144 Type: schema.TypeSet, 145 Required: true, 146 Elem: &schema.Resource{ 147 Schema: map[string]*schema.Schema{ 148 "instance_port": &schema.Schema{ 149 Type: schema.TypeInt, 150 Required: true, 151 ValidateFunc: validateIntegerInRange(1, 65535), 152 }, 153 154 "instance_protocol": &schema.Schema{ 155 Type: schema.TypeString, 156 Required: true, 157 ValidateFunc: validateListenerProtocol, 158 }, 159 160 "lb_port": &schema.Schema{ 161 Type: schema.TypeInt, 162 Required: true, 163 ValidateFunc: validateIntegerInRange(1, 65535), 164 }, 165 166 "lb_protocol": &schema.Schema{ 167 Type: schema.TypeString, 168 Required: true, 169 ValidateFunc: validateListenerProtocol, 170 }, 171 172 "ssl_certificate_id": &schema.Schema{ 173 Type: schema.TypeString, 174 Optional: true, 175 }, 176 }, 177 }, 178 Set: resourceAwsElbListenerHash, 179 }, 180 181 "health_check": &schema.Schema{ 182 Type: schema.TypeList, 183 Optional: true, 184 Computed: true, 185 MaxItems: 1, 186 Elem: &schema.Resource{ 187 Schema: map[string]*schema.Schema{ 188 "healthy_threshold": &schema.Schema{ 189 Type: schema.TypeInt, 190 Required: true, 191 ValidateFunc: validateIntegerInRange(2, 10), 192 }, 193 194 "unhealthy_threshold": &schema.Schema{ 195 Type: schema.TypeInt, 196 Required: true, 197 ValidateFunc: validateIntegerInRange(2, 10), 198 }, 199 200 "target": &schema.Schema{ 201 Type: schema.TypeString, 202 Required: true, 203 ValidateFunc: validateHeathCheckTarget, 204 }, 205 206 "interval": &schema.Schema{ 207 Type: schema.TypeInt, 208 Required: true, 209 ValidateFunc: validateIntegerInRange(5, 300), 210 }, 211 212 "timeout": &schema.Schema{ 213 Type: schema.TypeInt, 214 Required: true, 215 ValidateFunc: validateIntegerInRange(2, 60), 216 }, 217 }, 218 }, 219 }, 220 221 "dns_name": &schema.Schema{ 222 Type: schema.TypeString, 223 Computed: true, 224 }, 225 226 "zone_id": &schema.Schema{ 227 Type: schema.TypeString, 228 Computed: true, 229 }, 230 231 "tags": tagsSchema(), 232 }, 233 } 234 } 235 236 func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error { 237 elbconn := meta.(*AWSClient).elbconn 238 239 // Expand the "listener" set to aws-sdk-go compat []*elb.Listener 240 listeners, err := expandListeners(d.Get("listener").(*schema.Set).List()) 241 if err != nil { 242 return err 243 } 244 245 var elbName string 246 if v, ok := d.GetOk("name"); ok { 247 elbName = v.(string) 248 } else { 249 elbName = resource.PrefixedUniqueId("tf-lb-") 250 d.Set("name", elbName) 251 } 252 253 tags := tagsFromMapELB(d.Get("tags").(map[string]interface{})) 254 // Provision the elb 255 elbOpts := &elb.CreateLoadBalancerInput{ 256 LoadBalancerName: aws.String(elbName), 257 Listeners: listeners, 258 Tags: tags, 259 } 260 261 if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) { 262 elbOpts.Scheme = aws.String("internal") 263 } 264 265 if v, ok := d.GetOk("availability_zones"); ok { 266 elbOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List()) 267 } 268 269 if v, ok := d.GetOk("security_groups"); ok { 270 elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List()) 271 } 272 273 if v, ok := d.GetOk("subnets"); ok { 274 elbOpts.Subnets = expandStringList(v.(*schema.Set).List()) 275 } 276 277 log.Printf("[DEBUG] ELB create configuration: %#v", elbOpts) 278 err = resource.Retry(1*time.Minute, func() *resource.RetryError { 279 _, err := elbconn.CreateLoadBalancer(elbOpts) 280 281 if err != nil { 282 if awsErr, ok := err.(awserr.Error); ok { 283 // Check for IAM SSL Cert error, eventual consistancy issue 284 if awsErr.Code() == "CertificateNotFound" { 285 return resource.RetryableError( 286 fmt.Errorf("[WARN] Error creating ELB Listener with SSL Cert, retrying: %s", err)) 287 } 288 } 289 return resource.NonRetryableError(err) 290 } 291 return nil 292 }) 293 294 if err != nil { 295 return err 296 } 297 298 // Assign the elb's unique identifier for use later 299 d.SetId(elbName) 300 log.Printf("[INFO] ELB ID: %s", d.Id()) 301 302 // Enable partial mode and record what we set 303 d.Partial(true) 304 d.SetPartial("name") 305 d.SetPartial("internal") 306 d.SetPartial("availability_zones") 307 d.SetPartial("listener") 308 d.SetPartial("security_groups") 309 d.SetPartial("subnets") 310 311 d.Set("tags", tagsToMapELB(tags)) 312 313 return resourceAwsElbUpdate(d, meta) 314 } 315 316 func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error { 317 elbconn := meta.(*AWSClient).elbconn 318 elbName := d.Id() 319 320 // Retrieve the ELB properties for updating the state 321 describeElbOpts := &elb.DescribeLoadBalancersInput{ 322 LoadBalancerNames: []*string{aws.String(elbName)}, 323 } 324 325 describeResp, err := elbconn.DescribeLoadBalancers(describeElbOpts) 326 if err != nil { 327 if isLoadBalancerNotFound(err) { 328 // The ELB is gone now, so just remove it from the state 329 d.SetId("") 330 return nil 331 } 332 333 return fmt.Errorf("Error retrieving ELB: %s", err) 334 } 335 if len(describeResp.LoadBalancerDescriptions) != 1 { 336 return fmt.Errorf("Unable to find ELB: %#v", describeResp.LoadBalancerDescriptions) 337 } 338 339 describeAttrsOpts := &elb.DescribeLoadBalancerAttributesInput{ 340 LoadBalancerName: aws.String(elbName), 341 } 342 describeAttrsResp, err := elbconn.DescribeLoadBalancerAttributes(describeAttrsOpts) 343 if err != nil { 344 if isLoadBalancerNotFound(err) { 345 // The ELB is gone now, so just remove it from the state 346 d.SetId("") 347 return nil 348 } 349 350 return fmt.Errorf("Error retrieving ELB: %s", err) 351 } 352 353 lbAttrs := describeAttrsResp.LoadBalancerAttributes 354 355 lb := describeResp.LoadBalancerDescriptions[0] 356 357 d.Set("name", lb.LoadBalancerName) 358 d.Set("dns_name", lb.DNSName) 359 d.Set("zone_id", lb.CanonicalHostedZoneNameID) 360 361 var scheme bool 362 if lb.Scheme != nil { 363 scheme = *lb.Scheme == "internal" 364 } 365 d.Set("internal", scheme) 366 d.Set("availability_zones", flattenStringList(lb.AvailabilityZones)) 367 d.Set("instances", flattenInstances(lb.Instances)) 368 d.Set("listener", flattenListeners(lb.ListenerDescriptions)) 369 d.Set("security_groups", flattenStringList(lb.SecurityGroups)) 370 if lb.SourceSecurityGroup != nil { 371 group := lb.SourceSecurityGroup.GroupName 372 if lb.SourceSecurityGroup.OwnerAlias != nil && *lb.SourceSecurityGroup.OwnerAlias != "" { 373 group = aws.String(*lb.SourceSecurityGroup.OwnerAlias + "/" + *lb.SourceSecurityGroup.GroupName) 374 } 375 d.Set("source_security_group", group) 376 377 // Manually look up the ELB Security Group ID, since it's not provided 378 var elbVpc string 379 if lb.VPCId != nil { 380 elbVpc = *lb.VPCId 381 sgId, err := sourceSGIdByName(meta, *lb.SourceSecurityGroup.GroupName, elbVpc) 382 if err != nil { 383 return fmt.Errorf("[WARN] Error looking up ELB Security Group ID: %s", err) 384 } else { 385 d.Set("source_security_group_id", sgId) 386 } 387 } 388 } 389 d.Set("subnets", flattenStringList(lb.Subnets)) 390 d.Set("idle_timeout", lbAttrs.ConnectionSettings.IdleTimeout) 391 d.Set("connection_draining", lbAttrs.ConnectionDraining.Enabled) 392 d.Set("connection_draining_timeout", lbAttrs.ConnectionDraining.Timeout) 393 d.Set("cross_zone_load_balancing", lbAttrs.CrossZoneLoadBalancing.Enabled) 394 if lbAttrs.AccessLog != nil { 395 if err := d.Set("access_logs", flattenAccessLog(lbAttrs.AccessLog)); err != nil { 396 return err 397 } 398 } 399 400 resp, err := elbconn.DescribeTags(&elb.DescribeTagsInput{ 401 LoadBalancerNames: []*string{lb.LoadBalancerName}, 402 }) 403 404 var et []*elb.Tag 405 if len(resp.TagDescriptions) > 0 { 406 et = resp.TagDescriptions[0].Tags 407 } 408 d.Set("tags", tagsToMapELB(et)) 409 410 // There's only one health check, so save that to state as we 411 // currently can 412 if *lb.HealthCheck.Target != "" { 413 d.Set("health_check", flattenHealthCheck(lb.HealthCheck)) 414 } 415 416 return nil 417 } 418 419 func resourceAwsElbUpdate(d *schema.ResourceData, meta interface{}) error { 420 elbconn := meta.(*AWSClient).elbconn 421 422 d.Partial(true) 423 424 if d.HasChange("listener") { 425 o, n := d.GetChange("listener") 426 os := o.(*schema.Set) 427 ns := n.(*schema.Set) 428 429 remove, _ := expandListeners(os.Difference(ns).List()) 430 add, _ := expandListeners(ns.Difference(os).List()) 431 432 if len(remove) > 0 { 433 ports := make([]*int64, 0, len(remove)) 434 for _, listener := range remove { 435 ports = append(ports, listener.LoadBalancerPort) 436 } 437 438 deleteListenersOpts := &elb.DeleteLoadBalancerListenersInput{ 439 LoadBalancerName: aws.String(d.Id()), 440 LoadBalancerPorts: ports, 441 } 442 443 log.Printf("[DEBUG] ELB Delete Listeners opts: %s", deleteListenersOpts) 444 _, err := elbconn.DeleteLoadBalancerListeners(deleteListenersOpts) 445 if err != nil { 446 return fmt.Errorf("Failure removing outdated ELB listeners: %s", err) 447 } 448 } 449 450 if len(add) > 0 { 451 createListenersOpts := &elb.CreateLoadBalancerListenersInput{ 452 LoadBalancerName: aws.String(d.Id()), 453 Listeners: add, 454 } 455 456 // Occasionally AWS will error with a 'duplicate listener', without any 457 // other listeners on the ELB. Retry here to eliminate that. 458 err := resource.Retry(1*time.Minute, func() *resource.RetryError { 459 log.Printf("[DEBUG] ELB Create Listeners opts: %s", createListenersOpts) 460 if _, err := elbconn.CreateLoadBalancerListeners(createListenersOpts); err != nil { 461 if awsErr, ok := err.(awserr.Error); ok { 462 if awsErr.Code() == "DuplicateListener" { 463 log.Printf("[DEBUG] Duplicate listener found for ELB (%s), retrying", d.Id()) 464 return resource.RetryableError(awsErr) 465 } 466 if awsErr.Code() == "CertificateNotFound" && strings.Contains(awsErr.Message(), "Server Certificate not found for the key: arn") { 467 log.Printf("[DEBUG] SSL Cert not found for given ARN, retrying") 468 return resource.RetryableError(awsErr) 469 } 470 } 471 472 // Didn't recognize the error, so shouldn't retry. 473 return resource.NonRetryableError(err) 474 } 475 // Successful creation 476 return nil 477 }) 478 if err != nil { 479 return fmt.Errorf("Failure adding new or updated ELB listeners: %s", err) 480 } 481 } 482 483 d.SetPartial("listener") 484 } 485 486 // If we currently have instances, or did have instances, 487 // we want to figure out what to add and remove from the load 488 // balancer 489 if d.HasChange("instances") { 490 o, n := d.GetChange("instances") 491 os := o.(*schema.Set) 492 ns := n.(*schema.Set) 493 remove := expandInstanceString(os.Difference(ns).List()) 494 add := expandInstanceString(ns.Difference(os).List()) 495 496 if len(add) > 0 { 497 registerInstancesOpts := elb.RegisterInstancesWithLoadBalancerInput{ 498 LoadBalancerName: aws.String(d.Id()), 499 Instances: add, 500 } 501 502 _, err := elbconn.RegisterInstancesWithLoadBalancer(®isterInstancesOpts) 503 if err != nil { 504 return fmt.Errorf("Failure registering instances with ELB: %s", err) 505 } 506 } 507 if len(remove) > 0 { 508 deRegisterInstancesOpts := elb.DeregisterInstancesFromLoadBalancerInput{ 509 LoadBalancerName: aws.String(d.Id()), 510 Instances: remove, 511 } 512 513 _, err := elbconn.DeregisterInstancesFromLoadBalancer(&deRegisterInstancesOpts) 514 if err != nil { 515 return fmt.Errorf("Failure deregistering instances from ELB: %s", err) 516 } 517 } 518 519 d.SetPartial("instances") 520 } 521 522 if d.HasChange("cross_zone_load_balancing") || d.HasChange("idle_timeout") || d.HasChange("access_logs") { 523 attrs := elb.ModifyLoadBalancerAttributesInput{ 524 LoadBalancerName: aws.String(d.Get("name").(string)), 525 LoadBalancerAttributes: &elb.LoadBalancerAttributes{ 526 CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{ 527 Enabled: aws.Bool(d.Get("cross_zone_load_balancing").(bool)), 528 }, 529 ConnectionSettings: &elb.ConnectionSettings{ 530 IdleTimeout: aws.Int64(int64(d.Get("idle_timeout").(int))), 531 }, 532 }, 533 } 534 535 logs := d.Get("access_logs").([]interface{}) 536 if len(logs) > 1 { 537 return fmt.Errorf("Only one access logs config per ELB is supported") 538 } else if len(logs) == 1 { 539 log := logs[0].(map[string]interface{}) 540 accessLog := &elb.AccessLog{ 541 Enabled: aws.Bool(log["enabled"].(bool)), 542 EmitInterval: aws.Int64(int64(log["interval"].(int))), 543 S3BucketName: aws.String(log["bucket"].(string)), 544 } 545 546 if log["bucket_prefix"] != "" { 547 accessLog.S3BucketPrefix = aws.String(log["bucket_prefix"].(string)) 548 } 549 550 attrs.LoadBalancerAttributes.AccessLog = accessLog 551 } else if len(logs) == 0 { 552 // disable access logs 553 attrs.LoadBalancerAttributes.AccessLog = &elb.AccessLog{ 554 Enabled: aws.Bool(false), 555 } 556 } 557 558 log.Printf("[DEBUG] ELB Modify Load Balancer Attributes Request: %#v", attrs) 559 _, err := elbconn.ModifyLoadBalancerAttributes(&attrs) 560 if err != nil { 561 return fmt.Errorf("Failure configuring ELB attributes: %s", err) 562 } 563 564 d.SetPartial("cross_zone_load_balancing") 565 d.SetPartial("idle_timeout") 566 d.SetPartial("connection_draining_timeout") 567 } 568 569 // We have to do these changes separately from everything else since 570 // they have some weird undocumented rules. You can't set the timeout 571 // without having connection draining to true, so we set that to true, 572 // set the timeout, then reset it to false if requested. 573 if d.HasChange("connection_draining") || d.HasChange("connection_draining_timeout") { 574 // We do timeout changes first since they require us to set draining 575 // to true for a hot second. 576 if d.HasChange("connection_draining_timeout") { 577 attrs := elb.ModifyLoadBalancerAttributesInput{ 578 LoadBalancerName: aws.String(d.Get("name").(string)), 579 LoadBalancerAttributes: &elb.LoadBalancerAttributes{ 580 ConnectionDraining: &elb.ConnectionDraining{ 581 Enabled: aws.Bool(true), 582 Timeout: aws.Int64(int64(d.Get("connection_draining_timeout").(int))), 583 }, 584 }, 585 } 586 587 _, err := elbconn.ModifyLoadBalancerAttributes(&attrs) 588 if err != nil { 589 return fmt.Errorf("Failure configuring ELB attributes: %s", err) 590 } 591 592 d.SetPartial("connection_draining_timeout") 593 } 594 595 // Then we always set connection draining even if there is no change. 596 // This lets us reset to "false" if requested even with a timeout 597 // change. 598 attrs := elb.ModifyLoadBalancerAttributesInput{ 599 LoadBalancerName: aws.String(d.Get("name").(string)), 600 LoadBalancerAttributes: &elb.LoadBalancerAttributes{ 601 ConnectionDraining: &elb.ConnectionDraining{ 602 Enabled: aws.Bool(d.Get("connection_draining").(bool)), 603 }, 604 }, 605 } 606 607 _, err := elbconn.ModifyLoadBalancerAttributes(&attrs) 608 if err != nil { 609 return fmt.Errorf("Failure configuring ELB attributes: %s", err) 610 } 611 612 d.SetPartial("connection_draining") 613 } 614 615 if d.HasChange("health_check") { 616 hc := d.Get("health_check").([]interface{}) 617 if len(hc) > 0 { 618 check := hc[0].(map[string]interface{}) 619 configureHealthCheckOpts := elb.ConfigureHealthCheckInput{ 620 LoadBalancerName: aws.String(d.Id()), 621 HealthCheck: &elb.HealthCheck{ 622 HealthyThreshold: aws.Int64(int64(check["healthy_threshold"].(int))), 623 UnhealthyThreshold: aws.Int64(int64(check["unhealthy_threshold"].(int))), 624 Interval: aws.Int64(int64(check["interval"].(int))), 625 Target: aws.String(check["target"].(string)), 626 Timeout: aws.Int64(int64(check["timeout"].(int))), 627 }, 628 } 629 _, err := elbconn.ConfigureHealthCheck(&configureHealthCheckOpts) 630 if err != nil { 631 return fmt.Errorf("Failure configuring health check for ELB: %s", err) 632 } 633 d.SetPartial("health_check") 634 } 635 } 636 637 if d.HasChange("security_groups") { 638 groups := d.Get("security_groups").(*schema.Set).List() 639 640 applySecurityGroupsOpts := elb.ApplySecurityGroupsToLoadBalancerInput{ 641 LoadBalancerName: aws.String(d.Id()), 642 SecurityGroups: expandStringList(groups), 643 } 644 645 _, err := elbconn.ApplySecurityGroupsToLoadBalancer(&applySecurityGroupsOpts) 646 if err != nil { 647 return fmt.Errorf("Failure applying security groups to ELB: %s", err) 648 } 649 650 d.SetPartial("security_groups") 651 } 652 653 if d.HasChange("availability_zones") { 654 o, n := d.GetChange("availability_zones") 655 os := o.(*schema.Set) 656 ns := n.(*schema.Set) 657 658 removed := expandStringList(os.Difference(ns).List()) 659 added := expandStringList(ns.Difference(os).List()) 660 661 if len(added) > 0 { 662 enableOpts := &elb.EnableAvailabilityZonesForLoadBalancerInput{ 663 LoadBalancerName: aws.String(d.Id()), 664 AvailabilityZones: added, 665 } 666 667 log.Printf("[DEBUG] ELB enable availability zones opts: %s", enableOpts) 668 _, err := elbconn.EnableAvailabilityZonesForLoadBalancer(enableOpts) 669 if err != nil { 670 return fmt.Errorf("Failure enabling ELB availability zones: %s", err) 671 } 672 } 673 674 if len(removed) > 0 { 675 disableOpts := &elb.DisableAvailabilityZonesForLoadBalancerInput{ 676 LoadBalancerName: aws.String(d.Id()), 677 AvailabilityZones: removed, 678 } 679 680 log.Printf("[DEBUG] ELB disable availability zones opts: %s", disableOpts) 681 _, err := elbconn.DisableAvailabilityZonesForLoadBalancer(disableOpts) 682 if err != nil { 683 return fmt.Errorf("Failure disabling ELB availability zones: %s", err) 684 } 685 } 686 687 d.SetPartial("availability_zones") 688 } 689 690 if d.HasChange("subnets") { 691 o, n := d.GetChange("subnets") 692 os := o.(*schema.Set) 693 ns := n.(*schema.Set) 694 695 removed := expandStringList(os.Difference(ns).List()) 696 added := expandStringList(ns.Difference(os).List()) 697 698 if len(removed) > 0 { 699 detachOpts := &elb.DetachLoadBalancerFromSubnetsInput{ 700 LoadBalancerName: aws.String(d.Id()), 701 Subnets: removed, 702 } 703 704 log.Printf("[DEBUG] ELB detach subnets opts: %s", detachOpts) 705 _, err := elbconn.DetachLoadBalancerFromSubnets(detachOpts) 706 if err != nil { 707 return fmt.Errorf("Failure removing ELB subnets: %s", err) 708 } 709 } 710 711 if len(added) > 0 { 712 attachOpts := &elb.AttachLoadBalancerToSubnetsInput{ 713 LoadBalancerName: aws.String(d.Id()), 714 Subnets: added, 715 } 716 717 log.Printf("[DEBUG] ELB attach subnets opts: %s", attachOpts) 718 err := resource.Retry(1*time.Minute, func() *resource.RetryError { 719 _, err := elbconn.AttachLoadBalancerToSubnets(attachOpts) 720 if err != nil { 721 if awsErr, ok := err.(awserr.Error); ok { 722 // eventually consistent issue with removing a subnet in AZ1 and 723 // immediately adding a new one in the same AZ 724 if awsErr.Code() == "InvalidConfigurationRequest" && strings.Contains(awsErr.Message(), "cannot be attached to multiple subnets in the same AZ") { 725 log.Printf("[DEBUG] retrying az association") 726 return resource.RetryableError(awsErr) 727 } 728 } 729 return resource.NonRetryableError(err) 730 } 731 return nil 732 }) 733 if err != nil { 734 return fmt.Errorf("Failure adding ELB subnets: %s", err) 735 } 736 } 737 738 d.SetPartial("subnets") 739 } 740 741 if err := setTagsELB(elbconn, d); err != nil { 742 return err 743 } 744 745 d.SetPartial("tags") 746 d.Partial(false) 747 748 return resourceAwsElbRead(d, meta) 749 } 750 751 func resourceAwsElbDelete(d *schema.ResourceData, meta interface{}) error { 752 elbconn := meta.(*AWSClient).elbconn 753 754 log.Printf("[INFO] Deleting ELB: %s", d.Id()) 755 756 // Destroy the load balancer 757 deleteElbOpts := elb.DeleteLoadBalancerInput{ 758 LoadBalancerName: aws.String(d.Id()), 759 } 760 if _, err := elbconn.DeleteLoadBalancer(&deleteElbOpts); err != nil { 761 return fmt.Errorf("Error deleting ELB: %s", err) 762 } 763 764 return nil 765 } 766 767 func resourceAwsElbListenerHash(v interface{}) int { 768 var buf bytes.Buffer 769 m := v.(map[string]interface{}) 770 buf.WriteString(fmt.Sprintf("%d-", m["instance_port"].(int))) 771 buf.WriteString(fmt.Sprintf("%s-", 772 strings.ToLower(m["instance_protocol"].(string)))) 773 buf.WriteString(fmt.Sprintf("%d-", m["lb_port"].(int))) 774 buf.WriteString(fmt.Sprintf("%s-", 775 strings.ToLower(m["lb_protocol"].(string)))) 776 777 if v, ok := m["ssl_certificate_id"]; ok { 778 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 779 } 780 781 return hashcode.String(buf.String()) 782 } 783 784 func isLoadBalancerNotFound(err error) bool { 785 elberr, ok := err.(awserr.Error) 786 return ok && elberr.Code() == "LoadBalancerNotFound" 787 } 788 789 func sourceSGIdByName(meta interface{}, sg, vpcId string) (string, error) { 790 conn := meta.(*AWSClient).ec2conn 791 var filters []*ec2.Filter 792 var sgFilterName, sgFilterVPCID *ec2.Filter 793 sgFilterName = &ec2.Filter{ 794 Name: aws.String("group-name"), 795 Values: []*string{aws.String(sg)}, 796 } 797 798 if vpcId != "" { 799 sgFilterVPCID = &ec2.Filter{ 800 Name: aws.String("vpc-id"), 801 Values: []*string{aws.String(vpcId)}, 802 } 803 } 804 805 filters = append(filters, sgFilterName) 806 807 if sgFilterVPCID != nil { 808 filters = append(filters, sgFilterVPCID) 809 } 810 811 req := &ec2.DescribeSecurityGroupsInput{ 812 Filters: filters, 813 } 814 resp, err := conn.DescribeSecurityGroups(req) 815 if err != nil { 816 if ec2err, ok := err.(awserr.Error); ok { 817 if ec2err.Code() == "InvalidSecurityGroupID.NotFound" || 818 ec2err.Code() == "InvalidGroup.NotFound" { 819 resp = nil 820 err = nil 821 } 822 } 823 824 if err != nil { 825 log.Printf("Error on ELB SG look up: %s", err) 826 return "", err 827 } 828 } 829 830 if resp == nil || len(resp.SecurityGroups) == 0 { 831 return "", fmt.Errorf("No security groups found for name %s and vpc id %s", sg, vpcId) 832 } 833 834 group := resp.SecurityGroups[0] 835 return *group.GroupId, nil 836 } 837 838 func validateAccessLogsInterval(v interface{}, k string) (ws []string, errors []error) { 839 value := v.(int) 840 841 // Check if the value is either 5 or 60 (minutes). 842 if value != 5 && value != 60 { 843 errors = append(errors, fmt.Errorf( 844 "%q contains an invalid Access Logs interval \"%d\". "+ 845 "Valid intervals are either 5 or 60 (minutes).", 846 k, value)) 847 } 848 return 849 } 850 851 func validateHeathCheckTarget(v interface{}, k string) (ws []string, errors []error) { 852 value := v.(string) 853 854 // Parse the Health Check target value. 855 matches := regexp.MustCompile(`\A(\w+):(\d+)(.+)?\z`).FindStringSubmatch(value) 856 857 // Check if the value contains a valid target. 858 if matches == nil || len(matches) < 1 { 859 errors = append(errors, fmt.Errorf( 860 "%q contains an invalid Health Check: %s", 861 k, value)) 862 863 // Invalid target? Return immediately, 864 // there is no need to collect other 865 // errors. 866 return 867 } 868 869 // Check if the value contains a valid protocol. 870 if !isValidProtocol(matches[1]) { 871 errors = append(errors, fmt.Errorf( 872 "%q contains an invalid Health Check protocol %q. "+ 873 "Valid protocols are either %q, %q, %q, or %q.", 874 k, matches[1], "TCP", "SSL", "HTTP", "HTTPS")) 875 } 876 877 // Check if the value contains a valid port range. 878 port, _ := strconv.Atoi(matches[2]) 879 if port < 1 || port > 65535 { 880 errors = append(errors, fmt.Errorf( 881 "%q contains an invalid Health Check target port \"%d\". "+ 882 "Valid port is in the range from 1 to 65535 inclusive.", 883 k, port)) 884 } 885 886 switch strings.ToLower(matches[1]) { 887 case "tcp", "ssl": 888 // Check if value is in the form <PROTOCOL>:<PORT> for TCP and/or SSL. 889 if matches[3] != "" { 890 errors = append(errors, fmt.Errorf( 891 "%q cannot contain a path in the Health Check target: %s", 892 k, value)) 893 } 894 break 895 case "http", "https": 896 // Check if value is in the form <PROTOCOL>:<PORT>/<PATH> for HTTP and/or HTTPS. 897 if matches[3] == "" { 898 errors = append(errors, fmt.Errorf( 899 "%q must contain a path in the Health Check target: %s", 900 k, value)) 901 } 902 903 // Cannot be longer than 1024 multibyte characters. 904 if len([]rune(matches[3])) > 1024 { 905 errors = append(errors, fmt.Errorf("%q cannot contain a path longer "+ 906 "than 1024 characters in the Health Check target: %s", 907 k, value)) 908 } 909 break 910 } 911 912 return 913 } 914 915 func validateListenerProtocol(v interface{}, k string) (ws []string, errors []error) { 916 value := v.(string) 917 918 if !isValidProtocol(value) { 919 errors = append(errors, fmt.Errorf( 920 "%q contains an invalid Listener protocol %q. "+ 921 "Valid protocols are either %q, %q, %q, or %q.", 922 k, value, "TCP", "SSL", "HTTP", "HTTPS")) 923 } 924 return 925 } 926 927 func isValidProtocol(s string) bool { 928 if s == "" { 929 return false 930 } 931 s = strings.ToLower(s) 932 933 validProtocols := map[string]bool{ 934 "http": true, 935 "https": true, 936 "ssl": true, 937 "tcp": true, 938 } 939 940 if _, ok := validProtocols[s]; !ok { 941 return false 942 } 943 944 return true 945 }