github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/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: validateElbName, 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 "vpc_id": { 112 Type: schema.TypeString, 113 Computed: true, 114 }, 115 116 "zone_id": { 117 Type: schema.TypeString, 118 Computed: true, 119 }, 120 121 "dns_name": { 122 Type: schema.TypeString, 123 Computed: true, 124 }, 125 126 "tags": tagsSchema(), 127 }, 128 } 129 } 130 131 func resourceAwsAlbCreate(d *schema.ResourceData, meta interface{}) error { 132 elbconn := meta.(*AWSClient).elbv2conn 133 134 var name string 135 if v, ok := d.GetOk("name"); ok { 136 name = v.(string) 137 } else if v, ok := d.GetOk("name_prefix"); ok { 138 name = resource.PrefixedUniqueId(v.(string)) 139 } else { 140 name = resource.PrefixedUniqueId("tf-lb-") 141 } 142 d.Set("name", name) 143 144 elbOpts := &elbv2.CreateLoadBalancerInput{ 145 Name: aws.String(name), 146 Tags: tagsFromMapELBv2(d.Get("tags").(map[string]interface{})), 147 } 148 149 if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) { 150 elbOpts.Scheme = aws.String("internal") 151 } 152 153 if v, ok := d.GetOk("security_groups"); ok { 154 elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List()) 155 } 156 157 if v, ok := d.GetOk("subnets"); ok { 158 elbOpts.Subnets = expandStringList(v.(*schema.Set).List()) 159 } 160 161 log.Printf("[DEBUG] ALB create configuration: %#v", elbOpts) 162 163 resp, err := elbconn.CreateLoadBalancer(elbOpts) 164 if err != nil { 165 return errwrap.Wrapf("Error creating Application Load Balancer: {{err}}", err) 166 } 167 168 if len(resp.LoadBalancers) != 1 { 169 return fmt.Errorf("No load balancers returned following creation of %s", d.Get("name").(string)) 170 } 171 172 lb := resp.LoadBalancers[0] 173 d.SetId(*lb.LoadBalancerArn) 174 log.Printf("[INFO] ALB ID: %s", d.Id()) 175 176 stateConf := &resource.StateChangeConf{ 177 Pending: []string{"active", "provisioning", "failed"}, 178 Target: []string{"active"}, 179 Refresh: func() (interface{}, string, error) { 180 describeResp, err := elbconn.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{ 181 LoadBalancerArns: []*string{lb.LoadBalancerArn}, 182 }) 183 if err != nil { 184 return nil, "", err 185 } 186 187 if len(describeResp.LoadBalancers) != 1 { 188 return nil, "", fmt.Errorf("No load balancers returned for %s", *lb.LoadBalancerArn) 189 } 190 dLb := describeResp.LoadBalancers[0] 191 192 log.Printf("[INFO] ALB state: %s", *dLb.State.Code) 193 194 return describeResp, *dLb.State.Code, nil 195 }, 196 Timeout: 5 * time.Minute, 197 MinTimeout: 3 * time.Second, 198 } 199 _, err = stateConf.WaitForState() 200 if err != nil { 201 return err 202 } 203 204 return resourceAwsAlbUpdate(d, meta) 205 } 206 207 func resourceAwsAlbRead(d *schema.ResourceData, meta interface{}) error { 208 elbconn := meta.(*AWSClient).elbv2conn 209 albArn := d.Id() 210 211 describeAlbOpts := &elbv2.DescribeLoadBalancersInput{ 212 LoadBalancerArns: []*string{aws.String(albArn)}, 213 } 214 215 describeResp, err := elbconn.DescribeLoadBalancers(describeAlbOpts) 216 if err != nil { 217 if isLoadBalancerNotFound(err) { 218 // The ALB is gone now, so just remove it from the state 219 log.Printf("[WARN] ALB %s not found in AWS, removing from state", d.Id()) 220 d.SetId("") 221 return nil 222 } 223 224 return errwrap.Wrapf("Error retrieving ALB: {{err}}", err) 225 } 226 if len(describeResp.LoadBalancers) != 1 { 227 return fmt.Errorf("Unable to find ALB: %#v", describeResp.LoadBalancers) 228 } 229 230 return flattenAwsAlbResource(d, meta, describeResp.LoadBalancers[0]) 231 } 232 233 func resourceAwsAlbUpdate(d *schema.ResourceData, meta interface{}) error { 234 elbconn := meta.(*AWSClient).elbv2conn 235 236 if !d.IsNewResource() { 237 if err := setElbV2Tags(elbconn, d); err != nil { 238 return errwrap.Wrapf("Error Modifying Tags on ALB: {{err}}", err) 239 } 240 } 241 242 attributes := make([]*elbv2.LoadBalancerAttribute, 0) 243 244 if d.HasChange("access_logs") { 245 logs := d.Get("access_logs").([]interface{}) 246 if len(logs) == 1 { 247 log := logs[0].(map[string]interface{}) 248 249 attributes = append(attributes, 250 &elbv2.LoadBalancerAttribute{ 251 Key: aws.String("access_logs.s3.enabled"), 252 Value: aws.String(strconv.FormatBool(log["enabled"].(bool))), 253 }, 254 &elbv2.LoadBalancerAttribute{ 255 Key: aws.String("access_logs.s3.bucket"), 256 Value: aws.String(log["bucket"].(string)), 257 }) 258 259 if prefix, ok := log["prefix"]; ok { 260 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 261 Key: aws.String("access_logs.s3.prefix"), 262 Value: aws.String(prefix.(string)), 263 }) 264 } 265 } else if len(logs) == 0 { 266 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 267 Key: aws.String("access_logs.s3.enabled"), 268 Value: aws.String("false"), 269 }) 270 } 271 } 272 273 if d.HasChange("enable_deletion_protection") { 274 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 275 Key: aws.String("deletion_protection.enabled"), 276 Value: aws.String(fmt.Sprintf("%t", d.Get("enable_deletion_protection").(bool))), 277 }) 278 } 279 280 if d.HasChange("idle_timeout") { 281 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 282 Key: aws.String("idle_timeout.timeout_seconds"), 283 Value: aws.String(fmt.Sprintf("%d", d.Get("idle_timeout").(int))), 284 }) 285 } 286 287 if len(attributes) != 0 { 288 input := &elbv2.ModifyLoadBalancerAttributesInput{ 289 LoadBalancerArn: aws.String(d.Id()), 290 Attributes: attributes, 291 } 292 293 log.Printf("[DEBUG] ALB Modify Load Balancer Attributes Request: %#v", input) 294 _, err := elbconn.ModifyLoadBalancerAttributes(input) 295 if err != nil { 296 return fmt.Errorf("Failure configuring ALB attributes: %s", err) 297 } 298 } 299 300 if d.HasChange("security_groups") { 301 sgs := expandStringList(d.Get("security_groups").(*schema.Set).List()) 302 303 params := &elbv2.SetSecurityGroupsInput{ 304 LoadBalancerArn: aws.String(d.Id()), 305 SecurityGroups: sgs, 306 } 307 _, err := elbconn.SetSecurityGroups(params) 308 if err != nil { 309 return fmt.Errorf("Failure Setting ALB Security Groups: %s", err) 310 } 311 312 } 313 314 if d.HasChange("subnets") { 315 subnets := expandStringList(d.Get("subnets").(*schema.Set).List()) 316 317 params := &elbv2.SetSubnetsInput{ 318 LoadBalancerArn: aws.String(d.Id()), 319 Subnets: subnets, 320 } 321 322 _, err := elbconn.SetSubnets(params) 323 if err != nil { 324 return fmt.Errorf("Failure Setting ALB Subnets: %s", err) 325 } 326 } 327 328 return resourceAwsAlbRead(d, meta) 329 } 330 331 func resourceAwsAlbDelete(d *schema.ResourceData, meta interface{}) error { 332 albconn := meta.(*AWSClient).elbv2conn 333 334 log.Printf("[INFO] Deleting ALB: %s", d.Id()) 335 336 // Destroy the load balancer 337 deleteElbOpts := elbv2.DeleteLoadBalancerInput{ 338 LoadBalancerArn: aws.String(d.Id()), 339 } 340 if _, err := albconn.DeleteLoadBalancer(&deleteElbOpts); err != nil { 341 return fmt.Errorf("Error deleting ALB: %s", err) 342 } 343 344 return nil 345 } 346 347 // flattenSubnetsFromAvailabilityZones creates a slice of strings containing the subnet IDs 348 // for the ALB based on the AvailabilityZones structure returned by the API. 349 func flattenSubnetsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []string { 350 var result []string 351 for _, az := range availabilityZones { 352 result = append(result, *az.SubnetId) 353 } 354 return result 355 } 356 357 func albSuffixFromARN(arn *string) string { 358 if arn == nil { 359 return "" 360 } 361 362 if arnComponents := regexp.MustCompile(`arn:.*:loadbalancer/(.*)`).FindAllStringSubmatch(*arn, -1); len(arnComponents) == 1 { 363 if len(arnComponents[0]) == 2 { 364 return arnComponents[0][1] 365 } 366 } 367 368 return "" 369 } 370 371 // flattenAwsAlbResource takes a *elbv2.LoadBalancer and populates all respective resource fields. 372 func flattenAwsAlbResource(d *schema.ResourceData, meta interface{}, alb *elbv2.LoadBalancer) error { 373 elbconn := meta.(*AWSClient).elbv2conn 374 375 d.Set("arn", alb.LoadBalancerArn) 376 d.Set("arn_suffix", albSuffixFromARN(alb.LoadBalancerArn)) 377 d.Set("name", alb.LoadBalancerName) 378 d.Set("internal", (alb.Scheme != nil && *alb.Scheme == "internal")) 379 d.Set("security_groups", flattenStringList(alb.SecurityGroups)) 380 d.Set("subnets", flattenSubnetsFromAvailabilityZones(alb.AvailabilityZones)) 381 d.Set("vpc_id", alb.VpcId) 382 d.Set("zone_id", alb.CanonicalHostedZoneId) 383 d.Set("dns_name", alb.DNSName) 384 385 respTags, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{ 386 ResourceArns: []*string{alb.LoadBalancerArn}, 387 }) 388 if err != nil { 389 return errwrap.Wrapf("Error retrieving ALB Tags: {{err}}", err) 390 } 391 392 var et []*elbv2.Tag 393 if len(respTags.TagDescriptions) > 0 { 394 et = respTags.TagDescriptions[0].Tags 395 } 396 d.Set("tags", tagsToMapELBv2(et)) 397 398 attributesResp, err := elbconn.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{ 399 LoadBalancerArn: aws.String(d.Id()), 400 }) 401 if err != nil { 402 return errwrap.Wrapf("Error retrieving ALB Attributes: {{err}}", err) 403 } 404 405 accessLogMap := map[string]interface{}{} 406 for _, attr := range attributesResp.Attributes { 407 switch *attr.Key { 408 case "access_logs.s3.enabled": 409 accessLogMap["enabled"] = *attr.Value 410 case "access_logs.s3.bucket": 411 accessLogMap["bucket"] = *attr.Value 412 case "access_logs.s3.prefix": 413 accessLogMap["prefix"] = *attr.Value 414 case "idle_timeout.timeout_seconds": 415 timeout, err := strconv.Atoi(*attr.Value) 416 if err != nil { 417 return errwrap.Wrapf("Error parsing ALB timeout: {{err}}", err) 418 } 419 log.Printf("[DEBUG] Setting ALB Timeout Seconds: %d", timeout) 420 d.Set("idle_timeout", timeout) 421 case "deletion_protection.enabled": 422 protectionEnabled := (*attr.Value) == "true" 423 log.Printf("[DEBUG] Setting ALB Deletion Protection Enabled: %t", protectionEnabled) 424 d.Set("enable_deletion_protection", protectionEnabled) 425 } 426 } 427 428 log.Printf("[DEBUG] Setting ALB Access Logs: %#v", accessLogMap) 429 if accessLogMap["bucket"] != "" || accessLogMap["prefix"] != "" { 430 d.Set("access_logs", []interface{}{accessLogMap}) 431 } else { 432 d.Set("access_logs", []interface{}{}) 433 } 434 435 return nil 436 }