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