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