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  }