github.com/alexissmirnov/terraform@v0.4.3-0.20150423153700-1ef9731a2f14/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: func(v interface{}) int { 47 return hashcode.String(v.(string)) 48 }, 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: func(v interface{}) int { 57 return hashcode.String(v.(string)) 58 }, 59 }, 60 61 // TODO: could be not ForceNew 62 "security_groups": &schema.Schema{ 63 Type: schema.TypeSet, 64 Elem: &schema.Schema{Type: schema.TypeString}, 65 Optional: true, 66 ForceNew: true, 67 Computed: true, 68 Set: func(v interface{}) int { 69 return hashcode.String(v.(string)) 70 }, 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: func(v interface{}) int { 80 return hashcode.String(v.(string)) 81 }, 82 }, 83 84 "idle_timeout": &schema.Schema{ 85 Type: schema.TypeInt, 86 Optional: true, 87 Default: 60, 88 }, 89 90 "connection_draining": &schema.Schema{ 91 Type: schema.TypeBool, 92 Optional: true, 93 Default: false, 94 }, 95 96 "connection_draining_timeout": &schema.Schema{ 97 Type: schema.TypeInt, 98 Optional: true, 99 Default: 300, 100 }, 101 102 "listener": &schema.Schema{ 103 Type: schema.TypeSet, 104 Required: true, 105 Elem: &schema.Resource{ 106 Schema: map[string]*schema.Schema{ 107 "instance_port": &schema.Schema{ 108 Type: schema.TypeInt, 109 Required: true, 110 }, 111 112 "instance_protocol": &schema.Schema{ 113 Type: schema.TypeString, 114 Required: true, 115 }, 116 117 "lb_port": &schema.Schema{ 118 Type: schema.TypeInt, 119 Required: true, 120 }, 121 122 "lb_protocol": &schema.Schema{ 123 Type: schema.TypeString, 124 Required: true, 125 }, 126 127 "ssl_certificate_id": &schema.Schema{ 128 Type: schema.TypeString, 129 Optional: true, 130 }, 131 }, 132 }, 133 Set: resourceAwsElbListenerHash, 134 }, 135 136 "health_check": &schema.Schema{ 137 Type: schema.TypeSet, 138 Optional: true, 139 Computed: true, 140 Elem: &schema.Resource{ 141 Schema: map[string]*schema.Schema{ 142 "healthy_threshold": &schema.Schema{ 143 Type: schema.TypeInt, 144 Required: true, 145 }, 146 147 "unhealthy_threshold": &schema.Schema{ 148 Type: schema.TypeInt, 149 Required: true, 150 }, 151 152 "target": &schema.Schema{ 153 Type: schema.TypeString, 154 Required: true, 155 }, 156 157 "interval": &schema.Schema{ 158 Type: schema.TypeInt, 159 Required: true, 160 }, 161 162 "timeout": &schema.Schema{ 163 Type: schema.TypeInt, 164 Required: true, 165 }, 166 }, 167 }, 168 Set: resourceAwsElbHealthCheckHash, 169 }, 170 171 "dns_name": &schema.Schema{ 172 Type: schema.TypeString, 173 Computed: true, 174 }, 175 176 "tags": tagsSchema(), 177 }, 178 } 179 } 180 181 func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error { 182 elbconn := meta.(*AWSClient).elbconn 183 184 // Expand the "listener" set to aws-sdk-go compat []*elb.Listener 185 listeners, err := expandListeners(d.Get("listener").(*schema.Set).List()) 186 if err != nil { 187 return err 188 } 189 190 tags := tagsFromMapELB(d.Get("tags").(map[string]interface{})) 191 // Provision the elb 192 elbOpts := &elb.CreateLoadBalancerInput{ 193 LoadBalancerName: aws.String(d.Get("name").(string)), 194 Listeners: listeners, 195 Tags: tags, 196 } 197 198 if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) { 199 elbOpts.Scheme = aws.String("internal") 200 } 201 202 if v, ok := d.GetOk("availability_zones"); ok { 203 elbOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List()) 204 } 205 206 if v, ok := d.GetOk("security_groups"); ok { 207 elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List()) 208 } 209 210 if v, ok := d.GetOk("subnets"); ok { 211 elbOpts.Subnets = expandStringList(v.(*schema.Set).List()) 212 } 213 214 log.Printf("[DEBUG] ELB create configuration: %#v", elbOpts) 215 if _, err := elbconn.CreateLoadBalancer(elbOpts); err != nil { 216 return fmt.Errorf("Error creating ELB: %s", err) 217 } 218 219 // Assign the elb's unique identifier for use later 220 d.SetId(d.Get("name").(string)) 221 log.Printf("[INFO] ELB ID: %s", d.Id()) 222 223 // Enable partial mode and record what we set 224 d.Partial(true) 225 d.SetPartial("name") 226 d.SetPartial("internal") 227 d.SetPartial("availability_zones") 228 d.SetPartial("listener") 229 d.SetPartial("security_groups") 230 d.SetPartial("subnets") 231 232 d.Set("tags", tagsToMapELB(tags)) 233 234 return resourceAwsElbUpdate(d, meta) 235 } 236 237 func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error { 238 elbconn := meta.(*AWSClient).elbconn 239 elbName := d.Id() 240 241 // Retrieve the ELB properties for updating the state 242 describeElbOpts := &elb.DescribeLoadBalancersInput{ 243 LoadBalancerNames: []*string{aws.String(elbName)}, 244 } 245 246 describeResp, err := elbconn.DescribeLoadBalancers(describeElbOpts) 247 if err != nil { 248 if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { 249 // The ELB is gone now, so just remove it from the state 250 d.SetId("") 251 return nil 252 } 253 254 return fmt.Errorf("Error retrieving ELB: %s", err) 255 } 256 if len(describeResp.LoadBalancerDescriptions) != 1 { 257 return fmt.Errorf("Unable to find ELB: %#v", describeResp.LoadBalancerDescriptions) 258 } 259 260 describeAttrsOpts := &elb.DescribeLoadBalancerAttributesInput{ 261 LoadBalancerName: aws.String(elbName), 262 } 263 describeAttrsResp, err := elbconn.DescribeLoadBalancerAttributes(describeAttrsOpts) 264 if err != nil { 265 if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { 266 // The ELB is gone now, so just remove it from the state 267 d.SetId("") 268 return nil 269 } 270 271 return fmt.Errorf("Error retrieving ELB: %s", err) 272 } 273 274 lbAttrs := describeAttrsResp.LoadBalancerAttributes 275 276 lb := describeResp.LoadBalancerDescriptions[0] 277 278 d.Set("name", *lb.LoadBalancerName) 279 d.Set("dns_name", *lb.DNSName) 280 d.Set("internal", *lb.Scheme == "internal") 281 d.Set("availability_zones", lb.AvailabilityZones) 282 d.Set("instances", flattenInstances(lb.Instances)) 283 d.Set("listener", flattenListeners(lb.ListenerDescriptions)) 284 d.Set("security_groups", lb.SecurityGroups) 285 d.Set("subnets", lb.Subnets) 286 d.Set("idle_timeout", lbAttrs.ConnectionSettings.IdleTimeout) 287 d.Set("connection_draining", lbAttrs.ConnectionDraining.Enabled) 288 d.Set("connection_draining_timeout", lbAttrs.ConnectionDraining.Timeout) 289 290 resp, err := elbconn.DescribeTags(&elb.DescribeTagsInput{ 291 LoadBalancerNames: []*string{lb.LoadBalancerName}, 292 }) 293 294 var et []*elb.Tag 295 if len(resp.TagDescriptions) > 0 { 296 et = resp.TagDescriptions[0].Tags 297 } 298 d.Set("tags", tagsToMapELB(et)) 299 // There's only one health check, so save that to state as we 300 // currently can 301 if *lb.HealthCheck.Target != "" { 302 d.Set("health_check", flattenHealthCheck(lb.HealthCheck)) 303 } 304 305 return nil 306 } 307 308 func resourceAwsElbUpdate(d *schema.ResourceData, meta interface{}) error { 309 elbconn := meta.(*AWSClient).elbconn 310 311 d.Partial(true) 312 313 if d.HasChange("listener") { 314 o, n := d.GetChange("listener") 315 os := o.(*schema.Set) 316 ns := n.(*schema.Set) 317 318 remove, _ := expandListeners(os.Difference(ns).List()) 319 add, _ := expandListeners(ns.Difference(os).List()) 320 321 if len(remove) > 0 { 322 ports := make([]*int64, 0, len(remove)) 323 for _, listener := range remove { 324 ports = append(ports, listener.LoadBalancerPort) 325 } 326 327 deleteListenersOpts := &elb.DeleteLoadBalancerListenersInput{ 328 LoadBalancerName: aws.String(d.Id()), 329 LoadBalancerPorts: ports, 330 } 331 332 _, err := elbconn.DeleteLoadBalancerListeners(deleteListenersOpts) 333 if err != nil { 334 return fmt.Errorf("Failure removing outdated listeners: %s", err) 335 } 336 } 337 338 if len(add) > 0 { 339 createListenersOpts := &elb.CreateLoadBalancerListenersInput{ 340 LoadBalancerName: aws.String(d.Id()), 341 Listeners: add, 342 } 343 344 _, err := elbconn.CreateLoadBalancerListeners(createListenersOpts) 345 if err != nil { 346 return fmt.Errorf("Failure adding new or updated listeners: %s", err) 347 } 348 } 349 350 d.SetPartial("listener") 351 } 352 353 // If we currently have instances, or did have instances, 354 // we want to figure out what to add and remove from the load 355 // balancer 356 if d.HasChange("instances") { 357 o, n := d.GetChange("instances") 358 os := o.(*schema.Set) 359 ns := n.(*schema.Set) 360 remove := expandInstanceString(os.Difference(ns).List()) 361 add := expandInstanceString(ns.Difference(os).List()) 362 363 if len(add) > 0 { 364 registerInstancesOpts := elb.RegisterInstancesWithLoadBalancerInput{ 365 LoadBalancerName: aws.String(d.Id()), 366 Instances: add, 367 } 368 369 _, err := elbconn.RegisterInstancesWithLoadBalancer(®isterInstancesOpts) 370 if err != nil { 371 return fmt.Errorf("Failure registering instances: %s", err) 372 } 373 } 374 if len(remove) > 0 { 375 deRegisterInstancesOpts := elb.DeregisterInstancesFromLoadBalancerInput{ 376 LoadBalancerName: aws.String(d.Id()), 377 Instances: remove, 378 } 379 380 _, err := elbconn.DeregisterInstancesFromLoadBalancer(&deRegisterInstancesOpts) 381 if err != nil { 382 return fmt.Errorf("Failure deregistering instances: %s", err) 383 } 384 } 385 386 d.SetPartial("instances") 387 } 388 389 log.Println("[INFO] outside modify attributes") 390 if d.HasChange("cross_zone_load_balancing") || d.HasChange("idle_timeout") || d.HasChange("connection_draining") || d.HasChange("connection_draining_timeout") { 391 log.Println("[INFO] inside modify attributes") 392 attrs := elb.ModifyLoadBalancerAttributesInput{ 393 LoadBalancerName: aws.String(d.Get("name").(string)), 394 LoadBalancerAttributes: &elb.LoadBalancerAttributes{ 395 CrossZoneLoadBalancing: &elb.CrossZoneLoadBalancing{ 396 Enabled: aws.Boolean(d.Get("cross_zone_load_balancing").(bool)), 397 }, 398 ConnectionSettings: &elb.ConnectionSettings{ 399 IdleTimeout: aws.Long(int64(d.Get("idle_timeout").(int))), 400 }, 401 ConnectionDraining: &elb.ConnectionDraining{ 402 Enabled: aws.Boolean(d.Get("connection_draining").(bool)), 403 Timeout: aws.Long(int64(d.Get("connection_draining_timeout").(int))), 404 }, 405 }, 406 } 407 _, err := elbconn.ModifyLoadBalancerAttributes(&attrs) 408 if err != nil { 409 return fmt.Errorf("Failure configuring elb attributes: %s", err) 410 } 411 d.SetPartial("cross_zone_load_balancing") 412 d.SetPartial("idle_timeout") 413 d.SetPartial("connection_draining") 414 d.SetPartial("connection_draining_timeout") 415 } 416 417 if d.HasChange("health_check") { 418 vs := d.Get("health_check").(*schema.Set).List() 419 if len(vs) > 0 { 420 check := vs[0].(map[string]interface{}) 421 configureHealthCheckOpts := elb.ConfigureHealthCheckInput{ 422 LoadBalancerName: aws.String(d.Id()), 423 HealthCheck: &elb.HealthCheck{ 424 HealthyThreshold: aws.Long(int64(check["healthy_threshold"].(int))), 425 UnhealthyThreshold: aws.Long(int64(check["unhealthy_threshold"].(int))), 426 Interval: aws.Long(int64(check["interval"].(int))), 427 Target: aws.String(check["target"].(string)), 428 Timeout: aws.Long(int64(check["timeout"].(int))), 429 }, 430 } 431 _, err := elbconn.ConfigureHealthCheck(&configureHealthCheckOpts) 432 if err != nil { 433 return fmt.Errorf("Failure configuring health check: %s", err) 434 } 435 d.SetPartial("health_check") 436 } 437 } 438 439 if err := setTagsELB(elbconn, d); err != nil { 440 return err 441 } 442 443 d.SetPartial("tags") 444 d.Partial(false) 445 446 return resourceAwsElbRead(d, meta) 447 } 448 449 func resourceAwsElbDelete(d *schema.ResourceData, meta interface{}) error { 450 elbconn := meta.(*AWSClient).elbconn 451 452 log.Printf("[INFO] Deleting ELB: %s", d.Id()) 453 454 // Destroy the load balancer 455 deleteElbOpts := elb.DeleteLoadBalancerInput{ 456 LoadBalancerName: aws.String(d.Id()), 457 } 458 if _, err := elbconn.DeleteLoadBalancer(&deleteElbOpts); err != nil { 459 return fmt.Errorf("Error deleting ELB: %s", err) 460 } 461 462 return nil 463 } 464 465 func resourceAwsElbHealthCheckHash(v interface{}) int { 466 var buf bytes.Buffer 467 m := v.(map[string]interface{}) 468 buf.WriteString(fmt.Sprintf("%d-", m["healthy_threshold"].(int))) 469 buf.WriteString(fmt.Sprintf("%d-", m["unhealthy_threshold"].(int))) 470 buf.WriteString(fmt.Sprintf("%s-", m["target"].(string))) 471 buf.WriteString(fmt.Sprintf("%d-", m["interval"].(int))) 472 buf.WriteString(fmt.Sprintf("%d-", m["timeout"].(int))) 473 474 return hashcode.String(buf.String()) 475 } 476 477 func resourceAwsElbListenerHash(v interface{}) int { 478 var buf bytes.Buffer 479 m := v.(map[string]interface{}) 480 buf.WriteString(fmt.Sprintf("%d-", m["instance_port"].(int))) 481 buf.WriteString(fmt.Sprintf("%s-", m["instance_protocol"].(string))) 482 buf.WriteString(fmt.Sprintf("%d-", m["lb_port"].(int))) 483 buf.WriteString(fmt.Sprintf("%s-", m["lb_protocol"].(string))) 484 485 if v, ok := m["ssl_certificate_id"]; ok { 486 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 487 } 488 489 return hashcode.String(buf.String()) 490 }