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