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  }