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  }