github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/builtin/providers/aws/resource_aws_elb.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "regexp" 8 "strings" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/aws/awserr" 12 "github.com/aws/aws-sdk-go/service/elb" 13 "github.com/hashicorp/terraform/helper/hashcode" 14 "github.com/hashicorp/terraform/helper/resource" 15 "github.com/hashicorp/terraform/helper/schema" 16 ) 17 18 func resourceAwsElb() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceAwsElbCreate, 21 Read: resourceAwsElbRead, 22 Update: resourceAwsElbUpdate, 23 Delete: resourceAwsElbDelete, 24 25 Schema: map[string]*schema.Schema{ 26 "name": &schema.Schema{ 27 Type: schema.TypeString, 28 Optional: true, 29 Computed: true, 30 ForceNew: true, 31 ValidateFunc: validateElbName, 32 }, 33 34 "internal": &schema.Schema{ 35 Type: schema.TypeBool, 36 Optional: true, 37 ForceNew: true, 38 Computed: true, 39 }, 40 41 "cross_zone_load_balancing": &schema.Schema{ 42 Type: schema.TypeBool, 43 Optional: true, 44 }, 45 46 "availability_zones": &schema.Schema{ 47 Type: schema.TypeSet, 48 Elem: &schema.Schema{Type: schema.TypeString}, 49 Optional: true, 50 ForceNew: true, 51 Computed: true, 52 Set: schema.HashString, 53 }, 54 55 "instances": &schema.Schema{ 56 Type: schema.TypeSet, 57 Elem: &schema.Schema{Type: schema.TypeString}, 58 Optional: true, 59 Computed: true, 60 Set: schema.HashString, 61 }, 62 63 "security_groups": &schema.Schema{ 64 Type: schema.TypeSet, 65 Elem: &schema.Schema{Type: schema.TypeString}, 66 Optional: true, 67 Computed: true, 68 Set: schema.HashString, 69 }, 70 71 "source_security_group": &schema.Schema{ 72 Type: schema.TypeString, 73 Optional: true, 74 Computed: true, 75 }, 76 77 "subnets": &schema.Schema{ 78 Type: schema.TypeSet, 79 Elem: &schema.Schema{Type: schema.TypeString}, 80 Optional: true, 81 ForceNew: true, 82 Computed: true, 83 Set: schema.HashString, 84 }, 85 86 "idle_timeout": &schema.Schema{ 87 Type: schema.TypeInt, 88 Optional: true, 89 Default: 60, 90 }, 91 92 "connection_draining": &schema.Schema{ 93 Type: schema.TypeBool, 94 Optional: true, 95 Default: false, 96 }, 97 98 "connection_draining_timeout": &schema.Schema{ 99 Type: schema.TypeInt, 100 Optional: true, 101 Default: 300, 102 }, 103 104 "listener": &schema.Schema{ 105 Type: schema.TypeSet, 106 Required: true, 107 Elem: &schema.Resource{ 108 Schema: map[string]*schema.Schema{ 109 "instance_port": &schema.Schema{ 110 Type: schema.TypeInt, 111 Required: true, 112 }, 113 114 "instance_protocol": &schema.Schema{ 115 Type: schema.TypeString, 116 Required: true, 117 }, 118 119 "lb_port": &schema.Schema{ 120 Type: schema.TypeInt, 121 Required: true, 122 }, 123 124 "lb_protocol": &schema.Schema{ 125 Type: schema.TypeString, 126 Required: true, 127 }, 128 129 "ssl_certificate_id": &schema.Schema{ 130 Type: schema.TypeString, 131 Optional: true, 132 }, 133 }, 134 }, 135 Set: resourceAwsElbListenerHash, 136 }, 137 138 "health_check": &schema.Schema{ 139 Type: schema.TypeSet, 140 Optional: true, 141 Computed: true, 142 Elem: &schema.Resource{ 143 Schema: map[string]*schema.Schema{ 144 "healthy_threshold": &schema.Schema{ 145 Type: schema.TypeInt, 146 Required: true, 147 }, 148 149 "unhealthy_threshold": &schema.Schema{ 150 Type: schema.TypeInt, 151 Required: true, 152 }, 153 154 "target": &schema.Schema{ 155 Type: schema.TypeString, 156 Required: true, 157 }, 158 159 "interval": &schema.Schema{ 160 Type: schema.TypeInt, 161 Required: true, 162 }, 163 164 "timeout": &schema.Schema{ 165 Type: schema.TypeInt, 166 Required: true, 167 }, 168 }, 169 }, 170 Set: resourceAwsElbHealthCheckHash, 171 }, 172 173 "dns_name": &schema.Schema{ 174 Type: schema.TypeString, 175 Computed: true, 176 }, 177 178 "zone_id": &schema.Schema{ 179 Type: schema.TypeString, 180 Computed: true, 181 }, 182 183 "tags": tagsSchema(), 184 }, 185 } 186 } 187 188 func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error { 189 elbconn := meta.(*AWSClient).elbconn 190 191 // Expand the "listener" set to aws-sdk-go compat []*elb.Listener 192 listeners, err := expandListeners(d.Get("listener").(*schema.Set).List()) 193 if err != nil { 194 return err 195 } 196 197 var elbName string 198 if v, ok := d.GetOk("name"); ok { 199 elbName = v.(string) 200 } else { 201 elbName = resource.PrefixedUniqueId("tf-lb-") 202 d.Set("name", elbName) 203 } 204 205 tags := tagsFromMapELB(d.Get("tags").(map[string]interface{})) 206 // Provision the elb 207 elbOpts := &elb.CreateLoadBalancerInput{ 208 LoadBalancerName: aws.String(elbName), 209 Listeners: listeners, 210 Tags: tags, 211 } 212 213 if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) { 214 elbOpts.Scheme = aws.String("internal") 215 } 216 217 if v, ok := d.GetOk("availability_zones"); ok { 218 elbOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List()) 219 } 220 221 if v, ok := d.GetOk("security_groups"); ok { 222 elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List()) 223 } 224 225 if v, ok := d.GetOk("subnets"); ok { 226 elbOpts.Subnets = expandStringList(v.(*schema.Set).List()) 227 } 228 229 log.Printf("[DEBUG] ELB create configuration: %#v", elbOpts) 230 if _, err := elbconn.CreateLoadBalancer(elbOpts); err != nil { 231 return fmt.Errorf("Error creating ELB: %s", err) 232 } 233 234 // Assign the elb's unique identifier for use later 235 d.SetId(elbName) 236 log.Printf("[INFO] ELB ID: %s", d.Id()) 237 238 // Enable partial mode and record what we set 239 d.Partial(true) 240 d.SetPartial("name") 241 d.SetPartial("internal") 242 d.SetPartial("availability_zones") 243 d.SetPartial("listener") 244 d.SetPartial("security_groups") 245 d.SetPartial("subnets") 246 247 d.Set("tags", tagsToMapELB(tags)) 248 249 return resourceAwsElbUpdate(d, meta) 250 } 251 252 func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error { 253 elbconn := meta.(*AWSClient).elbconn 254 elbName := d.Id() 255 256 // Retrieve the ELB properties for updating the state 257 describeElbOpts := &elb.DescribeLoadBalancersInput{ 258 LoadBalancerNames: []*string{aws.String(elbName)}, 259 } 260 261 describeResp, err := elbconn.DescribeLoadBalancers(describeElbOpts) 262 if err != nil { 263 if isLoadBalancerNotFound(err) { 264 // The ELB is gone now, so just remove it from the state 265 d.SetId("") 266 return nil 267 } 268 269 return fmt.Errorf("Error retrieving ELB: %s", err) 270 } 271 if len(describeResp.LoadBalancerDescriptions) != 1 { 272 return fmt.Errorf("Unable to find ELB: %#v", describeResp.LoadBalancerDescriptions) 273 } 274 275 describeAttrsOpts := &elb.DescribeLoadBalancerAttributesInput{ 276 LoadBalancerName: aws.String(elbName), 277 } 278 describeAttrsResp, err := elbconn.DescribeLoadBalancerAttributes(describeAttrsOpts) 279 if err != nil { 280 if isLoadBalancerNotFound(err) { 281 // The ELB is gone now, so just remove it from the state 282 d.SetId("") 283 return nil 284 } 285 286 return fmt.Errorf("Error retrieving ELB: %s", err) 287 } 288 289 lbAttrs := describeAttrsResp.LoadBalancerAttributes 290 291 lb := describeResp.LoadBalancerDescriptions[0] 292 293 d.Set("name", *lb.LoadBalancerName) 294 d.Set("dns_name", *lb.DNSName) 295 d.Set("zone_id", *lb.CanonicalHostedZoneNameID) 296 d.Set("internal", *lb.Scheme == "internal") 297 d.Set("availability_zones", lb.AvailabilityZones) 298 d.Set("instances", flattenInstances(lb.Instances)) 299 d.Set("listener", flattenListeners(lb.ListenerDescriptions)) 300 d.Set("security_groups", lb.SecurityGroups) 301 if lb.SourceSecurityGroup != nil { 302 d.Set("source_security_group", lb.SourceSecurityGroup.GroupName) 303 } 304 d.Set("subnets", lb.Subnets) 305 d.Set("idle_timeout", lbAttrs.ConnectionSettings.IdleTimeout) 306 d.Set("connection_draining", lbAttrs.ConnectionDraining.Enabled) 307 d.Set("connection_draining_timeout", lbAttrs.ConnectionDraining.Timeout) 308 309 resp, err := elbconn.DescribeTags(&elb.DescribeTagsInput{ 310 LoadBalancerNames: []*string{lb.LoadBalancerName}, 311 }) 312 313 var et []*elb.Tag 314 if len(resp.TagDescriptions) > 0 { 315 et = resp.TagDescriptions[0].Tags 316 } 317 d.Set("tags", tagsToMapELB(et)) 318 // There's only one health check, so save that to state as we 319 // currently can 320 if *lb.HealthCheck.Target != "" { 321 d.Set("health_check", flattenHealthCheck(lb.HealthCheck)) 322 } 323 324 return nil 325 } 326 327 func resourceAwsElbUpdate(d *schema.ResourceData, meta interface{}) error { 328 elbconn := meta.(*AWSClient).elbconn 329 330 d.Partial(true) 331 332 if d.HasChange("listener") { 333 o, n := d.GetChange("listener") 334 os := o.(*schema.Set) 335 ns := n.(*schema.Set) 336 337 remove, _ := expandListeners(os.Difference(ns).List()) 338 add, _ := expandListeners(ns.Difference(os).List()) 339 340 if len(remove) > 0 { 341 ports := make([]*int64, 0, len(remove)) 342 for _, listener := range remove { 343 ports = append(ports, listener.LoadBalancerPort) 344 } 345 346 deleteListenersOpts := &elb.DeleteLoadBalancerListenersInput{ 347 LoadBalancerName: aws.String(d.Id()), 348 LoadBalancerPorts: ports, 349 } 350 351 _, err := elbconn.DeleteLoadBalancerListeners(deleteListenersOpts) 352 if err != nil { 353 return fmt.Errorf("Failure removing outdated ELB listeners: %s", err) 354 } 355 } 356 357 if len(add) > 0 { 358 createListenersOpts := &elb.CreateLoadBalancerListenersInput{ 359 LoadBalancerName: aws.String(d.Id()), 360 Listeners: add, 361 } 362 363 _, err := elbconn.CreateLoadBalancerListeners(createListenersOpts) 364 if err != nil { 365 return fmt.Errorf("Failure adding new or updated ELB listeners: %s", err) 366 } 367 } 368 369 d.SetPartial("listener") 370 } 371 372 // If we currently have instances, or did have instances, 373 // we want to figure out what to add and remove from the load 374 // balancer 375 if d.HasChange("instances") { 376 o, n := d.GetChange("instances") 377 os := o.(*schema.Set) 378 ns := n.(*schema.Set) 379 remove := expandInstanceString(os.Difference(ns).List()) 380 add := expandInstanceString(ns.Difference(os).List()) 381 382 if len(add) > 0 { 383 registerInstancesOpts := elb.RegisterInstancesWithLoadBalancerInput{ 384 LoadBalancerName: aws.String(d.Id()), 385 Instances: add, 386 } 387 388 _, err := elbconn.RegisterInstancesWithLoadBalancer(®isterInstancesOpts) 389 if err != nil { 390 return fmt.Errorf("Failure registering instances with ELB: %s", err) 391 } 392 } 393 if len(remove) > 0 { 394 deRegisterInstancesOpts := elb.DeregisterInstancesFromLoadBalancerInput{ 395 LoadBalancerName: aws.String(d.Id()), 396 Instances: remove, 397 } 398 399 _, err := elbconn.DeregisterInstancesFromLoadBalancer(&deRegisterInstancesOpts) 400 if err != nil { 401 return fmt.Errorf("Failure deregistering instances from ELB: %s", err) 402 } 403 } 404 405 d.SetPartial("instances") 406 } 407 408 if d.HasChange("cross_zone_load_balancing") || d.HasChange("idle_timeout") { 409 attrs := elb.ModifyLoadBalancerAttributesInput{ 410 LoadBalancerName: aws.String(d.Get("name").(string)), 411 LoadBalancerAttributes: &elb.LoadBalancerAttributes{ 412 CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{ 413 Enabled: aws.Bool(d.Get("cross_zone_load_balancing").(bool)), 414 }, 415 ConnectionSettings: &elb.ConnectionSettings{ 416 IdleTimeout: aws.Int64(int64(d.Get("idle_timeout").(int))), 417 }, 418 }, 419 } 420 421 _, err := elbconn.ModifyLoadBalancerAttributes(&attrs) 422 if err != nil { 423 return fmt.Errorf("Failure configuring ELB attributes: %s", err) 424 } 425 426 d.SetPartial("cross_zone_load_balancing") 427 d.SetPartial("idle_timeout") 428 d.SetPartial("connection_draining_timeout") 429 } 430 431 // We have to do these changes separately from everything else since 432 // they have some weird undocumented rules. You can't set the timeout 433 // without having connection draining to true, so we set that to true, 434 // set the timeout, then reset it to false if requested. 435 if d.HasChange("connection_draining") || d.HasChange("connection_draining_timeout") { 436 // We do timeout changes first since they require us to set draining 437 // to true for a hot second. 438 if d.HasChange("connection_draining_timeout") { 439 attrs := elb.ModifyLoadBalancerAttributesInput{ 440 LoadBalancerName: aws.String(d.Get("name").(string)), 441 LoadBalancerAttributes: &elb.LoadBalancerAttributes{ 442 ConnectionDraining: &elb.ConnectionDraining{ 443 Enabled: aws.Bool(true), 444 Timeout: aws.Int64(int64(d.Get("connection_draining_timeout").(int))), 445 }, 446 }, 447 } 448 449 _, err := elbconn.ModifyLoadBalancerAttributes(&attrs) 450 if err != nil { 451 return fmt.Errorf("Failure configuring ELB attributes: %s", err) 452 } 453 454 d.SetPartial("connection_draining_timeout") 455 } 456 457 // Then we always set connection draining even if there is no change. 458 // This lets us reset to "false" if requested even with a timeout 459 // change. 460 attrs := elb.ModifyLoadBalancerAttributesInput{ 461 LoadBalancerName: aws.String(d.Get("name").(string)), 462 LoadBalancerAttributes: &elb.LoadBalancerAttributes{ 463 ConnectionDraining: &elb.ConnectionDraining{ 464 Enabled: aws.Bool(d.Get("connection_draining").(bool)), 465 }, 466 }, 467 } 468 469 _, err := elbconn.ModifyLoadBalancerAttributes(&attrs) 470 if err != nil { 471 return fmt.Errorf("Failure configuring ELB attributes: %s", err) 472 } 473 474 d.SetPartial("connection_draining") 475 } 476 477 if d.HasChange("health_check") { 478 vs := d.Get("health_check").(*schema.Set).List() 479 if len(vs) > 0 { 480 check := vs[0].(map[string]interface{}) 481 configureHealthCheckOpts := elb.ConfigureHealthCheckInput{ 482 LoadBalancerName: aws.String(d.Id()), 483 HealthCheck: &elb.HealthCheck{ 484 HealthyThreshold: aws.Int64(int64(check["healthy_threshold"].(int))), 485 UnhealthyThreshold: aws.Int64(int64(check["unhealthy_threshold"].(int))), 486 Interval: aws.Int64(int64(check["interval"].(int))), 487 Target: aws.String(check["target"].(string)), 488 Timeout: aws.Int64(int64(check["timeout"].(int))), 489 }, 490 } 491 _, err := elbconn.ConfigureHealthCheck(&configureHealthCheckOpts) 492 if err != nil { 493 return fmt.Errorf("Failure configuring health check for ELB: %s", err) 494 } 495 d.SetPartial("health_check") 496 } 497 } 498 499 if d.HasChange("security_groups") { 500 groups := d.Get("security_groups").(*schema.Set).List() 501 502 applySecurityGroupsOpts := elb.ApplySecurityGroupsToLoadBalancerInput{ 503 LoadBalancerName: aws.String(d.Id()), 504 SecurityGroups: expandStringList(groups), 505 } 506 507 _, err := elbconn.ApplySecurityGroupsToLoadBalancer(&applySecurityGroupsOpts) 508 if err != nil { 509 return fmt.Errorf("Failure applying security groups to ELB: %s", err) 510 } 511 512 d.SetPartial("security_groups") 513 } 514 515 if err := setTagsELB(elbconn, d); err != nil { 516 return err 517 } 518 519 d.SetPartial("tags") 520 d.Partial(false) 521 522 return resourceAwsElbRead(d, meta) 523 } 524 525 func resourceAwsElbDelete(d *schema.ResourceData, meta interface{}) error { 526 elbconn := meta.(*AWSClient).elbconn 527 528 log.Printf("[INFO] Deleting ELB: %s", d.Id()) 529 530 // Destroy the load balancer 531 deleteElbOpts := elb.DeleteLoadBalancerInput{ 532 LoadBalancerName: aws.String(d.Id()), 533 } 534 if _, err := elbconn.DeleteLoadBalancer(&deleteElbOpts); err != nil { 535 return fmt.Errorf("Error deleting ELB: %s", err) 536 } 537 538 return nil 539 } 540 541 func resourceAwsElbHealthCheckHash(v interface{}) int { 542 var buf bytes.Buffer 543 m := v.(map[string]interface{}) 544 buf.WriteString(fmt.Sprintf("%d-", m["healthy_threshold"].(int))) 545 buf.WriteString(fmt.Sprintf("%d-", m["unhealthy_threshold"].(int))) 546 buf.WriteString(fmt.Sprintf("%s-", m["target"].(string))) 547 buf.WriteString(fmt.Sprintf("%d-", m["interval"].(int))) 548 buf.WriteString(fmt.Sprintf("%d-", m["timeout"].(int))) 549 550 return hashcode.String(buf.String()) 551 } 552 553 func resourceAwsElbListenerHash(v interface{}) int { 554 var buf bytes.Buffer 555 m := v.(map[string]interface{}) 556 buf.WriteString(fmt.Sprintf("%d-", m["instance_port"].(int))) 557 buf.WriteString(fmt.Sprintf("%s-", 558 strings.ToLower(m["instance_protocol"].(string)))) 559 buf.WriteString(fmt.Sprintf("%d-", m["lb_port"].(int))) 560 buf.WriteString(fmt.Sprintf("%s-", 561 strings.ToLower(m["lb_protocol"].(string)))) 562 563 if v, ok := m["ssl_certificate_id"]; ok { 564 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 565 } 566 567 return hashcode.String(buf.String()) 568 } 569 570 func isLoadBalancerNotFound(err error) bool { 571 elberr, ok := err.(awserr.Error) 572 return ok && elberr.Code() == "LoadBalancerNotFound" 573 } 574 575 func validateElbName(v interface{}, k string) (ws []string, errors []error) { 576 value := v.(string) 577 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 578 errors = append(errors, fmt.Errorf( 579 "only alphanumeric characters and hyphens allowed in %q: %q", 580 k, value)) 581 } 582 if len(value) > 32 { 583 errors = append(errors, fmt.Errorf( 584 "%q cannot be longer than 32 characters: %q", k, value)) 585 } 586 if regexp.MustCompile(`^-`).MatchString(value) { 587 errors = append(errors, fmt.Errorf( 588 "%q cannot begin with a hyphen: %q", k, value)) 589 } 590 if regexp.MustCompile(`-$`).MatchString(value) { 591 errors = append(errors, fmt.Errorf( 592 "%q cannot end with a hyphen: %q", k, value)) 593 } 594 return 595 596 }