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