github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/builtin/providers/aws/resource_aws_alb.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "regexp" 7 "strconv" 8 9 "github.com/aws/aws-sdk-go/aws" 10 "github.com/aws/aws-sdk-go/service/elbv2" 11 "github.com/hashicorp/errwrap" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 ) 15 16 func resourceAwsAlb() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceAwsAlbCreate, 19 Read: resourceAwsAlbRead, 20 Update: resourceAwsAlbUpdate, 21 Delete: resourceAwsAlbDelete, 22 Importer: &schema.ResourceImporter{ 23 State: schema.ImportStatePassthrough, 24 }, 25 26 Schema: map[string]*schema.Schema{ 27 "arn": { 28 Type: schema.TypeString, 29 Computed: true, 30 }, 31 32 "arn_suffix": { 33 Type: schema.TypeString, 34 Computed: true, 35 }, 36 37 "name": { 38 Type: schema.TypeString, 39 Optional: true, 40 Computed: true, 41 ForceNew: true, 42 ConflictsWith: []string{"name_prefix"}, 43 ValidateFunc: validateElbName, 44 }, 45 46 "name_prefix": { 47 Type: schema.TypeString, 48 Optional: true, 49 ForceNew: true, 50 ValidateFunc: validateElbName, 51 }, 52 53 "internal": { 54 Type: schema.TypeBool, 55 Optional: true, 56 ForceNew: true, 57 Computed: true, 58 }, 59 60 "security_groups": { 61 Type: schema.TypeSet, 62 Elem: &schema.Schema{Type: schema.TypeString}, 63 Computed: true, 64 Optional: true, 65 Set: schema.HashString, 66 }, 67 68 "subnets": { 69 Type: schema.TypeSet, 70 Elem: &schema.Schema{Type: schema.TypeString}, 71 ForceNew: true, 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 d.SetId(*resp.LoadBalancers[0].LoadBalancerArn) 173 log.Printf("[INFO] ALB ID: %s", d.Id()) 174 175 return resourceAwsAlbUpdate(d, meta) 176 } 177 178 func resourceAwsAlbRead(d *schema.ResourceData, meta interface{}) error { 179 elbconn := meta.(*AWSClient).elbv2conn 180 albArn := d.Id() 181 182 describeAlbOpts := &elbv2.DescribeLoadBalancersInput{ 183 LoadBalancerArns: []*string{aws.String(albArn)}, 184 } 185 186 describeResp, err := elbconn.DescribeLoadBalancers(describeAlbOpts) 187 if err != nil { 188 if isLoadBalancerNotFound(err) { 189 // The ALB is gone now, so just remove it from the state 190 log.Printf("[WARN] ALB %s not found in AWS, removing from state", d.Id()) 191 d.SetId("") 192 return nil 193 } 194 195 return errwrap.Wrapf("Error retrieving ALB: {{err}}", err) 196 } 197 if len(describeResp.LoadBalancers) != 1 { 198 return fmt.Errorf("Unable to find ALB: %#v", describeResp.LoadBalancers) 199 } 200 201 return flattenAwsAlbResource(d, meta, describeResp.LoadBalancers[0]) 202 } 203 204 func resourceAwsAlbUpdate(d *schema.ResourceData, meta interface{}) error { 205 elbconn := meta.(*AWSClient).elbv2conn 206 207 if !d.IsNewResource() { 208 if err := setElbV2Tags(elbconn, d); err != nil { 209 return errwrap.Wrapf("Error Modifying Tags on ALB: {{err}}", err) 210 } 211 } 212 213 attributes := make([]*elbv2.LoadBalancerAttribute, 0) 214 215 if d.HasChange("access_logs") { 216 logs := d.Get("access_logs").([]interface{}) 217 if len(logs) == 1 { 218 log := logs[0].(map[string]interface{}) 219 220 attributes = append(attributes, 221 &elbv2.LoadBalancerAttribute{ 222 Key: aws.String("access_logs.s3.enabled"), 223 Value: aws.String(strconv.FormatBool(log["enabled"].(bool))), 224 }, 225 &elbv2.LoadBalancerAttribute{ 226 Key: aws.String("access_logs.s3.bucket"), 227 Value: aws.String(log["bucket"].(string)), 228 }) 229 230 if prefix, ok := log["prefix"]; ok { 231 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 232 Key: aws.String("access_logs.s3.prefix"), 233 Value: aws.String(prefix.(string)), 234 }) 235 } 236 } else if len(logs) == 0 { 237 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 238 Key: aws.String("access_logs.s3.enabled"), 239 Value: aws.String("false"), 240 }) 241 } 242 } 243 244 if d.HasChange("enable_deletion_protection") { 245 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 246 Key: aws.String("deletion_protection.enabled"), 247 Value: aws.String(fmt.Sprintf("%t", d.Get("enable_deletion_protection").(bool))), 248 }) 249 } 250 251 if d.HasChange("idle_timeout") { 252 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 253 Key: aws.String("idle_timeout.timeout_seconds"), 254 Value: aws.String(fmt.Sprintf("%d", d.Get("idle_timeout").(int))), 255 }) 256 } 257 258 if len(attributes) != 0 { 259 input := &elbv2.ModifyLoadBalancerAttributesInput{ 260 LoadBalancerArn: aws.String(d.Id()), 261 Attributes: attributes, 262 } 263 264 log.Printf("[DEBUG] ALB Modify Load Balancer Attributes Request: %#v", input) 265 _, err := elbconn.ModifyLoadBalancerAttributes(input) 266 if err != nil { 267 return fmt.Errorf("Failure configuring ALB attributes: %s", err) 268 } 269 } 270 271 if d.HasChange("security_groups") { 272 sgs := expandStringList(d.Get("security_groups").(*schema.Set).List()) 273 274 params := &elbv2.SetSecurityGroupsInput{ 275 LoadBalancerArn: aws.String(d.Id()), 276 SecurityGroups: sgs, 277 } 278 _, err := elbconn.SetSecurityGroups(params) 279 if err != nil { 280 return fmt.Errorf("Failure Setting ALB Security Groups: %s", err) 281 } 282 283 } 284 285 return resourceAwsAlbRead(d, meta) 286 } 287 288 func resourceAwsAlbDelete(d *schema.ResourceData, meta interface{}) error { 289 albconn := meta.(*AWSClient).elbv2conn 290 291 log.Printf("[INFO] Deleting ALB: %s", d.Id()) 292 293 // Destroy the load balancer 294 deleteElbOpts := elbv2.DeleteLoadBalancerInput{ 295 LoadBalancerArn: aws.String(d.Id()), 296 } 297 if _, err := albconn.DeleteLoadBalancer(&deleteElbOpts); err != nil { 298 return fmt.Errorf("Error deleting ALB: %s", err) 299 } 300 301 return nil 302 } 303 304 // flattenSubnetsFromAvailabilityZones creates a slice of strings containing the subnet IDs 305 // for the ALB based on the AvailabilityZones structure returned by the API. 306 func flattenSubnetsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []string { 307 var result []string 308 for _, az := range availabilityZones { 309 result = append(result, *az.SubnetId) 310 } 311 return result 312 } 313 314 func albSuffixFromARN(arn *string) string { 315 if arn == nil { 316 return "" 317 } 318 319 if arnComponents := regexp.MustCompile(`arn:.*:loadbalancer/(.*)`).FindAllStringSubmatch(*arn, -1); len(arnComponents) == 1 { 320 if len(arnComponents[0]) == 2 { 321 return arnComponents[0][1] 322 } 323 } 324 325 return "" 326 } 327 328 // flattenAwsAlbResource takes a *elbv2.LoadBalancer and populates all respective resource fields. 329 func flattenAwsAlbResource(d *schema.ResourceData, meta interface{}, alb *elbv2.LoadBalancer) error { 330 elbconn := meta.(*AWSClient).elbv2conn 331 332 d.Set("arn", alb.LoadBalancerArn) 333 d.Set("arn_suffix", albSuffixFromARN(alb.LoadBalancerArn)) 334 d.Set("name", alb.LoadBalancerName) 335 d.Set("internal", (alb.Scheme != nil && *alb.Scheme == "internal")) 336 d.Set("security_groups", flattenStringList(alb.SecurityGroups)) 337 d.Set("subnets", flattenSubnetsFromAvailabilityZones(alb.AvailabilityZones)) 338 d.Set("vpc_id", alb.VpcId) 339 d.Set("zone_id", alb.CanonicalHostedZoneId) 340 d.Set("dns_name", alb.DNSName) 341 342 respTags, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{ 343 ResourceArns: []*string{alb.LoadBalancerArn}, 344 }) 345 if err != nil { 346 return errwrap.Wrapf("Error retrieving ALB Tags: {{err}}", err) 347 } 348 349 var et []*elbv2.Tag 350 if len(respTags.TagDescriptions) > 0 { 351 et = respTags.TagDescriptions[0].Tags 352 } 353 d.Set("tags", tagsToMapELBv2(et)) 354 355 attributesResp, err := elbconn.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{ 356 LoadBalancerArn: aws.String(d.Id()), 357 }) 358 if err != nil { 359 return errwrap.Wrapf("Error retrieving ALB Attributes: {{err}}", err) 360 } 361 362 accessLogMap := map[string]interface{}{} 363 for _, attr := range attributesResp.Attributes { 364 switch *attr.Key { 365 case "access_logs.s3.enabled": 366 accessLogMap["enabled"] = *attr.Value 367 case "access_logs.s3.bucket": 368 accessLogMap["bucket"] = *attr.Value 369 case "access_logs.s3.prefix": 370 accessLogMap["prefix"] = *attr.Value 371 case "idle_timeout.timeout_seconds": 372 timeout, err := strconv.Atoi(*attr.Value) 373 if err != nil { 374 return errwrap.Wrapf("Error parsing ALB timeout: {{err}}", err) 375 } 376 log.Printf("[DEBUG] Setting ALB Timeout Seconds: %d", timeout) 377 d.Set("idle_timeout", timeout) 378 case "deletion_protection.enabled": 379 protectionEnabled := (*attr.Value) == "true" 380 log.Printf("[DEBUG] Setting ALB Deletion Protection Enabled: %t", protectionEnabled) 381 d.Set("enable_deletion_protection", protectionEnabled) 382 } 383 } 384 385 log.Printf("[DEBUG] Setting ALB Access Logs: %#v", accessLogMap) 386 if accessLogMap["bucket"] != "" || accessLogMap["prefix"] != "" { 387 d.Set("access_logs", []interface{}{accessLogMap}) 388 } else { 389 d.Set("access_logs", []interface{}{}) 390 } 391 392 return nil 393 }