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