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