diff --git a/.changelog/34070.txt b/.changelog/34070.txt new file mode 100644 index 000000000000..d95439d57780 --- /dev/null +++ b/.changelog/34070.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_lb_target_group: Add `target_health_state` configuration block +``` + +```release-note:enhancement +resource/aws_lb_target_group: Remove default value (`false`) for `connection_termination` argument and mark as Computed, to support new default behavior for UDP/TCP_UDP target groups +``` diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index bacb8ca2bdb5..3c510e710bbd 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -67,7 +67,7 @@ func ResourceTargetGroup() *schema.Resource { "connection_termination": { Type: schema.TypeBool, Optional: true, - Default: false, + Computed: true, }, "deregistration_delay": { Type: nullable.TypeNullableInt, @@ -316,6 +316,19 @@ func ResourceTargetGroup() *schema.Resource { }, }, }, + "target_health_state": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable_unhealthy_connection_termination": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + }, "target_type": { Type: schema.TypeString, Optional: true, @@ -537,6 +550,22 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta } } + // Only supported for TCP & TLS protocols + if v, ok := d.Get("protocol").(string); ok { + if v == elbv2.ProtocolEnumTcp || v == elbv2.ProtocolEnumTls { + if v, ok := d.GetOk("target_health_state"); ok && len(v.([]interface{})) > 0 { + targetHealthStateBlock := v.([]interface{}) + targetHealthState := targetHealthStateBlock[0].(map[string]interface{}) + attrs = append(attrs, + &elbv2.TargetGroupAttribute{ + Key: aws.String("target_health_state.unhealthy.connection_termination.enabled"), + Value: aws.String(strconv.FormatBool(targetHealthState["enable_unhealthy_connection_termination"].(bool))), + }, + ) + } + } + } + if v, ok := d.GetOk("stickiness"); ok && len(v.([]interface{})) > 0 { stickinessBlocks := v.([]interface{}) stickiness := stickinessBlocks[0].(map[string]interface{}) @@ -789,6 +818,18 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta }) } + if d.HasChange("target_health_state") { + targetHealthStateBlock := d.Get("target_health_state").([]interface{}) + if len(targetHealthStateBlock) == 1 { + targetHealthState := targetHealthStateBlock[0].(map[string]interface{}) + attrs = append(attrs, + &elbv2.TargetGroupAttribute{ + Key: aws.String("target_health_state.unhealthy.connection_termination.enabled"), + Value: aws.String(strconv.FormatBool(targetHealthState["enable_unhealthy_connection_termination"].(bool))), + }) + } + } + if d.HasChange("target_failover") { failoverBlock := d.Get("target_failover").([]interface{}) if len(failoverBlock) == 1 { @@ -1102,6 +1143,14 @@ func flattenTargetGroupResource(ctx context.Context, d *schema.ResourceData, met return fmt.Errorf("setting stickiness: %w", err) } + targetHealthStateAttr, err := flattenTargetHealthState(attrResp.Attributes) + if err != nil { + return fmt.Errorf("flattening target health state: %w", err) + } + if err := d.Set("target_health_state", targetHealthStateAttr); err != nil { + return fmt.Errorf("setting target health state: %w", err) + } + // Set target failover attributes for GWLB targetFailoverAttr := flattenTargetGroupFailover(attrResp.Attributes) if err != nil { @@ -1115,6 +1164,27 @@ func flattenTargetGroupResource(ctx context.Context, d *schema.ResourceData, met return nil } +func flattenTargetHealthState(attributes []*elbv2.TargetGroupAttribute) ([]interface{}, error) { + if len(attributes) == 0 { + return []interface{}{}, nil + } + + m := make(map[string]interface{}) + + for _, attr := range attributes { + switch aws.StringValue(attr.Key) { + case "target_health_state.unhealthy.connection_termination.enabled": + enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) + if err != nil { + return nil, fmt.Errorf("converting target_health_state.unhealthy.connection_termination to bool: %s", aws.StringValue(attr.Value)) + } + m["enable_unhealthy_connection_termination"] = enabled + } + } + + return []interface{}{m}, nil +} + func flattenTargetGroupFailover(attributes []*elbv2.TargetGroupAttribute) []interface{} { if len(attributes) == 0 { return []interface{}{} diff --git a/internal/service/elbv2/target_group_test.go b/internal/service/elbv2/target_group_test.go index 4255019b8ade..f35aeb0ecd48 100644 --- a/internal/service/elbv2/target_group_test.go +++ b/internal/service/elbv2/target_group_test.go @@ -2342,6 +2342,62 @@ func TestAccELBV2TargetGroup_ALBAlias_updateStickinessEnabled(t *testing.T) { }) } +func TestAccELBV2TargetGroup_targetHealthStateUnhealthyConnectionTermination(t *testing.T) { + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_target_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TCP", false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "protocol", "TCP"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "false"), + ), + }, + { + Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TCP", true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "protocol", "TCP"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "true"), + ), + }, + { + Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TLS", false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "protocol", "TLS"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "false"), + ), + }, + { + Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TLS", true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "protocol", "TLS"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "true"), + ), + }, + }, + }) +} + func testAccCheckTargetGroupDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) @@ -3118,6 +3174,29 @@ resource "aws_vpc" "test" { `, protocol, stickyType, enabled, rName) } +func testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, protocol string, enabled bool) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + name = %[1]q + port = 25 + protocol = %[2]q + vpc_id = aws_vpc.test.id + + target_health_state { + enable_unhealthy_connection_termination = %[3]t + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} +`, rName, protocol, enabled) +} + func testAccTargetGroupConfig_typeTCP(rName string) string { return fmt.Sprintf(` resource "aws_lb_target_group" "test" { diff --git a/website/docs/r/lb_target_group.html.markdown b/website/docs/r/lb_target_group.html.markdown index f520553928b2..556d6518bd3c 100644 --- a/website/docs/r/lb_target_group.html.markdown +++ b/website/docs/r/lb_target_group.html.markdown @@ -66,6 +66,21 @@ resource "aws_lb_target_group" "alb-example" { } ``` +### Target group with unhealthy connection termination disabled + +```terraform +resource "aws_lb_target_group" "tcp-example" { + name = "tf-example-lb-nlb-tg" + port = 25 + protocol = "TCP" + vpc_id = aws_vpc.main.id + + target_health_state { + enable_unhealthy_connection_termination = false + } +} +``` + ## Argument Reference This resource supports the following arguments: @@ -87,6 +102,7 @@ This resource supports the following arguments: * `stickiness` - (Optional, Maximum of 1) Stickiness configuration block. Detailed below. * `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `target_failover` - (Optional) Target failover block. Only applicable for Gateway Load Balancer target groups. See [target_failover](#target_failover) for more information. +* `target_health_state` - (Optional) Target health state block. Only applicable for Network Load Balancer target groups when `protocol` is `TCP` or `TLS`. See [target_health_state](#target_health_state) for more information. * `target_type` - (May be required, Forces new resource) Type of target that you must specify when registering targets with this target group. See [doc](https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html) for supported values. The default is `instance`. Note that you can't specify targets for a target group using both instance IDs and IP addresses. @@ -129,6 +145,12 @@ This resource supports the following arguments: * `on_deregistration` - (Optional) Indicates how the GWLB handles existing flows when a target is deregistered. Possible values are `rebalance` and `no_rebalance`. Must match the attribute value set for `on_unhealthy`. Default: `no_rebalance`. * `on_unhealthy` - Indicates how the GWLB handles existing flows when a target is unhealthy. Possible values are `rebalance` and `no_rebalance`. Must match the attribute value set for `on_deregistration`. Default: `no_rebalance`. +### target_health_state + +~> **NOTE:** This block is only valid for a Network Load Balancer (NLB) target group when `protocol` is `TCP` or `TLS`. + +* `enable_unhealthy_connection_termination` - (Optional) Indicates whether the load balancer terminates connections to unhealthy targets. Possible values are `true` or `false`. Default: `true`. + ## Attribute Reference This resource exports the following attributes in addition to the arguments above: