github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_alb_target_group.go (about) 1 package aws 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "regexp" 8 "strconv" 9 "strings" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/awserr" 13 "github.com/aws/aws-sdk-go/service/elbv2" 14 "github.com/hashicorp/errwrap" 15 "github.com/hashicorp/terraform/helper/resource" 16 "github.com/hashicorp/terraform/helper/schema" 17 ) 18 19 func resourceAwsAlbTargetGroup() *schema.Resource { 20 return &schema.Resource{ 21 Create: resourceAwsAlbTargetGroupCreate, 22 Read: resourceAwsAlbTargetGroupRead, 23 Update: resourceAwsAlbTargetGroupUpdate, 24 Delete: resourceAwsAlbTargetGroupDelete, 25 Importer: &schema.ResourceImporter{ 26 State: schema.ImportStatePassthrough, 27 }, 28 29 Schema: map[string]*schema.Schema{ 30 "arn": { 31 Type: schema.TypeString, 32 Computed: true, 33 }, 34 35 "arn_suffix": { 36 Type: schema.TypeString, 37 Computed: true, 38 }, 39 40 "name": { 41 Type: schema.TypeString, 42 Optional: true, 43 Computed: true, 44 ForceNew: true, 45 ConflictsWith: []string{"name_prefix"}, 46 ValidateFunc: validateAwsAlbTargetGroupName, 47 }, 48 "name_prefix": { 49 Type: schema.TypeString, 50 Optional: true, 51 ForceNew: true, 52 ValidateFunc: validateAwsAlbTargetGroupNamePrefix, 53 }, 54 55 "port": { 56 Type: schema.TypeInt, 57 Required: true, 58 ForceNew: true, 59 ValidateFunc: validateAwsAlbTargetGroupPort, 60 }, 61 62 "protocol": { 63 Type: schema.TypeString, 64 Required: true, 65 ForceNew: true, 66 ValidateFunc: validateAwsAlbTargetGroupProtocol, 67 }, 68 69 "vpc_id": { 70 Type: schema.TypeString, 71 Required: true, 72 ForceNew: true, 73 }, 74 75 "deregistration_delay": { 76 Type: schema.TypeInt, 77 Optional: true, 78 Default: 300, 79 ValidateFunc: validateAwsAlbTargetGroupDeregistrationDelay, 80 }, 81 82 "stickiness": { 83 Type: schema.TypeList, 84 Optional: true, 85 Computed: true, 86 MaxItems: 1, 87 Elem: &schema.Resource{ 88 Schema: map[string]*schema.Schema{ 89 "enabled": { 90 Type: schema.TypeBool, 91 Optional: true, 92 Default: true, 93 }, 94 "type": { 95 Type: schema.TypeString, 96 Required: true, 97 ValidateFunc: validateAwsAlbTargetGroupStickinessType, 98 }, 99 "cookie_duration": { 100 Type: schema.TypeInt, 101 Optional: true, 102 Default: 86400, 103 ValidateFunc: validateAwsAlbTargetGroupStickinessCookieDuration, 104 }, 105 }, 106 }, 107 }, 108 109 "health_check": { 110 Type: schema.TypeList, 111 Optional: true, 112 Computed: true, 113 MaxItems: 1, 114 Elem: &schema.Resource{ 115 Schema: map[string]*schema.Schema{ 116 "interval": { 117 Type: schema.TypeInt, 118 Optional: true, 119 Default: 30, 120 }, 121 122 "path": { 123 Type: schema.TypeString, 124 Optional: true, 125 Default: "/", 126 ValidateFunc: validateAwsAlbTargetGroupHealthCheckPath, 127 }, 128 129 "port": { 130 Type: schema.TypeString, 131 Optional: true, 132 Default: "traffic-port", 133 ValidateFunc: validateAwsAlbTargetGroupHealthCheckPort, 134 }, 135 136 "protocol": { 137 Type: schema.TypeString, 138 Optional: true, 139 Default: "HTTP", 140 StateFunc: func(v interface{}) string { 141 return strings.ToUpper(v.(string)) 142 }, 143 ValidateFunc: validateAwsAlbTargetGroupHealthCheckProtocol, 144 }, 145 146 "timeout": { 147 Type: schema.TypeInt, 148 Optional: true, 149 Default: 5, 150 ValidateFunc: validateAwsAlbTargetGroupHealthCheckTimeout, 151 }, 152 153 "healthy_threshold": { 154 Type: schema.TypeInt, 155 Optional: true, 156 Default: 5, 157 ValidateFunc: validateAwsAlbTargetGroupHealthCheckHealthyThreshold, 158 }, 159 160 "matcher": { 161 Type: schema.TypeString, 162 Optional: true, 163 Default: "200", 164 }, 165 166 "unhealthy_threshold": { 167 Type: schema.TypeInt, 168 Optional: true, 169 Default: 2, 170 ValidateFunc: validateAwsAlbTargetGroupHealthCheckHealthyThreshold, 171 }, 172 }, 173 }, 174 }, 175 176 "tags": tagsSchema(), 177 }, 178 } 179 } 180 181 func resourceAwsAlbTargetGroupCreate(d *schema.ResourceData, meta interface{}) error { 182 elbconn := meta.(*AWSClient).elbv2conn 183 184 var groupName string 185 if v, ok := d.GetOk("name"); ok { 186 groupName = v.(string) 187 } else if v, ok := d.GetOk("name_prefix"); ok { 188 groupName = resource.PrefixedUniqueId(v.(string)) 189 } else { 190 groupName = resource.PrefixedUniqueId("tf-") 191 } 192 193 params := &elbv2.CreateTargetGroupInput{ 194 Name: aws.String(groupName), 195 Port: aws.Int64(int64(d.Get("port").(int))), 196 Protocol: aws.String(d.Get("protocol").(string)), 197 VpcId: aws.String(d.Get("vpc_id").(string)), 198 } 199 200 if healthChecks := d.Get("health_check").([]interface{}); len(healthChecks) == 1 { 201 healthCheck := healthChecks[0].(map[string]interface{}) 202 203 params.HealthCheckIntervalSeconds = aws.Int64(int64(healthCheck["interval"].(int))) 204 params.HealthCheckPath = aws.String(healthCheck["path"].(string)) 205 params.HealthCheckPort = aws.String(healthCheck["port"].(string)) 206 params.HealthCheckProtocol = aws.String(healthCheck["protocol"].(string)) 207 params.HealthCheckTimeoutSeconds = aws.Int64(int64(healthCheck["timeout"].(int))) 208 params.HealthyThresholdCount = aws.Int64(int64(healthCheck["healthy_threshold"].(int))) 209 params.UnhealthyThresholdCount = aws.Int64(int64(healthCheck["unhealthy_threshold"].(int))) 210 params.Matcher = &elbv2.Matcher{ 211 HttpCode: aws.String(healthCheck["matcher"].(string)), 212 } 213 } 214 215 resp, err := elbconn.CreateTargetGroup(params) 216 if err != nil { 217 return errwrap.Wrapf("Error creating ALB Target Group: {{err}}", err) 218 } 219 220 if len(resp.TargetGroups) == 0 { 221 return errors.New("Error creating ALB Target Group: no groups returned in response") 222 } 223 224 targetGroupArn := resp.TargetGroups[0].TargetGroupArn 225 d.SetId(*targetGroupArn) 226 227 return resourceAwsAlbTargetGroupUpdate(d, meta) 228 } 229 230 func resourceAwsAlbTargetGroupRead(d *schema.ResourceData, meta interface{}) error { 231 elbconn := meta.(*AWSClient).elbv2conn 232 233 resp, err := elbconn.DescribeTargetGroups(&elbv2.DescribeTargetGroupsInput{ 234 TargetGroupArns: []*string{aws.String(d.Id())}, 235 }) 236 if err != nil { 237 if isTargetGroupNotFound(err) { 238 log.Printf("[DEBUG] DescribeTargetGroups - removing %s from state", d.Id()) 239 d.SetId("") 240 return nil 241 } 242 return errwrap.Wrapf("Error retrieving Target Group: {{err}}", err) 243 } 244 245 if len(resp.TargetGroups) != 1 { 246 return fmt.Errorf("Error retrieving Target Group %q", d.Id()) 247 } 248 249 targetGroup := resp.TargetGroups[0] 250 251 d.Set("arn", targetGroup.TargetGroupArn) 252 d.Set("arn_suffix", albTargetGroupSuffixFromARN(targetGroup.TargetGroupArn)) 253 d.Set("name", targetGroup.TargetGroupName) 254 d.Set("port", targetGroup.Port) 255 d.Set("protocol", targetGroup.Protocol) 256 d.Set("vpc_id", targetGroup.VpcId) 257 258 healthCheck := make(map[string]interface{}) 259 healthCheck["interval"] = *targetGroup.HealthCheckIntervalSeconds 260 healthCheck["path"] = *targetGroup.HealthCheckPath 261 healthCheck["port"] = *targetGroup.HealthCheckPort 262 healthCheck["protocol"] = *targetGroup.HealthCheckProtocol 263 healthCheck["timeout"] = *targetGroup.HealthCheckTimeoutSeconds 264 healthCheck["healthy_threshold"] = *targetGroup.HealthyThresholdCount 265 healthCheck["unhealthy_threshold"] = *targetGroup.UnhealthyThresholdCount 266 healthCheck["matcher"] = *targetGroup.Matcher.HttpCode 267 d.Set("health_check", []interface{}{healthCheck}) 268 269 attrResp, err := elbconn.DescribeTargetGroupAttributes(&elbv2.DescribeTargetGroupAttributesInput{ 270 TargetGroupArn: aws.String(d.Id()), 271 }) 272 if err != nil { 273 return errwrap.Wrapf("Error retrieving Target Group Attributes: {{err}}", err) 274 } 275 276 stickinessMap := map[string]interface{}{} 277 for _, attr := range attrResp.Attributes { 278 switch *attr.Key { 279 case "stickiness.enabled": 280 enabled, err := strconv.ParseBool(*attr.Value) 281 if err != nil { 282 return fmt.Errorf("Error converting stickiness.enabled to bool: %s", *attr.Value) 283 } 284 stickinessMap["enabled"] = enabled 285 case "stickiness.type": 286 stickinessMap["type"] = *attr.Value 287 case "stickiness.lb_cookie.duration_seconds": 288 duration, err := strconv.Atoi(*attr.Value) 289 if err != nil { 290 return fmt.Errorf("Error converting stickiness.lb_cookie.duration_seconds to int: %s", *attr.Value) 291 } 292 stickinessMap["cookie_duration"] = duration 293 case "deregistration_delay.timeout_seconds": 294 timeout, err := strconv.Atoi(*attr.Value) 295 if err != nil { 296 return fmt.Errorf("Error converting deregistration_delay.timeout_seconds to int: %s", *attr.Value) 297 } 298 d.Set("deregistration_delay", timeout) 299 } 300 } 301 302 if err := d.Set("stickiness", []interface{}{stickinessMap}); err != nil { 303 return err 304 } 305 306 tagsResp, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{ 307 ResourceArns: []*string{aws.String(d.Id())}, 308 }) 309 if err != nil { 310 return errwrap.Wrapf("Error retrieving Target Group Tags: {{err}}", err) 311 } 312 for _, t := range tagsResp.TagDescriptions { 313 if *t.ResourceArn == d.Id() { 314 if err := d.Set("tags", tagsToMapELBv2(t.Tags)); err != nil { 315 return err 316 } 317 } 318 } 319 320 return nil 321 } 322 323 func resourceAwsAlbTargetGroupUpdate(d *schema.ResourceData, meta interface{}) error { 324 elbconn := meta.(*AWSClient).elbv2conn 325 326 if err := setElbV2Tags(elbconn, d); err != nil { 327 return errwrap.Wrapf("Error Modifying Tags on ALB Target Group: {{err}}", err) 328 } 329 330 if d.HasChange("health_check") { 331 healthChecks := d.Get("health_check").([]interface{}) 332 333 var params *elbv2.ModifyTargetGroupInput 334 if len(healthChecks) == 1 { 335 healthCheck := healthChecks[0].(map[string]interface{}) 336 337 params = &elbv2.ModifyTargetGroupInput{ 338 TargetGroupArn: aws.String(d.Id()), 339 HealthCheckIntervalSeconds: aws.Int64(int64(healthCheck["interval"].(int))), 340 HealthCheckPath: aws.String(healthCheck["path"].(string)), 341 HealthCheckPort: aws.String(healthCheck["port"].(string)), 342 HealthCheckProtocol: aws.String(healthCheck["protocol"].(string)), 343 HealthCheckTimeoutSeconds: aws.Int64(int64(healthCheck["timeout"].(int))), 344 HealthyThresholdCount: aws.Int64(int64(healthCheck["healthy_threshold"].(int))), 345 UnhealthyThresholdCount: aws.Int64(int64(healthCheck["unhealthy_threshold"].(int))), 346 Matcher: &elbv2.Matcher{ 347 HttpCode: aws.String(healthCheck["matcher"].(string)), 348 }, 349 } 350 } else { 351 params = &elbv2.ModifyTargetGroupInput{ 352 TargetGroupArn: aws.String(d.Id()), 353 } 354 } 355 356 _, err := elbconn.ModifyTargetGroup(params) 357 if err != nil { 358 return errwrap.Wrapf("Error modifying Target Group: {{err}}", err) 359 } 360 } 361 362 var attrs []*elbv2.TargetGroupAttribute 363 364 if d.HasChange("deregistration_delay") { 365 attrs = append(attrs, &elbv2.TargetGroupAttribute{ 366 Key: aws.String("deregistration_delay.timeout_seconds"), 367 Value: aws.String(fmt.Sprintf("%d", d.Get("deregistration_delay").(int))), 368 }) 369 } 370 371 if d.HasChange("stickiness") { 372 stickinessBlocks := d.Get("stickiness").([]interface{}) 373 if len(stickinessBlocks) == 1 { 374 stickiness := stickinessBlocks[0].(map[string]interface{}) 375 376 attrs = append(attrs, 377 &elbv2.TargetGroupAttribute{ 378 Key: aws.String("stickiness.enabled"), 379 Value: aws.String(strconv.FormatBool(stickiness["enabled"].(bool))), 380 }, 381 &elbv2.TargetGroupAttribute{ 382 Key: aws.String("stickiness.type"), 383 Value: aws.String(stickiness["type"].(string)), 384 }, 385 &elbv2.TargetGroupAttribute{ 386 Key: aws.String("stickiness.lb_cookie.duration_seconds"), 387 Value: aws.String(fmt.Sprintf("%d", stickiness["cookie_duration"].(int))), 388 }) 389 } else if len(stickinessBlocks) == 0 { 390 attrs = append(attrs, &elbv2.TargetGroupAttribute{ 391 Key: aws.String("stickiness.enabled"), 392 Value: aws.String("false"), 393 }) 394 } 395 } 396 397 if len(attrs) > 0 { 398 params := &elbv2.ModifyTargetGroupAttributesInput{ 399 TargetGroupArn: aws.String(d.Id()), 400 Attributes: attrs, 401 } 402 403 _, err := elbconn.ModifyTargetGroupAttributes(params) 404 if err != nil { 405 return errwrap.Wrapf("Error modifying Target Group Attributes: {{err}}", err) 406 } 407 } 408 409 return resourceAwsAlbTargetGroupRead(d, meta) 410 } 411 412 func resourceAwsAlbTargetGroupDelete(d *schema.ResourceData, meta interface{}) error { 413 elbconn := meta.(*AWSClient).elbv2conn 414 415 _, err := elbconn.DeleteTargetGroup(&elbv2.DeleteTargetGroupInput{ 416 TargetGroupArn: aws.String(d.Id()), 417 }) 418 if err != nil { 419 return errwrap.Wrapf("Error deleting Target Group: {{err}}", err) 420 } 421 422 return nil 423 } 424 425 func isTargetGroupNotFound(err error) bool { 426 elberr, ok := err.(awserr.Error) 427 return ok && elberr.Code() == "TargetGroupNotFound" 428 } 429 430 func validateAwsAlbTargetGroupHealthCheckPath(v interface{}, k string) (ws []string, errors []error) { 431 value := v.(string) 432 if len(value) > 1024 { 433 errors = append(errors, fmt.Errorf( 434 "%q cannot be longer than 1024 characters: %q", k, value)) 435 } 436 return 437 } 438 439 func validateAwsAlbTargetGroupHealthCheckPort(v interface{}, k string) (ws []string, errors []error) { 440 value := v.(string) 441 442 if value == "traffic-port" { 443 return 444 } 445 446 port, err := strconv.Atoi(value) 447 if err != nil { 448 errors = append(errors, fmt.Errorf("%q must be a valid port number (1-65536) or %q", k, "traffic-port")) 449 } 450 451 if port < 1 || port > 65536 { 452 errors = append(errors, fmt.Errorf("%q must be a valid port number (1-65536) or %q", k, "traffic-port")) 453 } 454 455 return 456 } 457 458 func validateAwsAlbTargetGroupHealthCheckHealthyThreshold(v interface{}, k string) (ws []string, errors []error) { 459 value := v.(int) 460 if value < 2 || value > 10 { 461 errors = append(errors, fmt.Errorf("%q must be an integer between 2 and 10", k)) 462 } 463 return 464 } 465 466 func validateAwsAlbTargetGroupHealthCheckTimeout(v interface{}, k string) (ws []string, errors []error) { 467 value := v.(int) 468 if value < 2 || value > 60 { 469 errors = append(errors, fmt.Errorf("%q must be an integer between 2 and 60", k)) 470 } 471 return 472 } 473 474 func validateAwsAlbTargetGroupHealthCheckProtocol(v interface{}, k string) (ws []string, errors []error) { 475 value := strings.ToLower(v.(string)) 476 if value == "http" || value == "https" { 477 return 478 } 479 480 errors = append(errors, fmt.Errorf("%q must be either %q or %q", k, "HTTP", "HTTPS")) 481 return 482 } 483 484 func validateAwsAlbTargetGroupPort(v interface{}, k string) (ws []string, errors []error) { 485 port := v.(int) 486 if port < 1 || port > 65536 { 487 errors = append(errors, fmt.Errorf("%q must be a valid port number (1-65536)", k)) 488 } 489 return 490 } 491 492 func validateAwsAlbTargetGroupProtocol(v interface{}, k string) (ws []string, errors []error) { 493 protocol := strings.ToLower(v.(string)) 494 if protocol == "http" || protocol == "https" { 495 return 496 } 497 498 errors = append(errors, fmt.Errorf("%q must be either %q or %q", k, "HTTP", "HTTPS")) 499 return 500 } 501 502 func validateAwsAlbTargetGroupDeregistrationDelay(v interface{}, k string) (ws []string, errors []error) { 503 delay := v.(int) 504 if delay < 0 || delay > 3600 { 505 errors = append(errors, fmt.Errorf("%q must be in the range 0-3600 seconds", k)) 506 } 507 return 508 } 509 510 func validateAwsAlbTargetGroupStickinessType(v interface{}, k string) (ws []string, errors []error) { 511 stickinessType := v.(string) 512 if stickinessType != "lb_cookie" { 513 errors = append(errors, fmt.Errorf("%q must have the value %q", k, "lb_cookie")) 514 } 515 return 516 } 517 518 func validateAwsAlbTargetGroupStickinessCookieDuration(v interface{}, k string) (ws []string, errors []error) { 519 duration := v.(int) 520 if duration < 1 || duration > 604800 { 521 errors = append(errors, fmt.Errorf("%q must be a between 1 second and 1 week (1-604800 seconds))", k)) 522 } 523 return 524 } 525 526 func albTargetGroupSuffixFromARN(arn *string) string { 527 if arn == nil { 528 return "" 529 } 530 531 if arnComponents := regexp.MustCompile(`arn:.*:targetgroup/(.*)`).FindAllStringSubmatch(*arn, -1); len(arnComponents) == 1 { 532 if len(arnComponents[0]) == 2 { 533 return fmt.Sprintf("targetgroup/%s", arnComponents[0][1]) 534 } 535 } 536 537 return "" 538 }