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