Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding support for app cookie stickiness #18102

Merged
merged 10 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/18102.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_lb_target_group: Add support for application cookie stickiness
```
73 changes: 65 additions & 8 deletions aws/resource_aws_lb_target_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,33 @@ func resourceAwsLbTargetGroup() *schema.Resource {
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"app_cookie": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
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
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"cookie_name": {
Type: schema.TypeString,
Required: true,
},
"duration_seconds": {
Type: schema.TypeInt,
Optional: true,
Default: 86400,
ValidateFunc: validation.IntBetween(0, 604800),
},
},
},
},
"cookie_duration": {
Type: schema.TypeInt,
Optional: true,
Expand All @@ -234,8 +261,9 @@ func resourceAwsLbTargetGroup() *schema.Resource {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"lb_cookie", // Only for ALBs
"source_ip", // Only for NLBs
"lb_cookie", // Only for ALBs
"app_cookie", // Only for ALBs
"source_ip", // Only for NLBs
}, false),
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
switch d.Get("protocol").(string) {
Expand Down Expand Up @@ -537,7 +565,7 @@ func resourceAwsLbTargetGroupUpdate(d *schema.ResourceData, meta interface{}) er
if len(stickinessBlocks) == 1 {
stickiness := stickinessBlocks[0].(map[string]interface{})

if !stickiness["enabled"].(bool) && stickiness["type"].(string) == "lb_cookie" && d.Get("protocol").(string) != elbv2.ProtocolEnumHttp && d.Get("protocol").(string) != elbv2.ProtocolEnumHttps {
if !stickiness["enabled"].(bool) && (stickiness["type"].(string) == "lb_cookie" || stickiness["type"].(string) == "app_cookie") && d.Get("protocol").(string) != elbv2.ProtocolEnumHttp && d.Get("protocol").(string) != elbv2.ProtocolEnumHttps {
log.Printf("[WARN] invalid configuration, this will fail in a future version: stickiness enabled %v, protocol %s, type %s", stickiness["enabled"].(bool), d.Get("protocol").(string), stickiness["type"].(string))
} else {
attrs = append(attrs,
Expand All @@ -552,11 +580,30 @@ func resourceAwsLbTargetGroupUpdate(d *schema.ResourceData, meta interface{}) er

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))),
})
switch stickiness["type"].(string) {
case "lb_cookie":
attrs = append(attrs,
&elbv2.TargetGroupAttribute{
Key: aws.String("stickiness.lb_cookie.duration_seconds"),
Value: aws.String(fmt.Sprintf("%d", stickiness["cookie_duration"].(int))),
})
case "app_cookie":
appCookieBlocks := stickiness["app_cookie"].([]interface{})
if len(appCookieBlocks) == 1 {
appStickiness := appCookieBlocks[0].(map[string]interface{})
attrs = append(attrs,
&elbv2.TargetGroupAttribute{
Key: aws.String("stickiness.app_cookie.duration_seconds"),
Value: aws.String(fmt.Sprintf("%d", appStickiness["duration_seconds"].(int))),
},
&elbv2.TargetGroupAttribute{
Key: aws.String("stickiness.app_cookie.cookie_name"),
Value: aws.String(appStickiness["cookie_name"].(string)),
})
}
default:
log.Printf("[WARN] Unexpected stickiness type. Expected lb_cookie or app_cookie, got %s", stickiness["type"].(string))
}
}
}
} else if len(stickinessBlocks) == 0 {
Expand Down Expand Up @@ -782,6 +829,7 @@ func flattenAwsLbTargetGroupResource(d *schema.ResourceData, meta interface{}, t
}

func flattenAwsLbTargetGroupStickiness(attributes []*elbv2.TargetGroupAttribute) ([]interface{}, error) {
appStickinessMap := map[string]interface{}{}
if len(attributes) == 0 {
return []interface{}{}, nil
}
Expand All @@ -804,8 +852,17 @@ func flattenAwsLbTargetGroupStickiness(attributes []*elbv2.TargetGroupAttribute)
return nil, fmt.Errorf("error converting stickiness.lb_cookie.duration_seconds to int: %s", aws.StringValue(attr.Value))
}
m["cookie_duration"] = duration
case "stickiness.app_cookie.cookie_name":
appStickinessMap["cookie_name"] = aws.StringValue(attr.Value)
case "stickiness.app_cookie.duration_seconds":
duration, err := strconv.Atoi(aws.StringValue(attr.Value))
if err != nil {
return nil, fmt.Errorf("Error converting stickiness.app_cookie.duration_seconds to int: %s", aws.StringValue(attr.Value))
}
appStickinessMap["duration_seconds"] = duration
}
}
m["app_cookie"] = []interface{}{appStickinessMap}

return []interface{}{m}, nil
}
Expand Down
139 changes: 139 additions & 0 deletions aws/resource_aws_lb_target_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,96 @@ func TestAccAWSLBTargetGroup_updateSticknessEnabled(t *testing.T) {
})
}

func TestAccAWSLBTargetGroup_updateAppSticknessEnabled(t *testing.T) {
var conf elbv2.TargetGroup
targetGroupName := fmt.Sprintf("test-target-group-%s", acctest.RandString(10))
resourceName := "aws_lb_target_group.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, elbv2.EndpointsID),
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSLBTargetGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLBTargetGroupConfig_appStickiness(targetGroupName, false, false),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttrSet(resourceName, "arn"),
resource.TestCheckResourceAttr(resourceName, "name", targetGroupName),
resource.TestCheckResourceAttr(resourceName, "port", "443"),
resource.TestCheckResourceAttr(resourceName, "protocol", "HTTPS"),
resource.TestCheckResourceAttrSet(resourceName, "vpc_id"),
resource.TestCheckResourceAttr(resourceName, "deregistration_delay", "200"),
resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.path", "/health2"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.interval", "30"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.port", "8082"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", "HTTPS"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.timeout", "4"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.healthy_threshold", "4"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.unhealthy_threshold", "4"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", "200"),
),
},
{
Config: testAccAWSLBTargetGroupConfig_appStickiness(targetGroupName, true, true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttrSet(resourceName, "arn"),
resource.TestCheckResourceAttr(resourceName, "name", targetGroupName),
resource.TestCheckResourceAttr(resourceName, "port", "443"),
resource.TestCheckResourceAttr(resourceName, "protocol", "HTTPS"),
resource.TestCheckResourceAttrSet(resourceName, "vpc_id"),
resource.TestCheckResourceAttr(resourceName, "deregistration_delay", "200"),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "app_cookie"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.app_cookie.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.app_cookie.0.cookie_name", "Cookie"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.app_cookie.0.duration_seconds", "10000"),
resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.path", "/health2"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.interval", "30"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.port", "8082"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", "HTTPS"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.timeout", "4"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.healthy_threshold", "4"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.unhealthy_threshold", "4"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", "200"),
),
},
{
Config: testAccAWSLBTargetGroupConfig_appStickiness(targetGroupName, true, false),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttrSet(resourceName, "arn"),
resource.TestCheckResourceAttr(resourceName, "name", targetGroupName),
resource.TestCheckResourceAttr(resourceName, "port", "443"),
resource.TestCheckResourceAttr(resourceName, "protocol", "HTTPS"),
resource.TestCheckResourceAttrSet(resourceName, "vpc_id"),
resource.TestCheckResourceAttr(resourceName, "deregistration_delay", "200"),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "app_cookie"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.app_cookie.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.app_cookie.0.cookie_name", "Cookie"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.app_cookie.0.duration_seconds", "10000"),
resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.path", "/health2"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.interval", "30"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.port", "8082"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", "HTTPS"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.timeout", "4"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.healthy_threshold", "4"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.unhealthy_threshold", "4"),
resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", "200"),
),
},
},
})
}

func TestAccAWSLBTargetGroup_defaults_application(t *testing.T) {
var conf elbv2.TargetGroup
rName := acctest.RandomWithPrefix("tf-acc-test")
Expand Down Expand Up @@ -2278,6 +2368,55 @@ resource "aws_vpc" "test" {
`, rName, stickinessBlock)
}

func testAccAWSLBTargetGroupConfig_appStickiness(targetGroupName string, addAppStickinessBlock bool, enabled bool) string {
var appSstickinessBlock string

if addAppStickinessBlock {
appSstickinessBlock = fmt.Sprintf(`
stickiness {
enabled = "%[1]t"
type = "app_cookie"
app_cookie {
cookie_name = "Cookie"
duration_seconds = 10000
}
}
`, enabled)
}

return fmt.Sprintf(`
resource "aws_lb_target_group" "test" {
name = %[1]q
port = 443
protocol = "HTTPS"
vpc_id = aws_vpc.test.id

deregistration_delay = 200

%[2]s

health_check {
path = "/health2"
interval = 30
port = 8082
protocol = "HTTPS"
timeout = 4
healthy_threshold = 4
unhealthy_threshold = 4
matcher = "200"
}
}

resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"

tags = {
Name = "terraform-testacc-lb-target-group-stickiness"
}
}
`, targetGroupName, appSstickinessBlock)
}

func testAccAWSLBTargetGroupConfig_namePrefix(rName string) string {
return fmt.Sprintf(`
resource "aws_lb_target_group" "test" {
Expand Down
10 changes: 8 additions & 2 deletions website/docs/r/lb_target_group.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,14 @@ The following arguments are supported:
~> **NOTE:** Currently, an NLB (i.e., protocol of `HTTP` or `HTTPS`) can have an invalid `stickiness` block with `type` set to `lb_cookie` as long as `enabled` is set to `false`. However, please update your configurations to avoid errors in a future version of the provider: either remove the invalid `stickiness` block or set the `type` to `source_ip`.

* `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) Whether to enable `stickiness`. Default is `true`.
* `type` - (Required) Type of sticky sessions. The only current possible values are `lb_cookie` for ALBs and `source_ip` for NLBs.
* `enabled` - (Optional) Boolean to enable / disable `stickiness`. Default is `true`.
* `type` - (Required) The type of sticky sessions. The only current possible values are `lb_cookie`, `app_cookie` for ALBs, and `source_ip` for NLBs.
* `app_cookie` - (Option) An Application Cookie block. Application Cookie blocks are documented below.

#### app_cookie

* `cookie_name` - (Required) Name of the application based cookie. AWSALB, AWSALBAPP, and AWSALBTG prefixes are reserved and cannot be used.
* `duration_seconds` - (Optional) The time period, in seconds, during which requests from a client should be routed to the same target. The range is 1 to 604800 seconds. The default value is 86400.

## Attributes Reference

Expand Down