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