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