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