diff --git a/aws/resource_aws_lb_target_group.go b/aws/resource_aws_lb_target_group.go index 804ed0a8f53..811dc0906da 100644 --- a/aws/resource_aws_lb_target_group.go +++ b/aws/resource_aws_lb_target_group.go @@ -149,7 +149,8 @@ func resourceAwsLbTargetGroup() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringInSlice([]string{ - "lb_cookie", + "lb_cookie", // Only for ALBs + "source_ip", // Only for NLBs }, false), }, "cookie_duration": { @@ -157,6 +158,13 @@ func resourceAwsLbTargetGroup() *schema.Resource { Optional: true, Default: 86400, ValidateFunc: validation.IntBetween(0, 604800), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + switch d.Get("protocol").(string) { + case elbv2.ProtocolEnumTcp, elbv2.ProtocolEnumUdp, elbv2.ProtocolEnumTcpUdp, elbv2.ProtocolEnumTls: + return true + } + return false + }, }, }, }, @@ -434,11 +442,7 @@ func resourceAwsLbTargetGroupUpdate(d *schema.ResourceData, meta interface{}) er }) } - // In CustomizeDiff we allow LB stickiness to be declared for TCP target - // groups, so long as it's not enabled. This allows for better support for - // modules, but also means we need to completely skip sending the data to the - // API if it's defined on a TCP target group. - if d.HasChange("stickiness") && d.Get("protocol") != elbv2.ProtocolEnumTcp { + if d.HasChange("stickiness") { stickinessBlocks := d.Get("stickiness").([]interface{}) if len(stickinessBlocks) == 1 { stickiness := stickinessBlocks[0].(map[string]interface{}) @@ -451,11 +455,16 @@ func resourceAwsLbTargetGroupUpdate(d *schema.ResourceData, meta interface{}) er &elbv2.TargetGroupAttribute{ Key: aws.String("stickiness.type"), Value: aws.String(stickiness["type"].(string)), - }, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.lb_cookie.duration_seconds"), - Value: aws.String(fmt.Sprintf("%d", stickiness["cookie_duration"].(int))), }) + + switch d.Get("protocol").(string) { + case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: + attrs = append(attrs, + &elbv2.TargetGroupAttribute{ + Key: aws.String("stickiness.lb_cookie.duration_seconds"), + Value: aws.String(fmt.Sprintf("%d", stickiness["cookie_duration"].(int))), + }) + } } else if len(stickinessBlocks) == 0 { attrs = append(attrs, &elbv2.TargetGroupAttribute{ Key: aws.String("stickiness.enabled"), @@ -654,23 +663,8 @@ func flattenAwsLbTargetGroupResource(d *schema.ResourceData, meta interface{}, t } } - // We only read in the stickiness attributes if the target group is not - // TCP-based. This ensures we don't end up causing a spurious diff if someone - // has defined the stickiness block on a TCP target group (albeit with - // false), for which this update would clobber the state coming from config - // for. - // - // This is a workaround to support module design where the module needs to - // support HTTP and TCP target groups. - switch { - case aws.StringValue(targetGroup.Protocol) != elbv2.ProtocolEnumTcp: - if err = flattenAwsLbTargetGroupStickiness(d, attrResp.Attributes); err != nil { - return err - } - case aws.StringValue(targetGroup.Protocol) == elbv2.ProtocolEnumTcp && len(d.Get("stickiness").([]interface{})) < 1: - if err = d.Set("stickiness", []interface{}{}); err != nil { - return fmt.Errorf("error setting stickiness: %s", err) - } + if err = flattenAwsLbTargetGroupStickiness(d, attrResp.Attributes); err != nil { + return err } tags, err := keyvaluetags.Elbv2ListTags(elbconn, d.Id()) @@ -725,15 +719,6 @@ func flattenAwsLbTargetGroupStickiness(d *schema.ResourceData, attributes []*elb func resourceAwsLbTargetGroupCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { protocol := diff.Get("protocol").(string) - if protocol == elbv2.ProtocolEnumTcp { - // TCP load balancers do not support stickiness - if stickinessBlocks := diff.Get("stickiness").([]interface{}); len(stickinessBlocks) == 1 { - stickiness := stickinessBlocks[0].(map[string]interface{}) - if val := stickiness["enabled"].(bool); val { - return fmt.Errorf("Network Load Balancers do not support Stickiness") - } - } - } // Network Load Balancers have many special qwirks to them. // See http://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html diff --git a/aws/resource_aws_lb_target_group_test.go b/aws/resource_aws_lb_target_group_test.go index 37d133b401e..371ee24f80f 100644 --- a/aws/resource_aws_lb_target_group_test.go +++ b/aws/resource_aws_lb_target_group_test.go @@ -964,7 +964,7 @@ protocol = "TCP" }) } -func TestAccAWSLBTargetGroup_stickinessWithTCPDisabled(t *testing.T) { +func TestAccAWSLBTargetGroup_stickinessDefaultNLB(t *testing.T) { var conf elbv2.TargetGroup resourceName := "aws_lb_target_group.test" @@ -975,11 +975,135 @@ func TestAccAWSLBTargetGroup_stickinessWithTCPDisabled(t *testing.T) { CheckDestroy: testAccCheckAWSLBTargetGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSLBTargetGroupConfig_stickinessWithTCP(false), + Config: testAccAWSLBTargetGroupConfig_stickinessDefault("TCP"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckAWSLBTargetGroupExists(resourceName, &conf), resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"), + ), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessDefault("UDP"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBTargetGroupExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"), + ), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessDefault("TCP_UDP"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBTargetGroupExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"), + ), + }, + }, + }) +} + +func TestAccAWSLBTargetGroup_stickinessDefaultALB(t *testing.T) { + var conf elbv2.TargetGroup + resourceName := "aws_lb_target_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLBTargetGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLBTargetGroupConfig_stickinessDefault("HTTP"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBTargetGroupExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "lb_cookie"), + ), + }, + }, + }) +} + +func TestAccAWSLBTargetGroup_stickinessValidNLB(t *testing.T) { + var conf elbv2.TargetGroup + resourceName := "aws_lb_target_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLBTargetGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP", "source_ip", false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBTargetGroupExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"), + ), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP", "source_ip", true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBTargetGroupExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"), + ), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("UDP", "source_ip", true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBTargetGroupExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"), + ), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP_UDP", "source_ip", true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBTargetGroupExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"), + ), + }, + }, + }) +} + +func TestAccAWSLBTargetGroup_stickinessValidALB(t *testing.T) { + var conf elbv2.TargetGroup + resourceName := "aws_lb_target_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLBTargetGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("HTTP", "lb_cookie", true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBTargetGroupExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "lb_cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "86400"), + ), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("HTTPS", "lb_cookie", true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBTargetGroupExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"), resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "lb_cookie"), resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "86400"), ), @@ -988,16 +1112,55 @@ func TestAccAWSLBTargetGroup_stickinessWithTCPDisabled(t *testing.T) { }) } -func TestAccAWSLBTargetGroup_stickinessWithTCPEnabledShouldError(t *testing.T) { +func TestAccAWSLBTargetGroup_stickinessInvalidNLB(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLBTargetGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP", "lb_cookie", true), + ExpectError: regexp.MustCompile("Stickiness type 'lb_cookie' is not supported for target groups with"), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("UDP", "lb_cookie", true), + ExpectError: regexp.MustCompile("Stickiness type 'lb_cookie' is not supported for target groups with"), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP_UDP", "lb_cookie", true), + ExpectError: regexp.MustCompile("Stickiness type 'lb_cookie' is not supported for target groups with"), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP_UDP", "lb_cookie", false), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSLBTargetGroup_stickinessInvalidALB(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSLBTargetGroupDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSLBTargetGroupConfig_stickinessWithTCP(true), - PlanOnly: true, - ExpectError: regexp.MustCompile("Network Load Balancers do not support Stickiness"), + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("HTTP", "source_ip", true), + ExpectError: regexp.MustCompile("Stickiness type 'source_ip' is not supported for target groups with"), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("HTTPS", "source_ip", true), + ExpectError: regexp.MustCompile("Stickiness type 'source_ip' is not supported for target groups with"), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TLS", "lb_cookie", true), + ExpectError: regexp.MustCompile("Stickiness type 'lb_cookie' is not supported for target groups with"), + }, + { + Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP_UDP", "lb_cookie", false), + PlanOnly: true, + ExpectNonEmptyPlan: true, }, }, }) @@ -1923,16 +2086,35 @@ resource "aws_vpc" "test" { } ` -func testAccAWSLBTargetGroupConfig_stickinessWithTCP(enabled bool) string { +func testAccAWSLBTargetGroupConfig_stickinessDefault(protocol string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + name_prefix = "tf-" + port = 25 + protocol = %q + vpc_id = aws_vpc.test.id +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = "testAccAWSLBTargetGroupConfig_stickinessDefault" + } +} +`, protocol) +} + +func testAccAWSLBTargetGroupConfig_stickinessValidity(protocol, stickyType string, enabled bool) string { return fmt.Sprintf(` resource "aws_lb_target_group" "test" { name_prefix = "tf-" port = 25 - protocol = "TCP" + protocol = %q vpc_id = aws_vpc.test.id stickiness { - type = "lb_cookie" + type = %q enabled = %t } } @@ -1941,8 +2123,8 @@ resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "testAccAWSLBTargetGroupConfig_namePrefix" + Name = "testAccAWSLBTargetGroupConfig_stickinessValidity" } } -`, enabled) +`, protocol, stickyType, enabled) } diff --git a/website/docs/r/lb_target_group.html.markdown b/website/docs/r/lb_target_group.html.markdown index c1be583cdf4..ca86665dff2 100644 --- a/website/docs/r/lb_target_group.html.markdown +++ b/website/docs/r/lb_target_group.html.markdown @@ -69,8 +69,8 @@ The following arguments are supported: * `load_balancing_algorithm_type` - (Optional) Determines how the load balancer selects targets when routing requests. Only applicable for Application Load Balancer Target Groups. The value is `round_robin` or `least_outstanding_requests`. The default is `round_robin`. * `lambda_multi_value_headers_enabled` - (Optional) Boolean whether the request and response headers exchanged between the load balancer and the Lambda function include arrays of values or strings. Only applies when `target_type` is `lambda`. * `proxy_protocol_v2` - (Optional) Boolean to enable / disable support for proxy protocol v2 on Network Load Balancers. See [doc](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#proxy-protocol) for more information. -* `stickiness` - (Optional) A Stickiness block. Stickiness blocks are documented below. `stickiness` is only valid if used with Load Balancers of type `Application` -* `health_check` - (Optional) A Health Check block. Health Check blocks are documented below. +* `stickiness` - (Optional, Maximum of 1) A Stickiness block. Stickiness blocks are documented below. +* `health_check` - (Optional, Maximum of 1) A Health Check block. Health Check blocks are documented below. * `target_type` - (Optional, Forces new resource) The type of target that you must specify when registering targets with this target group. The possible values are `instance` (targets are specified by instance ID) or `ip` (targets are specified by IP address) or `lambda` (targets are specified by lambda arn). The default is `instance`. Note that you can't specify targets for a target group using both instance IDs and IP addresses. @@ -81,8 +81,8 @@ You can't specify publicly routable IP addresses. Stickiness Blocks (`stickiness`) support the following: -* `type` - (Required) The type of sticky sessions. The only current possible value is `lb_cookie`. -* `cookie_duration` - (Optional) The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the load balancer-generated cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). +* `type` - (Required) The type of sticky sessions. The only current possible values are `lb_cookie` for ALBs and `source_ip` for NLBs. +* `cookie_duration` - (Optional) Only used when the type is `lb_cookie`. The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the load balancer-generated cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). * `enabled` - (Optional) Boolean to enable / disable `stickiness`. Default is `true` ~> **NOTE:** To help facilitate the authoring of modules that support target groups of any protocol, you can define `stickiness` regardless of the protocol chosen. However, for `TCP` target groups, `enabled` must be `false`.