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