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