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