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