github.com/rhenning/terraform@v0.8.0-beta2/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 alb := describeResp.LoadBalancers[0] 202 203 d.Set("arn", alb.LoadBalancerArn) 204 d.Set("arn_suffix", albSuffixFromARN(alb.LoadBalancerArn)) 205 d.Set("name", alb.LoadBalancerName) 206 d.Set("internal", (alb.Scheme != nil && *alb.Scheme == "internal")) 207 d.Set("security_groups", flattenStringList(alb.SecurityGroups)) 208 d.Set("subnets", flattenSubnetsFromAvailabilityZones(alb.AvailabilityZones)) 209 d.Set("vpc_id", alb.VpcId) 210 d.Set("zone_id", alb.CanonicalHostedZoneId) 211 d.Set("dns_name", alb.DNSName) 212 213 respTags, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{ 214 ResourceArns: []*string{alb.LoadBalancerArn}, 215 }) 216 if err != nil { 217 return errwrap.Wrapf("Error retrieving ALB Tags: {{err}}", err) 218 } 219 220 var et []*elbv2.Tag 221 if len(respTags.TagDescriptions) > 0 { 222 et = respTags.TagDescriptions[0].Tags 223 } 224 d.Set("tags", tagsToMapELBv2(et)) 225 226 attributesResp, err := elbconn.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{ 227 LoadBalancerArn: aws.String(d.Id()), 228 }) 229 if err != nil { 230 return errwrap.Wrapf("Error retrieving ALB Attributes: {{err}}", err) 231 } 232 233 accessLogMap := map[string]interface{}{} 234 for _, attr := range attributesResp.Attributes { 235 switch *attr.Key { 236 case "access_logs.s3.enabled": 237 accessLogMap["enabled"] = *attr.Value 238 case "access_logs.s3.bucket": 239 accessLogMap["bucket"] = *attr.Value 240 case "access_logs.s3.prefix": 241 accessLogMap["prefix"] = *attr.Value 242 case "idle_timeout.timeout_seconds": 243 timeout, err := strconv.Atoi(*attr.Value) 244 if err != nil { 245 return errwrap.Wrapf("Error parsing ALB timeout: {{err}}", err) 246 } 247 log.Printf("[DEBUG] Setting ALB Timeout Seconds: %d", timeout) 248 d.Set("idle_timeout", timeout) 249 case "deletion_protection.enabled": 250 protectionEnabled := (*attr.Value) == "true" 251 log.Printf("[DEBUG] Setting ALB Deletion Protection Enabled: %t", protectionEnabled) 252 d.Set("enable_deletion_protection", protectionEnabled) 253 } 254 } 255 256 log.Printf("[DEBUG] Setting ALB Access Logs: %#v", accessLogMap) 257 if accessLogMap["bucket"] != "" || accessLogMap["prefix"] != "" { 258 d.Set("access_logs", []interface{}{accessLogMap}) 259 } else { 260 d.Set("access_logs", []interface{}{}) 261 } 262 263 return nil 264 } 265 266 func resourceAwsAlbUpdate(d *schema.ResourceData, meta interface{}) error { 267 elbconn := meta.(*AWSClient).elbv2conn 268 269 if !d.IsNewResource() { 270 if err := setElbV2Tags(elbconn, d); err != nil { 271 return errwrap.Wrapf("Error Modifying Tags on ALB: {{err}}", err) 272 } 273 } 274 275 attributes := make([]*elbv2.LoadBalancerAttribute, 0) 276 277 if d.HasChange("access_logs") { 278 logs := d.Get("access_logs").([]interface{}) 279 if len(logs) == 1 { 280 log := logs[0].(map[string]interface{}) 281 282 attributes = append(attributes, 283 &elbv2.LoadBalancerAttribute{ 284 Key: aws.String("access_logs.s3.enabled"), 285 Value: aws.String(strconv.FormatBool(log["enabled"].(bool))), 286 }, 287 &elbv2.LoadBalancerAttribute{ 288 Key: aws.String("access_logs.s3.bucket"), 289 Value: aws.String(log["bucket"].(string)), 290 }) 291 292 if prefix, ok := log["prefix"]; ok { 293 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 294 Key: aws.String("access_logs.s3.prefix"), 295 Value: aws.String(prefix.(string)), 296 }) 297 } 298 } else if len(logs) == 0 { 299 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 300 Key: aws.String("access_logs.s3.enabled"), 301 Value: aws.String("false"), 302 }) 303 } 304 } 305 306 if d.HasChange("enable_deletion_protection") { 307 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 308 Key: aws.String("deletion_protection.enabled"), 309 Value: aws.String(fmt.Sprintf("%t", d.Get("enable_deletion_protection").(bool))), 310 }) 311 } 312 313 if d.HasChange("idle_timeout") { 314 attributes = append(attributes, &elbv2.LoadBalancerAttribute{ 315 Key: aws.String("idle_timeout.timeout_seconds"), 316 Value: aws.String(fmt.Sprintf("%d", d.Get("idle_timeout").(int))), 317 }) 318 } 319 320 if len(attributes) != 0 { 321 input := &elbv2.ModifyLoadBalancerAttributesInput{ 322 LoadBalancerArn: aws.String(d.Id()), 323 Attributes: attributes, 324 } 325 326 log.Printf("[DEBUG] ALB Modify Load Balancer Attributes Request: %#v", input) 327 _, err := elbconn.ModifyLoadBalancerAttributes(input) 328 if err != nil { 329 return fmt.Errorf("Failure configuring ALB attributes: %s", err) 330 } 331 } 332 333 if d.HasChange("security_groups") { 334 sgs := expandStringList(d.Get("security_groups").(*schema.Set).List()) 335 336 params := &elbv2.SetSecurityGroupsInput{ 337 LoadBalancerArn: aws.String(d.Id()), 338 SecurityGroups: sgs, 339 } 340 _, err := elbconn.SetSecurityGroups(params) 341 if err != nil { 342 return fmt.Errorf("Failure Setting ALB Security Groups: %s", err) 343 } 344 345 } 346 347 return resourceAwsAlbRead(d, meta) 348 } 349 350 func resourceAwsAlbDelete(d *schema.ResourceData, meta interface{}) error { 351 albconn := meta.(*AWSClient).elbv2conn 352 353 log.Printf("[INFO] Deleting ALB: %s", d.Id()) 354 355 // Destroy the load balancer 356 deleteElbOpts := elbv2.DeleteLoadBalancerInput{ 357 LoadBalancerArn: aws.String(d.Id()), 358 } 359 if _, err := albconn.DeleteLoadBalancer(&deleteElbOpts); err != nil { 360 return fmt.Errorf("Error deleting ALB: %s", err) 361 } 362 363 return nil 364 } 365 366 // flattenSubnetsFromAvailabilityZones creates a slice of strings containing the subnet IDs 367 // for the ALB based on the AvailabilityZones structure returned by the API. 368 func flattenSubnetsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []string { 369 var result []string 370 for _, az := range availabilityZones { 371 result = append(result, *az.SubnetId) 372 } 373 return result 374 } 375 376 func albSuffixFromARN(arn *string) string { 377 if arn == nil { 378 return "" 379 } 380 381 if arnComponents := regexp.MustCompile(`arn:.*:loadbalancer/(.*)`).FindAllStringSubmatch(*arn, -1); len(arnComponents) == 1 { 382 if len(arnComponents[0]) == 2 { 383 return arnComponents[0][1] 384 } 385 } 386 387 return "" 388 }