github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/builtin/providers/aws/resource_aws_alb.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "regexp" 7 "strconv" 8 "time" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/service/elbv2" 12 "github.com/hashicorp/errwrap" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsAlb() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsAlbCreate, 20 Read: resourceAwsAlbRead, 21 Update: resourceAwsAlbUpdate, 22 Delete: resourceAwsAlbDelete, 23 Importer: &schema.ResourceImporter{ 24 State: schema.ImportStatePassthrough, 25 }, 26 27 Timeouts: &schema.ResourceTimeout{ 28 Create: schema.DefaultTimeout(10 * time.Minute), 29 Update: schema.DefaultTimeout(10 * time.Minute), 30 Delete: schema.DefaultTimeout(10 * time.Minute), 31 }, 32 33 Schema: map[string]*schema.Schema{ 34 "arn": { 35 Type: schema.TypeString, 36 Computed: true, 37 }, 38 39 "arn_suffix": { 40 Type: schema.TypeString, 41 Computed: true, 42 }, 43 44 "name": { 45 Type: schema.TypeString, 46 Optional: true, 47 Computed: true, 48 ForceNew: true, 49 ConflictsWith: []string{"name_prefix"}, 50 ValidateFunc: validateElbName, 51 }, 52 53 "name_prefix": { 54 Type: schema.TypeString, 55 Optional: true, 56 ForceNew: true, 57 ValidateFunc: validateElbNamePrefix, 58 }, 59 60 "internal": { 61 Type: schema.TypeBool, 62 Optional: true, 63 ForceNew: true, 64 Computed: true, 65 }, 66 67 "security_groups": { 68 Type: schema.TypeSet, 69 Elem: &schema.Schema{Type: schema.TypeString}, 70 Computed: true, 71 Optional: true, 72 Set: schema.HashString, 73 }, 74 75 "subnets": { 76 Type: schema.TypeSet, 77 Elem: &schema.Schema{Type: schema.TypeString}, 78 Required: true, 79 Set: schema.HashString, 80 }, 81 82 "access_logs": { 83 Type: schema.TypeList, 84 Optional: true, 85 MaxItems: 1, 86 Elem: &schema.Resource{ 87 Schema: map[string]*schema.Schema{ 88 "bucket": { 89 Type: schema.TypeString, 90 Required: true, 91 }, 92 "prefix": { 93 Type: schema.TypeString, 94 Optional: true, 95 }, 96 "enabled": { 97 Type: schema.TypeBool, 98 Optional: true, 99 Default: true, 100 }, 101 }, 102 }, 103 }, 104 105 "enable_deletion_protection": { 106 Type: schema.TypeBool, 107 Optional: true, 108 Default: false, 109 }, 110 111 "idle_timeout": { 112 Type: schema.TypeInt, 113 Optional: true, 114 Default: 60, 115 }, 116 117 "ip_address_type": { 118 Type: schema.TypeString, 119 Computed: true, 120 Optional: true, 121 }, 122 123 "vpc_id": { 124 Type: schema.TypeString, 125 Computed: true, 126 }, 127 128 "zone_id": { 129 Type: schema.TypeString, 130 Computed: true, 131 }, 132 133 "dns_name": { 134 Type: schema.TypeString, 135 Computed: true, 136 }, 137 138 "tags": tagsSchema(), 139 }, 140 } 141 } 142 143 func resourceAwsAlbCreate(d *schema.ResourceData, meta interface{}) error { 144 elbconn := meta.(*AWSClient).elbv2conn 145 146 var name string 147 if v, ok := d.GetOk("name"); ok { 148 name = v.(string) 149 } else if v, ok := d.GetOk("name_prefix"); ok { 150 name = resource.PrefixedUniqueId(v.(string)) 151 } else { 152 name = resource.PrefixedUniqueId("tf-lb-") 153 } 154 d.Set("name", name) 155 156 elbOpts := &elbv2.CreateLoadBalancerInput{ 157 Name: aws.String(name), 158 Tags: tagsFromMapELBv2(d.Get("tags").(map[string]interface{})), 159 } 160 161 if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) { 162 elbOpts.Scheme = aws.String("internal") 163 } 164 165 if v, ok := d.GetOk("security_groups"); ok { 166 elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List()) 167 } 168 169 if v, ok := d.GetOk("subnets"); ok { 170 elbOpts.Subnets = expandStringList(v.(*schema.Set).List()) 171 } 172 173 if v, ok := d.GetOk("ip_address_type"); ok { 174 elbOpts.IpAddressType = aws.String(v.(string)) 175 } 176 177 log.Printf("[DEBUG] ALB create configuration: %#v", elbOpts) 178 179 resp, err := elbconn.CreateLoadBalancer(elbOpts) 180 if err != nil { 181 return errwrap.Wrapf("Error creating Application Load Balancer: {{err}}", err) 182 } 183 184 if len(resp.LoadBalancers) != 1 { 185 return fmt.Errorf("No load balancers returned following creation of %s", d.Get("name").(string)) 186 } 187 188 lb := resp.LoadBalancers[0] 189 d.SetId(*lb.LoadBalancerArn) 190 log.Printf("[INFO] ALB ID: %s", d.Id()) 191 192 stateConf := &resource.StateChangeConf{ 193 Pending: []string{"provisioning", "failed"}, 194 Target: []string{"active"}, 195 Refresh: func() (interface{}, string, error) { 196 describeResp, err := elbconn.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{ 197 LoadBalancerArns: []*string{lb.LoadBalancerArn}, 198 }) 199 if err != nil { 200 return nil, "", err 201 } 202 203 if len(describeResp.LoadBalancers) != 1 { 204 return nil, "", fmt.Errorf("No load balancers returned for %s", *lb.LoadBalancerArn) 205 } 206 dLb := describeResp.LoadBalancers[0] 207 208 log.Printf("[INFO] ALB state: %s", *dLb.State.Code) 209 210 return describeResp, *dLb.State.Code, nil 211 }, 212 Timeout: d.Timeout(schema.TimeoutCreate), 213 MinTimeout: 10 * time.Second, 214 Delay: 30 * time.Second, // Wait 30 secs before starting 215 } 216 _, err = stateConf.WaitForState() 217 if err != nil { 218 return err 219 } 220 221 return resourceAwsAlbUpdate(d, meta) 222 } 223 224 func resourceAwsAlbRead(d *schema.ResourceData, meta interface{}) error { 225 elbconn := meta.(*AWSClient).elbv2conn 226 albArn := d.Id() 227 228 describeAlbOpts := &elbv2.DescribeLoadBalancersInput{ 229 LoadBalancerArns: []*string{aws.String(albArn)}, 230 } 231 232 describeResp, err := elbconn.DescribeLoadBalancers(describeAlbOpts) 233 if err != nil { 234 if isLoadBalancerNotFound(err) { 235 // The ALB is gone now, so just remove it from the state 236 log.Printf("[WARN] ALB %s not found in AWS, removing from state", d.Id()) 237 d.SetId("") 238 return nil 239 } 240 241 return errwrap.Wrapf("Error retrieving ALB: {{err}}", err) 242 } 243 if len(describeResp.LoadBalancers) != 1 { 244 return fmt.Errorf("Unable to find ALB: %#v", describeResp.LoadBalancers) 245 } 246 247 return flattenAwsAlbResource(d, meta, describeResp.LoadBalancers[0]) 248 } 249 250 func resourceAwsAlbUpdate(d *schema.ResourceData, meta interface{}) error { 251 elbconn := meta.(*AWSClient).elbv2conn 252 253 if !d.IsNewResource() { 254 if err := setElbV2Tags(elbconn, d); err != nil { 255 return errwrap.Wrapf("Error Modifying Tags on ALB: {{err}}", err) 256 } 257 } 258 259 attributes := make([]*elbv2.LoadBalancerAttribute, 0) 260 261 if d.HasChange("access_logs") { 262 logs := d.Get("access_logs").([]interface{}) 263 if len(logs) == 1 { 264 log := logs[0].(map[string]interface{}) 265 266 attributes = append(attributes, 267 &elbv2.LoadBalancerAttribute{ 268 Key: aws.String("access_logs.s3.enabled"), 269 Value: aws.String(strconv.FormatBool(log["enabled"].(bool))), 270 }, 271 &elbv2.LoadBalancerAttribute{ 272 Key: aws.String("access_logs.s3.bucket"), 273 Value: aws.String(log["bucket"].(string)), 274 }) 275 276 if prefix, ok := log["prefix"]; ok { 277 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 278 Key: aws.String("access_logs.s3.prefix"), 279 Value: aws.String(prefix.(string)), 280 }) 281 } 282 } else if len(logs) == 0 { 283 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 284 Key: aws.String("access_logs.s3.enabled"), 285 Value: aws.String("false"), 286 }) 287 } 288 } 289 290 if d.HasChange("enable_deletion_protection") { 291 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 292 Key: aws.String("deletion_protection.enabled"), 293 Value: aws.String(fmt.Sprintf("%t", d.Get("enable_deletion_protection").(bool))), 294 }) 295 } 296 297 if d.HasChange("idle_timeout") { 298 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 299 Key: aws.String("idle_timeout.timeout_seconds"), 300 Value: aws.String(fmt.Sprintf("%d", d.Get("idle_timeout").(int))), 301 }) 302 } 303 304 if len(attributes) != 0 { 305 input := &elbv2.ModifyLoadBalancerAttributesInput{ 306 LoadBalancerArn: aws.String(d.Id()), 307 Attributes: attributes, 308 } 309 310 log.Printf("[DEBUG] ALB Modify Load Balancer Attributes Request: %#v", input) 311 _, err := elbconn.ModifyLoadBalancerAttributes(input) 312 if err != nil { 313 return fmt.Errorf("Failure configuring ALB attributes: %s", err) 314 } 315 } 316 317 if d.HasChange("security_groups") { 318 sgs := expandStringList(d.Get("security_groups").(*schema.Set).List()) 319 320 params := &elbv2.SetSecurityGroupsInput{ 321 LoadBalancerArn: aws.String(d.Id()), 322 SecurityGroups: sgs, 323 } 324 _, err := elbconn.SetSecurityGroups(params) 325 if err != nil { 326 return fmt.Errorf("Failure Setting ALB Security Groups: %s", err) 327 } 328 329 } 330 331 if d.HasChange("subnets") { 332 subnets := expandStringList(d.Get("subnets").(*schema.Set).List()) 333 334 params := &elbv2.SetSubnetsInput{ 335 LoadBalancerArn: aws.String(d.Id()), 336 Subnets: subnets, 337 } 338 339 _, err := elbconn.SetSubnets(params) 340 if err != nil { 341 return fmt.Errorf("Failure Setting ALB Subnets: %s", err) 342 } 343 } 344 345 if d.HasChange("ip_address_type") { 346 347 params := &elbv2.SetIpAddressTypeInput{ 348 LoadBalancerArn: aws.String(d.Id()), 349 IpAddressType: aws.String(d.Get("ip_address_type").(string)), 350 } 351 352 _, err := elbconn.SetIpAddressType(params) 353 if err != nil { 354 return fmt.Errorf("Failure Setting ALB IP Address Type: %s", err) 355 } 356 357 } 358 359 stateConf := &resource.StateChangeConf{ 360 Pending: []string{"active", "provisioning", "failed"}, 361 Target: []string{"active"}, 362 Refresh: func() (interface{}, string, error) { 363 describeResp, err := elbconn.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{ 364 LoadBalancerArns: []*string{aws.String(d.Id())}, 365 }) 366 if err != nil { 367 return nil, "", err 368 } 369 370 if len(describeResp.LoadBalancers) != 1 { 371 return nil, "", fmt.Errorf("No load balancers returned for %s", d.Id()) 372 } 373 dLb := describeResp.LoadBalancers[0] 374 375 log.Printf("[INFO] ALB state: %s", *dLb.State.Code) 376 377 return describeResp, *dLb.State.Code, nil 378 }, 379 Timeout: d.Timeout(schema.TimeoutUpdate), 380 MinTimeout: 10 * time.Second, 381 Delay: 30 * time.Second, // Wait 30 secs before starting 382 } 383 _, err := stateConf.WaitForState() 384 if err != nil { 385 return err 386 } 387 388 return resourceAwsAlbRead(d, meta) 389 } 390 391 func resourceAwsAlbDelete(d *schema.ResourceData, meta interface{}) error { 392 albconn := meta.(*AWSClient).elbv2conn 393 394 log.Printf("[INFO] Deleting ALB: %s", d.Id()) 395 396 // Destroy the load balancer 397 deleteElbOpts := elbv2.DeleteLoadBalancerInput{ 398 LoadBalancerArn: aws.String(d.Id()), 399 } 400 if _, err := albconn.DeleteLoadBalancer(&deleteElbOpts); err != nil { 401 return fmt.Errorf("Error deleting ALB: %s", err) 402 } 403 404 return nil 405 } 406 407 // flattenSubnetsFromAvailabilityZones creates a slice of strings containing the subnet IDs 408 // for the ALB based on the AvailabilityZones structure returned by the API. 409 func flattenSubnetsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []string { 410 var result []string 411 for _, az := range availabilityZones { 412 result = append(result, *az.SubnetId) 413 } 414 return result 415 } 416 417 func albSuffixFromARN(arn *string) string { 418 if arn == nil { 419 return "" 420 } 421 422 if arnComponents := regexp.MustCompile(`arn:.*:loadbalancer/(.*)`).FindAllStringSubmatch(*arn, -1); len(arnComponents) == 1 { 423 if len(arnComponents[0]) == 2 { 424 return arnComponents[0][1] 425 } 426 } 427 428 return "" 429 } 430 431 // flattenAwsAlbResource takes a *elbv2.LoadBalancer and populates all respective resource fields. 432 func flattenAwsAlbResource(d *schema.ResourceData, meta interface{}, alb *elbv2.LoadBalancer) error { 433 elbconn := meta.(*AWSClient).elbv2conn 434 435 d.Set("arn", alb.LoadBalancerArn) 436 d.Set("arn_suffix", albSuffixFromARN(alb.LoadBalancerArn)) 437 d.Set("name", alb.LoadBalancerName) 438 d.Set("internal", (alb.Scheme != nil && *alb.Scheme == "internal")) 439 d.Set("security_groups", flattenStringList(alb.SecurityGroups)) 440 d.Set("subnets", flattenSubnetsFromAvailabilityZones(alb.AvailabilityZones)) 441 d.Set("vpc_id", alb.VpcId) 442 d.Set("zone_id", alb.CanonicalHostedZoneId) 443 d.Set("dns_name", alb.DNSName) 444 d.Set("ip_address_type", alb.IpAddressType) 445 446 respTags, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{ 447 ResourceArns: []*string{alb.LoadBalancerArn}, 448 }) 449 if err != nil { 450 return errwrap.Wrapf("Error retrieving ALB Tags: {{err}}", err) 451 } 452 453 var et []*elbv2.Tag 454 if len(respTags.TagDescriptions) > 0 { 455 et = respTags.TagDescriptions[0].Tags 456 } 457 d.Set("tags", tagsToMapELBv2(et)) 458 459 attributesResp, err := elbconn.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{ 460 LoadBalancerArn: aws.String(d.Id()), 461 }) 462 if err != nil { 463 return errwrap.Wrapf("Error retrieving ALB Attributes: {{err}}", err) 464 } 465 466 accessLogMap := map[string]interface{}{} 467 for _, attr := range attributesResp.Attributes { 468 switch *attr.Key { 469 case "access_logs.s3.enabled": 470 accessLogMap["enabled"] = *attr.Value 471 case "access_logs.s3.bucket": 472 accessLogMap["bucket"] = *attr.Value 473 case "access_logs.s3.prefix": 474 accessLogMap["prefix"] = *attr.Value 475 case "idle_timeout.timeout_seconds": 476 timeout, err := strconv.Atoi(*attr.Value) 477 if err != nil { 478 return errwrap.Wrapf("Error parsing ALB timeout: {{err}}", err) 479 } 480 log.Printf("[DEBUG] Setting ALB Timeout Seconds: %d", timeout) 481 d.Set("idle_timeout", timeout) 482 case "deletion_protection.enabled": 483 protectionEnabled := (*attr.Value) == "true" 484 log.Printf("[DEBUG] Setting ALB Deletion Protection Enabled: %t", protectionEnabled) 485 d.Set("enable_deletion_protection", protectionEnabled) 486 } 487 } 488 489 log.Printf("[DEBUG] Setting ALB Access Logs: %#v", accessLogMap) 490 if accessLogMap["bucket"] != "" || accessLogMap["prefix"] != "" { 491 d.Set("access_logs", []interface{}{accessLogMap}) 492 } else { 493 d.Set("access_logs", []interface{}{}) 494 } 495 496 return nil 497 }