Skip to content

Commit

Permalink
Merge pull request #37082 from Hmerac/f-aws_target_group-target_group…
Browse files Browse the repository at this point in the history
…_health-support

Add support for target_group_health block
  • Loading branch information
ewbankkit authored Jul 12, 2024
2 parents 1b31b73 + 2981938 commit 1811879
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/37082.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_lb_target_group: Add `target_group_health` configuration block
```
141 changes: 141 additions & 0 deletions internal/service/elbv2/target_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,57 @@ func resourceTargetGroup() *schema.Resource {
},
},
},
"target_group_health": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"dns_failover": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"minimum_healthy_targets_count": {
Type: schema.TypeString,
Optional: true,
Default: "off",
ValidateFunc: validTargetGroupHealthInput,
},
"minimum_healthy_targets_percentage": {
Type: schema.TypeString,
Optional: true,
Default: "off",
ValidateFunc: validTargetGroupHealthPercentageInput,
},
},
},
},
"unhealthy_state_routing": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"minimum_healthy_targets_count": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
},
"minimum_healthy_targets_percentage": {
Type: schema.TypeString,
Optional: true,
Default: "off",
ValidateFunc: validTargetGroupHealthPercentageInput,
},
},
},
},
},
},
},
"target_health_state": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -484,6 +535,10 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta
attributes = append(attributes, expandTargetGroupTargetFailoverAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...)
}

if v, ok := d.GetOk("target_group_health"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
attributes = append(attributes, expandTargetGroupHealthAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...)
}

if v, ok := d.GetOk("target_health_state"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
attributes = append(attributes, expandTargetGroupTargetHealthStateAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...)
}
Expand Down Expand Up @@ -582,6 +637,10 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i
return sdkdiag.AppendErrorf(diags, "setting target_failover: %s", err)
}

if err := d.Set("target_group_health", []interface{}{flattenTargetGroupHealthAttributes(attributes, protocol)}); err != nil {
return sdkdiag.AppendErrorf(diags, "setting target_group_health: %s", err)
}

if err := d.Set("target_health_state", []interface{}{flattenTargetGroupTargetHealthStateAttributes(attributes, protocol)}); err != nil {
return sdkdiag.AppendErrorf(diags, "setting target_health_state: %s", err)
}
Expand Down Expand Up @@ -664,6 +723,12 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta
}
}

if d.HasChange("target_group_health") {
if v, ok := d.GetOk("target_group_health"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
attributes = append(attributes, expandTargetGroupHealthAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...)
}
}

if d.HasChange("target_health_state") {
if v, ok := d.GetOk("target_health_state"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
attributes = append(attributes, expandTargetGroupTargetHealthStateAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...)
Expand Down Expand Up @@ -1282,6 +1347,82 @@ func flattenTargetGroupTargetHealthStateAttributes(apiObjects []awstypes.TargetG
return tfMap
}

func expandTargetGroupHealthAttributes(tfMap map[string]interface{}, protocol awstypes.ProtocolEnum) []awstypes.TargetGroupAttribute {
if tfMap == nil {
return nil
}

var apiObjects []awstypes.TargetGroupAttribute

// Supported on Application Load Balancers and Network Load Balancers.
switch protocol {
case awstypes.ProtocolEnumGeneve:
default:
if v, ok := tfMap["dns_failover"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
tfMap := v[0].(map[string]interface{})
apiObjects = append(apiObjects,
awstypes.TargetGroupAttribute{
Key: aws.String(targetGroupAttributeTargetGroupHealthDNSFailoverMinimumHealthyTargetsCount),
Value: aws.String(tfMap["minimum_healthy_targets_count"].(string)),
},
awstypes.TargetGroupAttribute{
Key: aws.String(targetGroupAttributeTargetGroupHealthDNSFailoverMinimumHealthyTargetsPercentage),
Value: aws.String(tfMap["minimum_healthy_targets_percentage"].(string)),
},
)
}

if v, ok := tfMap["unhealthy_state_routing"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
tfMap := v[0].(map[string]interface{})
apiObjects = append(apiObjects,
awstypes.TargetGroupAttribute{
Key: aws.String(targetGroupAttributeTargetGroupHealthUnhealthyStateRoutingMinimumHealthyTargetsCount),
Value: flex.IntValueToString(tfMap["minimum_healthy_targets_count"].(int)),
},
awstypes.TargetGroupAttribute{
Key: aws.String(targetGroupAttributeTargetGroupHealthUnhealthyStateRoutingMinimumHealthyTargetsPercentage),
Value: aws.String(tfMap["minimum_healthy_targets_percentage"].(string)),
},
)
}
}

return apiObjects
}

func flattenTargetGroupHealthAttributes(apiObjects []awstypes.TargetGroupAttribute, protocol awstypes.ProtocolEnum) map[string]interface{} {
if len(apiObjects) == 0 {
return nil
}

tfMap := map[string]interface{}{}
dnsFailoverMap := make(map[string]interface{})
unhealthyStateRoutingMap := make(map[string]interface{})

// Supported on Application Load Balancers and Network Load Balancers.
switch protocol {
case awstypes.ProtocolEnumGeneve:
default:
for _, apiObject := range apiObjects {
switch k, v := aws.ToString(apiObject.Key), apiObject.Value; k {
case targetGroupAttributeTargetGroupHealthDNSFailoverMinimumHealthyTargetsCount:
dnsFailoverMap["minimum_healthy_targets_count"] = aws.ToString(v)
case targetGroupAttributeTargetGroupHealthDNSFailoverMinimumHealthyTargetsPercentage:
dnsFailoverMap["minimum_healthy_targets_percentage"] = aws.ToString(v)
case targetGroupAttributeTargetGroupHealthUnhealthyStateRoutingMinimumHealthyTargetsCount:
unhealthyStateRoutingMap["minimum_healthy_targets_count"] = flex.StringToIntValue(v)
case targetGroupAttributeTargetGroupHealthUnhealthyStateRoutingMinimumHealthyTargetsPercentage:
unhealthyStateRoutingMap["minimum_healthy_targets_percentage"] = aws.ToString(v)
}
}
}

tfMap["dns_failover"] = []interface{}{dnsFailoverMap}
tfMap["unhealthy_state_routing"] = []interface{}{unhealthyStateRoutingMap}

return tfMap
}

func targetGroupRuntimeValidation(d *schema.ResourceData, diags *diag.Diagnostics) {
if targetType := awstypes.TargetTypeEnum(d.Get("target_type").(string)); targetType == awstypes.TargetTypeEnumLambda {
targetType := string(targetType)
Expand Down
117 changes: 117 additions & 0 deletions internal/service/elbv2/target_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2555,6 +2555,92 @@ func TestAccELBV2TargetGroup_targetHealthStateUnhealthyConnectionTermination(t *
})
}

func TestAccELBV2TargetGroup_targetGroupHealthState(t *testing.T) {
ctx := acctest.Context(t)
var targetGroup awstypes.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, names.ELBV2ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckTargetGroupDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccTargetGroupConfig_targetGroupHealthState(rName, "off", "off", 1, "off"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup),
resource.TestCheckResourceAttr(resourceName, names.AttrName, rName),
resource.TestCheckResourceAttr(resourceName, "target_group_health.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_count", "off"),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_percentage", "off"),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_count", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_percentage", "off"),
),
},
{
Config: testAccTargetGroupConfig_targetGroupHealthState(rName, acctest.Ct1, "off", 1, "off"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup),
resource.TestCheckResourceAttr(resourceName, names.AttrName, rName),
resource.TestCheckResourceAttr(resourceName, "target_group_health.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_count", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_percentage", "off"),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_count", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_percentage", "off"),
),
},
{
Config: testAccTargetGroupConfig_targetGroupHealthState(rName, acctest.Ct1, "100", 1, "off"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup),
resource.TestCheckResourceAttr(resourceName, names.AttrName, rName),
resource.TestCheckResourceAttr(resourceName, "target_group_health.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_count", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_percentage", "100"),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_count", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_percentage", "off"),
),
},
{
Config: testAccTargetGroupConfig_targetGroupHealthState(rName, acctest.Ct1, "off", 1, "100"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup),
resource.TestCheckResourceAttr(resourceName, names.AttrName, rName),
resource.TestCheckResourceAttr(resourceName, "target_group_health.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_count", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_percentage", "off"),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_count", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_percentage", "100"),
),
},
{
Config: testAccTargetGroupConfig_targetGroupHealthState(rName, acctest.Ct1, "100", 1, "100"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup),
resource.TestCheckResourceAttr(resourceName, names.AttrName, rName),
resource.TestCheckResourceAttr(resourceName, "target_group_health.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_count", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.dns_failover.0.minimum_healthy_targets_percentage", "100"),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.#", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_count", acctest.Ct1),
resource.TestCheckResourceAttr(resourceName, "target_group_health.0.unhealthy_state_routing.0.minimum_healthy_targets_percentage", "100"),
),
},
},
})
}

func TestAccELBV2TargetGroup_Instance_HealthCheck_defaults(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -4892,6 +4978,37 @@ resource "aws_vpc" "test" {
`, rName, protocol, enabled)
}

func testAccTargetGroupConfig_targetGroupHealthState(rName, targetGroupHealthCount string, targetGroupHealthPercentageEnabled string, unhealthyStateRoutingCount int, unhealthyStateRoutingPercentageEnabled string) string {
return fmt.Sprintf(`
resource "aws_lb_target_group" "test" {
name = %[1]q
port = 80
protocol = "TCP"
vpc_id = aws_vpc.test.id
target_group_health {
dns_failover {
minimum_healthy_targets_count = %[2]q
minimum_healthy_targets_percentage = %[3]q
}
unhealthy_state_routing {
minimum_healthy_targets_count = %[4]d
minimum_healthy_targets_percentage = %[5]q
}
}
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
tags = {
Name = %[1]q
}
}
`, rName, targetGroupHealthCount, targetGroupHealthPercentageEnabled, unhealthyStateRoutingCount, unhealthyStateRoutingPercentageEnabled)
}

func testAccTargetGroupConfig_typeTCP(rName string) string {
return fmt.Sprintf(`
resource "aws_lb_target_group" "test" {
Expand Down
27 changes: 27 additions & 0 deletions internal/service/elbv2/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package elbv2

import (
"fmt"
"strconv"

"github.com/YakDriver/regexache"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/id"
Expand Down Expand Up @@ -99,3 +100,29 @@ func validTargetGroupNamePrefix(v interface{}, k string) (ws []string, errors []
}
return
}

func validTargetGroupHealthInput(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)

if value != "off" {
_, err := strconv.Atoi(value)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q must be an integer or 'off'", k))
}
}
return
}

func validTargetGroupHealthPercentageInput(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)

if value != "off" {
intValue, err := strconv.Atoi(value)
if err != nil || intValue < 1 || intValue > 100 {
errors = append(errors, fmt.Errorf(
"%q must be an integer between 0 and 100 or 'off'", k))
}
}
return
}
Loading

0 comments on commit 1811879

Please sign in to comment.