From 858e8783300a29408c361305856b1c33e041613f Mon Sep 17 00:00:00 2001 From: Tim Rogers Date: Tue, 16 May 2023 17:17:51 -0500 Subject: [PATCH 01/31] Modified target_group.go to persist stickiness.app_cookie.cookie_name through updates between lb_cookie and app_cookie --- internal/service/elbv2/target_group.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 68fdbaecb978..4c33692be4a5 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -752,6 +752,10 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta &elbv2.TargetGroupAttribute{ Key: aws.String("stickiness.lb_cookie.duration_seconds"), Value: aws.String(fmt.Sprintf("%d", stickiness["cookie_duration"].(int))), + }, + &elbv2.TargetGroupAttribute{ + Key: aws.String("stickiness.app_cookie.cookie_name"), + Value: aws.String(stickiness["cookie_name"].(string)), }) case "app_cookie": attrs = append(attrs, From 27d24c1fd0a2dfdac9366629eec73ac43024ccb5 Mon Sep 17 00:00:00 2001 From: Tim Rogers Date: Tue, 16 May 2023 17:30:12 -0500 Subject: [PATCH 02/31] Added acceptance test for changing ALB stickiness type --- internal/service/elbv2/target_group_test.go | 93 +++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/internal/service/elbv2/target_group_test.go b/internal/service/elbv2/target_group_test.go index 97f5a07c2a64..05f5e10435cd 100644 --- a/internal/service/elbv2/target_group_test.go +++ b/internal/service/elbv2/target_group_test.go @@ -1450,6 +1450,99 @@ func TestAccELBV2TargetGroup_Stickiness_updateAppEnabled(t *testing.T) { }) } +func TestAccELBV2TargetGroup_Stickiness_updateStickinessType(t *testing.T) { + ctx := acctest.Context(t) + var conf 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_stickiness(rName, true, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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", "lb_cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "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: testAccTargetGroupConfig_appStickiness(rName, true, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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.cookie_name", "Cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "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: testAccTargetGroupConfig_stickiness(rName, true, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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", "lb_cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_name", "Cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "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 TestAccELBV2TargetGroup_HealthCheck_update(t *testing.T) { ctx := acctest.Context(t) var conf elbv2.TargetGroup From 2655cd5f917dd108f47df755ecffc2aa200bf903 Mon Sep 17 00:00:00 2001 From: Tim Rogers Date: Tue, 16 May 2023 17:39:44 -0500 Subject: [PATCH 03/31] Added changelog file 31436.txt --- .changelog/31436.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/31436.txt diff --git a/.changelog/31436.txt b/.changelog/31436.txt new file mode 100644 index 000000000000..c36d19086b48 --- /dev/null +++ b/.changelog/31436.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_lb_target_group: Persist `stickiness.app_cookie.cookie_name` across changes between app_cookie and lb_cookie ALB stickiness +``` \ No newline at end of file From 74cb6b501ba203ff0bc252beb59ac2e5f482e557 Mon Sep 17 00:00:00 2001 From: Tim Rogers Date: Tue, 16 May 2023 17:30:12 -0500 Subject: [PATCH 04/31] Added acceptance test for changing ALB stickiness type --- internal/service/elbv2/target_group_test.go | 93 +++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/internal/service/elbv2/target_group_test.go b/internal/service/elbv2/target_group_test.go index e1e6fba7ec3a..5a5f81c053a2 100644 --- a/internal/service/elbv2/target_group_test.go +++ b/internal/service/elbv2/target_group_test.go @@ -1520,6 +1520,99 @@ func TestAccELBV2TargetGroup_Stickiness_updateAppEnabled(t *testing.T) { }) } +func TestAccELBV2TargetGroup_Stickiness_updateStickinessType(t *testing.T) { + ctx := acctest.Context(t) + var conf 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_stickiness(rName, true, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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", "lb_cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "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: testAccTargetGroupConfig_appStickiness(rName, true, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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.cookie_name", "Cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "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: testAccTargetGroupConfig_stickiness(rName, true, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + 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", "lb_cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_name", "Cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "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 TestAccELBV2TargetGroup_HealthCheck_update(t *testing.T) { ctx := acctest.Context(t) var conf elbv2.TargetGroup From 668fcd10ffbec5f91144c406f02b4fba4b25ea6c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 14 Dec 2023 14:28:50 -0500 Subject: [PATCH 05/31] elbv2: Move constants. --- internal/service/elbv2/const.go | 26 ++++++++++++++++++++++++++ internal/service/elbv2/target_group.go | 20 -------------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/internal/service/elbv2/const.go b/internal/service/elbv2/const.go index ebd7a7df1d87..6ac49a89db85 100644 --- a/internal/service/elbv2/const.go +++ b/internal/service/elbv2/const.go @@ -3,6 +3,16 @@ package elbv2 +import ( + "time" + + "github.com/aws/aws-sdk-go/service/elbv2" +) + +const ( + propagationTimeout = 2 * time.Minute +) + const ( errCodeValidationError = "ValidationError" @@ -80,3 +90,19 @@ func httpXFFHeaderProcessingMode_Values() []string { httpXFFHeaderProcessingModeRemove, } } + +func healthCheckProtocolEnumValues() []string { + return []string{ + elbv2.ProtocolEnumHttp, + elbv2.ProtocolEnumHttps, + elbv2.ProtocolEnumTcp, + } +} + +func protocolVersionEnumValues() []string { + return []string{ + "GRPC", + "HTTP1", + "HTTP2", + } +} diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 5985a022985c..ef5132402b0c 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -32,26 +32,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -const ( - propagationTimeout = 2 * time.Minute -) - -func healthCheckProtocolEnumValues() []string { - return []string{ - elbv2.ProtocolEnumHttp, - elbv2.ProtocolEnumHttps, - elbv2.ProtocolEnumTcp, - } -} - -func protocolVersionEnumValues() []string { - return []string{ - "GRPC", - "HTTP1", - "HTTP2", - } -} - // @SDKResource("aws_alb_target_group", name="Target Group") // @SDKResource("aws_lb_target_group", name="Target Group") // @Tags(identifierAttribute="id") From 81c0e43bf4575371000fb65a5e8df4bee5ed4a64 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 14 Dec 2023 15:17:43 -0500 Subject: [PATCH 06/31] r/aws_lb_target_group: Tidy up Delete. --- internal/service/elbv2/const.go | 4 +++ internal/service/elbv2/target_group.go | 37 +++++++------------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/internal/service/elbv2/const.go b/internal/service/elbv2/const.go index 6ac49a89db85..111a320a0eb2 100644 --- a/internal/service/elbv2/const.go +++ b/internal/service/elbv2/const.go @@ -91,6 +91,10 @@ func httpXFFHeaderProcessingMode_Values() []string { } } +const ( + healthCheckPortTrafficPort = "traffic-port" +) + func healthCheckProtocolEnumValues() []string { return []string{ elbv2.ProtocolEnumHttp, diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index ef5132402b0c..1f0777d28a21 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -114,7 +114,7 @@ func ResourceTargetGroup() *schema.Resource { "port": { Type: schema.TypeString, Optional: true, - Default: "traffic-port", + Default: healthCheckPortTrafficPort, ValidateFunc: validTargetGroupHealthCheckPort, DiffSuppressFunc: suppressIfTargetType(elbv2.TargetTypeEnumLambda), }, @@ -877,36 +877,17 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta func resourceTargetGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - const ( - targetGroupDeleteTimeout = 2 * time.Minute - ) conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - input := &elbv2.DeleteTargetGroupInput{ - TargetGroupArn: aws.String(d.Id()), - } - - log.Printf("[DEBUG] Deleting Target Group (%s): %s", d.Id(), input) - err := retry.RetryContext(ctx, targetGroupDeleteTimeout, func() *retry.RetryError { - _, err := conn.DeleteTargetGroupWithContext(ctx, input) - - if tfawserr.ErrMessageContains(err, "ResourceInUse", "is currently in use by a listener or a rule") { - return retry.RetryableError(err) - } - - if err != nil { - return retry.NonRetryableError(err) - } - - return nil - }) - - if tfresource.TimedOut(err) { - _, err = conn.DeleteTargetGroupWithContext(ctx, input) - } + log.Printf("[DEBUG] Deleting ELBv2 Target Group: %s", d.Id()) + _, err := tfresource.RetryWhenAWSErrMessageContains(ctx, 2*time.Minute, func() (interface{}, error) { + return conn.DeleteTargetGroupWithContext(ctx, &elbv2.DeleteTargetGroupInput{ + TargetGroupArn: aws.String(d.Id()), + }) + }, elbv2.ErrCodeResourceInUseException, "is currently in use by a listener or a rule") if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Target Group: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting ELBv2 Target Group (%s): %s", d.Id(), err) } return diags @@ -1028,7 +1009,7 @@ func validateSlowStart(v interface{}, k string) (ws []string, errors []error) { func validTargetGroupHealthCheckPort(v interface{}, k string) (ws []string, errors []error) { value := v.(string) - if value == "traffic-port" { + if value == healthCheckPortTrafficPort { return } From ddce4ea724808ac1d12ab021d10d31083e127892 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 14 Dec 2023 15:22:41 -0500 Subject: [PATCH 07/31] Tidy up 'findTargetGroup'. --- internal/service/elbv2/target_group.go | 38 ++++++++----------- .../service/elbv2/target_group_data_source.go | 2 +- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 1f0777d28a21..48c0151be64f 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -365,7 +365,7 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta create.WithConfiguredPrefix(d.Get("name_prefix").(string)), create.WithDefaultPrefix("tf-"), ).Generate() - exist, err := FindTargetGroupByName(ctx, conn, name) + exist, err := findTargetGroupByName(ctx, conn, name) if err != nil && !tfresource.NotFound(err) { return sdkdiag.AppendErrorf(diags, "reading ELBv2 Target Group (%s): %s", name, err) @@ -898,7 +898,7 @@ func FindTargetGroupByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) (* TargetGroupArns: aws.StringSlice([]string{arn}), } - output, err := FindTargetGroup(ctx, conn, input) + output, err := findTargetGroup(ctx, conn, input) if err != nil { return nil, err @@ -914,12 +914,12 @@ func FindTargetGroupByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) (* return output, nil } -func FindTargetGroupByName(ctx context.Context, conn *elbv2.ELBV2, name string) (*elbv2.TargetGroup, error) { +func findTargetGroupByName(ctx context.Context, conn *elbv2.ELBV2, name string) (*elbv2.TargetGroup, error) { input := &elbv2.DescribeTargetGroupsInput{ Names: aws.StringSlice([]string{name}), } - output, err := FindTargetGroup(ctx, conn, input) + output, err := findTargetGroup(ctx, conn, input) if err != nil { return nil, err @@ -935,7 +935,17 @@ func FindTargetGroupByName(ctx context.Context, conn *elbv2.ELBV2, name string) return output, nil } -func FindTargetGroups(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTargetGroupsInput) ([]*elbv2.TargetGroup, error) { +func findTargetGroup(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTargetGroupsInput) (*elbv2.TargetGroup, error) { + output, err := findTargetGroups(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSinglePtrResult(output) +} + +func findTargetGroups(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTargetGroupsInput) ([]*elbv2.TargetGroup, error) { var output []*elbv2.TargetGroup err := conn.DescribeTargetGroupsPagesWithContext(ctx, input, func(page *elbv2.DescribeTargetGroupsOutput, lastPage bool) bool { @@ -966,24 +976,6 @@ func FindTargetGroups(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.Descr return output, nil } -func FindTargetGroup(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTargetGroupsInput) (*elbv2.TargetGroup, error) { - output, err := FindTargetGroups(ctx, conn, input) - - if err != nil { - return nil, err - } - - if len(output) == 0 || output[0] == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - if count := len(output); count > 1 { - return nil, tfresource.NewTooManyResultsError(count, input) - } - - return output[0], nil -} - func validTargetGroupHealthCheckPath(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if !strings.HasPrefix(value, "/") { diff --git a/internal/service/elbv2/target_group_data_source.go b/internal/service/elbv2/target_group_data_source.go index e8ecfe7e5d5c..1dd94c9293cd 100644 --- a/internal/service/elbv2/target_group_data_source.go +++ b/internal/service/elbv2/target_group_data_source.go @@ -184,7 +184,7 @@ func dataSourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta input.Names = aws.StringSlice([]string{v.(string)}) } - results, err := FindTargetGroups(ctx, conn, input) + results, err := findTargetGroups(ctx, conn, input) if err != nil { return sdkdiag.AppendErrorf(diags, "reading ELBv2 Target Groups: %s", err) From 91060cc76aa5119a4ba2a9f1924845e49a5a0ad3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 14 Dec 2023 16:39:19 -0500 Subject: [PATCH 08/31] r/aws_lb_target_group: Tidy up Create. --- internal/service/elbv2/const.go | 12 +++-- internal/service/elbv2/target_group.go | 61 ++++++++++++-------------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/internal/service/elbv2/const.go b/internal/service/elbv2/const.go index 111a320a0eb2..c771aeccee05 100644 --- a/internal/service/elbv2/const.go +++ b/internal/service/elbv2/const.go @@ -103,10 +103,16 @@ func healthCheckProtocolEnumValues() []string { } } +const ( + protocolVersionGRPC = "GRPC" + protocolVersionHTTP1 = "HTTP1" + protocolVersionHTTP2 = "HTTP2" +) + func protocolVersionEnumValues() []string { return []string{ - "GRPC", - "HTTP1", - "HTTP2", + protocolVersionGRPC, + protocolVersionHTTP1, + protocolVersionHTTP2, } } diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 48c0151be64f..cb5ff6f3c771 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -383,60 +383,57 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta TargetType: aws.String(d.Get("target_type").(string)), } - if d.Get("target_type").(string) != elbv2.TargetTypeEnumLambda { + if targetType := d.Get("target_type").(string); targetType != elbv2.TargetTypeEnumLambda { input.Port = aws.Int64(int64(d.Get("port").(int))) - input.Protocol = aws.String(d.Get("protocol").(string)) - switch d.Get("protocol").(string) { + protocol := d.Get("protocol").(string) + input.Protocol = aws.String(protocol) + switch protocol { case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: input.ProtocolVersion = aws.String(d.Get("protocol_version").(string)) } input.VpcId = aws.String(d.Get("vpc_id").(string)) - if d.Get("target_type").(string) == elbv2.TargetTypeEnumIp { - if _, ok := d.GetOk("ip_address_type"); ok { - input.IpAddressType = aws.String(d.Get("ip_address_type").(string)) + if targetType == elbv2.TargetTypeEnumIp { + if v, ok := d.GetOk("ip_address_type"); ok { + input.IpAddressType = aws.String(v.(string)) } } } - if healthChecks := d.Get("health_check").([]interface{}); len(healthChecks) == 1 { - healthCheck := healthChecks[0].(map[string]interface{}) - - input.HealthCheckEnabled = aws.Bool(healthCheck["enabled"].(bool)) + if v, ok := d.GetOk("health_check"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) - input.HealthCheckIntervalSeconds = aws.Int64(int64(healthCheck["interval"].(int))) + input.HealthCheckEnabled = aws.Bool(tfMap["enabled"].(bool)) + input.HealthCheckIntervalSeconds = aws.Int64(int64(tfMap["interval"].(int))) + input.HealthyThresholdCount = aws.Int64(int64(tfMap["healthy_threshold"].(int))) + input.UnhealthyThresholdCount = aws.Int64(int64(tfMap["unhealthy_threshold"].(int))) - input.HealthyThresholdCount = aws.Int64(int64(healthCheck["healthy_threshold"].(int))) - input.UnhealthyThresholdCount = aws.Int64(int64(healthCheck["unhealthy_threshold"].(int))) - t := healthCheck["timeout"].(int) - if t != 0 { - input.HealthCheckTimeoutSeconds = aws.Int64(int64(t)) + if v, ok := tfMap["timeout"].(int); ok && v != 0 { + input.HealthCheckTimeoutSeconds = aws.Int64(int64(v)) } - healthCheckProtocol := healthCheck["protocol"].(string) - if healthCheckProtocol != elbv2.ProtocolEnumTcp { - p := healthCheck["path"].(string) - if p != "" { - input.HealthCheckPath = aws.String(p) + protocol := tfMap["protocol"].(string) + if protocol != elbv2.ProtocolEnumTcp { + if v, ok := tfMap["path"].(string); ok && v != "" { + input.HealthCheckPath = aws.String(v) } - m := healthCheck["matcher"].(string) - protocolVersion := d.Get("protocol_version").(string) - if m != "" { - if protocolVersion == "GRPC" { + if v, ok := tfMap["matcher"].(string); ok && v != "" { + if protocolVersion := d.Get("protocol_version").(string); protocolVersion == protocolVersionGRPC { input.Matcher = &elbv2.Matcher{ - GrpcCode: aws.String(m), + GrpcCode: aws.String(v), } } else { input.Matcher = &elbv2.Matcher{ - HttpCode: aws.String(m), + HttpCode: aws.String(v), } } } } - if d.Get("target_type").(string) != elbv2.TargetTypeEnumLambda { - input.HealthCheckPort = aws.String(healthCheck["port"].(string)) - input.HealthCheckProtocol = aws.String(healthCheckProtocol) + + if targetType := d.Get("target_type").(string); targetType != elbv2.TargetTypeEnumLambda { + input.HealthCheckPort = aws.String(tfMap["port"].(string)) + input.HealthCheckProtocol = aws.String(protocol) } } @@ -461,10 +458,6 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "creating ELBv2 Target Group (%s): %s", name, err) } - if len(output.TargetGroups) == 0 { - return sdkdiag.AppendErrorf(diags, "creating LB Target Group: no groups returned in response") - } - d.SetId(aws.StringValue(output.TargetGroups[0].TargetGroupArn)) _, err = tfresource.RetryWhenNotFound(ctx, propagationTimeout, func() (interface{}, error) { From 43d401bce33f9cfd0b401659e9af3383a75647c9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 14 Dec 2023 17:19:26 -0500 Subject: [PATCH 09/31] r/aws_lb_target_group: Add attribute constants. --- internal/service/elbv2/const.go | 95 ++++++++++++++++++++++++++ internal/service/elbv2/target_group.go | 53 +++++--------- 2 files changed, 112 insertions(+), 36 deletions(-) diff --git a/internal/service/elbv2/const.go b/internal/service/elbv2/const.go index c771aeccee05..87ca3b2676e5 100644 --- a/internal/service/elbv2/const.go +++ b/internal/service/elbv2/const.go @@ -91,6 +91,101 @@ func httpXFFHeaderProcessingMode_Values() []string { } } +// See https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_TargetGroupAttribute.html#API_TargetGroupAttribute_Contents. +const ( + // The following attributes are supported by all load balancers: + targetGroupAttributeDeregistrationDelayTimeoutSeconds = "deregistration_delay.timeout_seconds" + targetGroupAttributeDeregistrationStickinessEnabled = "stickiness.enabled" + targetGroupAttributeDeregistrationStickinessType = "stickiness.enabled" + + // The following attributes are supported by Application Load Balancers and Network Load Balancers: + targetGroupAttributeLoadBalancingCrossZoneEnabled = "load_balancing.cross_zone.enabled" + targetGroupAttributeTargetGroupHealthDNSFailoverMinimumHealthyTargetsCount = "target_group_health.dns_failover.minimum_healthy_targets.count" + targetGroupAttributeTargetGroupHealthDNSFailoverMinimumHealthyTargetsPercentage = "target_group_health.dns_failover.minimum_healthy_targets.percentage" + targetGroupAttributeTargetGroupHealthUnhealthyStateRoutingMinimumHealthyTargetsCount = "target_group_health.unhealthy_state_routing.minimum_healthy_targets.count" + targetGroupAttributeTargetGroupHealthUnhealthyStateRoutingMinimumHealthyTargetsPercentage = "target_group_health.unhealthy_state_routing.minimum_healthy_targets.percentage" + + // The following attributes are supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address: + targetGroupAttributeLoadBalancingAlgorithmType = "load_balancing.algorithm.type" + targetGroupAttributeLoadBalancingAlgorithmAnomalyMitigation = "load_balancing.algorithm.anomaly_mitigation" + targetGroupAttributeSlowStartDurationSeconds = "slow_start.duration_seconds" + targetGroupAttributeStickinessAppCookieCookieName = "stickiness.app_cookie.cookie_name" + targetGroupAttributeStickinessAppCookieDurationSeconds = "stickiness.app_cookie.duration_seconds" + targetGroupAttributeStickinessLBCookieDurationSeconds = "stickiness.lb_cookie.duration_seconds" + + // The following attribute is supported only if the load balancer is an Application Load Balancer and the target is a Lambda function: + targetGroupAttributeLambdaMultiValueHeadersEnabled = "lambda.multi_value_headers.enabled" + + // The following attributes are supported only by Network Load Balancers: + targetGroupAttributeDeregistrationDelayConnectionTerminationEnabled = "deregistration_delay.connection_termination.enabled" + targetGroupAttributePreserveClientIPEnabled = "preserve_client_ip.enabled" + targetGroupAttributeProxyProtocolV2Enabled = "proxy_protocol_v2.enabled" + targetGroupAttributeTargetHealthStateUnhealthyConnectionTerminationEnabled = "target_health_state.unhealthy.connection_termination.enabled" + + // The following attributes are supported only by Gateway Load Balancers: + targetGroupAttributeTargetFailoverOnDeregistration = "target_failover.on_deregistration" + targetGroupAttributeTargetFailoverOnUnhealthy = "target_failover.on_unhealthy" +) + +const ( + loadBalancingAlgorithmTypeRoundRobin = "round_robin" + loadBalancingAlgorithmTypeLeastOutstandingRequests = "least_outstanding_requests" + loadBalancingAlgorithmTypeWeightedRandom = "weighted_random" +) + +func loadBalancingAlgorithmType_Values() []string { + return []string{ + loadBalancingAlgorithmTypeRoundRobin, + loadBalancingAlgorithmTypeLeastOutstandingRequests, + // TODO + // loadBalancingAlgorithmTypeWeightedRandom, + } +} + +const ( + loadBalancingCrossZoneEnabledTrue = "true" + loadBalancingCrossZoneEnabledFalse = "false" + loadBalancingCrossZoneEnabledUseLoadBalancerConfiguration = "use_load_balancer_configuration" +) + +func loadBalancingCrossZoneEnabled_Values() []string { + return []string{ + loadBalancingCrossZoneEnabledTrue, + loadBalancingCrossZoneEnabledFalse, + loadBalancingCrossZoneEnabledUseLoadBalancerConfiguration, + } +} + +const ( + stickinessTypeLBCookie = "lb_cookie" // Only for ALBs + stickinessTypeAppCookie = "app_cookie" // Only for ALBs + stickinessTypeSourceIP = "source_ip" // Only for NLBs + stickinessTypeSourceIPDestIP = "source_ip_dest_ip" // Only for GWLBs + stickinessTypeSourceIPDestIPProto = "source_ip_dest_ip_proto" // Only for GWLBs +) + +func stickinessType_Values() []string { + return []string{ + stickinessTypeLBCookie, + stickinessTypeAppCookie, + stickinessTypeSourceIP, + stickinessTypeSourceIPDestIP, + stickinessTypeSourceIPDestIPProto, + } +} + +const ( + targetFailoverRebalance = "rebalance" + targetFailoverNoRebalance = "no_rebalance" +) + +func targetFailover_Values() []string { + return []string{ + targetFailoverRebalance, + targetFailoverNoRebalance, + } +} + const ( healthCheckPortTrafficPort = "traffic-port" ) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index cb5ff6f3c771..df6b14cb5b1b 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -156,23 +156,16 @@ func ResourceTargetGroup() *schema.Resource { Default: false, }, "load_balancing_algorithm_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - "round_robin", - "least_outstanding_requests", - }, false), + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(loadBalancingAlgorithmType_Values(), false), }, "load_balancing_cross_zone_enabled": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - "true", - "false", - "use_load_balancer_configuration", - }, false), + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(loadBalancingCrossZoneEnabled_Values(), false), }, "name": { Type: schema.TypeString, @@ -276,15 +269,9 @@ func ResourceTargetGroup() *schema.Resource { Default: true, }, "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - "lb_cookie", // Only for ALBs - "app_cookie", // Only for ALBs - "source_ip", // Only for NLBs - "source_ip_dest_ip", // Only for GWLBs - "source_ip_dest_ip_proto", // Only for GWLBs - }, false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(stickinessType_Values(), false), }, }, }, @@ -298,20 +285,14 @@ func ResourceTargetGroup() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "on_deregistration": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - "rebalance", - "no_rebalance", - }, false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(targetFailover_Values(), false), }, "on_unhealthy": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - "rebalance", - "no_rebalance", - }, false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(targetFailover_Values(), false), }, }, }, From 7f04626a6596e1a9ea8df1bc9675ea2a5bf399ab Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 14 Dec 2023 17:33:51 -0500 Subject: [PATCH 10/31] r/aws_lb_target_group: Tidy up Update. --- internal/service/elbv2/target_group.go | 58 +++++++++++++------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index df6b14cb5b1b..8b7c08b0175b 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -646,48 +646,46 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) if d.HasChange("health_check") { - var params *elbv2.ModifyTargetGroupInput - healthChecks := d.Get("health_check").([]interface{}) - if len(healthChecks) == 1 { - healthCheck := healthChecks[0].(map[string]interface{}) + if v, ok := d.GetOk("health_check"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) - params = &elbv2.ModifyTargetGroupInput{ + input := &elbv2.ModifyTargetGroupInput{ + HealthCheckEnabled: aws.Bool(tfMap["enabled"].(bool)), + HealthCheckIntervalSeconds: aws.Int64(int64(tfMap["interval"].(int))), + HealthyThresholdCount: aws.Int64(int64(tfMap["healthy_threshold"].(int))), TargetGroupArn: aws.String(d.Id()), - HealthCheckEnabled: aws.Bool(healthCheck["enabled"].(bool)), - HealthCheckIntervalSeconds: aws.Int64(int64(healthCheck["interval"].(int))), - HealthyThresholdCount: aws.Int64(int64(healthCheck["healthy_threshold"].(int))), - UnhealthyThresholdCount: aws.Int64(int64(healthCheck["unhealthy_threshold"].(int))), + UnhealthyThresholdCount: aws.Int64(int64(tfMap["unhealthy_threshold"].(int))), } - t := healthCheck["timeout"].(int) - if t != 0 { - params.HealthCheckTimeoutSeconds = aws.Int64(int64(t)) + if v, ok := tfMap["timeout"].(int); ok && v != 0 { + input.HealthCheckTimeoutSeconds = aws.Int64(int64(v)) } - healthCheckProtocol := healthCheck["protocol"].(string) - protocolVersion := d.Get("protocol_version").(string) - if healthCheckProtocol != elbv2.ProtocolEnumTcp && !d.IsNewResource() { - if protocolVersion == "GRPC" { - params.Matcher = &elbv2.Matcher{ - GrpcCode: aws.String(healthCheck["matcher"].(string)), - } - } else { - params.Matcher = &elbv2.Matcher{ - HttpCode: aws.String(healthCheck["matcher"].(string)), + protocol := tfMap["protocol"].(string) + if protocol != elbv2.ProtocolEnumTcp { + if v, ok := tfMap["matcher"].(string); ok { + if protocolVersion := d.Get("protocol_version").(string); protocolVersion == protocolVersionGRPC { + input.Matcher = &elbv2.Matcher{ + GrpcCode: aws.String(v), + } + } else { + input.Matcher = &elbv2.Matcher{ + HttpCode: aws.String(v), + } } } - params.HealthCheckPath = aws.String(healthCheck["path"].(string)) + input.HealthCheckPath = aws.String(tfMap["path"].(string)) } - if d.Get("target_type").(string) != elbv2.TargetTypeEnumLambda { - params.HealthCheckPort = aws.String(healthCheck["port"].(string)) - params.HealthCheckProtocol = aws.String(healthCheckProtocol) + + if targetType := d.Get("target_type").(string); targetType != elbv2.TargetTypeEnumLambda { + input.HealthCheckPort = aws.String(tfMap["port"].(string)) + input.HealthCheckProtocol = aws.String(protocol) } - } - if params != nil { - _, err := conn.ModifyTargetGroupWithContext(ctx, params) + _, err := conn.ModifyTargetGroupWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying Target Group: %s", err) + return sdkdiag.AppendErrorf(diags, "modifying ELBv2 Target Group (%s): %s", d.Id(), err) } } } From 7cfb2cc4e906cba10f60047b8ea048a3ded65df2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 09:00:56 -0500 Subject: [PATCH 11/31] r/aws_lb_target_group: Add 'expandTargetGroupStickinessAttributes'. --- internal/service/elbv2/const.go | 4 +- internal/service/elbv2/target_group.go | 242 +++++++++++-------------- 2 files changed, 110 insertions(+), 136 deletions(-) diff --git a/internal/service/elbv2/const.go b/internal/service/elbv2/const.go index 87ca3b2676e5..d4ce150cf1ce 100644 --- a/internal/service/elbv2/const.go +++ b/internal/service/elbv2/const.go @@ -95,8 +95,8 @@ func httpXFFHeaderProcessingMode_Values() []string { const ( // The following attributes are supported by all load balancers: targetGroupAttributeDeregistrationDelayTimeoutSeconds = "deregistration_delay.timeout_seconds" - targetGroupAttributeDeregistrationStickinessEnabled = "stickiness.enabled" - targetGroupAttributeDeregistrationStickinessType = "stickiness.enabled" + targetGroupAttributeStickinessEnabled = "stickiness.enabled" + targetGroupAttributeStickinessType = "stickiness.type" // The following attributes are supported by Application Load Balancers and Network Load Balancers: targetGroupAttributeLoadBalancingCrossZoneEnabled = "load_balancing.cross_zone.enabled" diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 8b7c08b0175b..9ee89eb7a047 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -25,6 +25,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/types/nullable" @@ -358,15 +359,16 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta runtimeValidations(d, &diags) + protocol := d.Get("protocol").(string) + targetType := d.Get("target_type").(string) input := &elbv2.CreateTargetGroupInput{ Name: aws.String(name), Tags: getTagsIn(ctx), - TargetType: aws.String(d.Get("target_type").(string)), + TargetType: aws.String(targetType), } - if targetType := d.Get("target_type").(string); targetType != elbv2.TargetTypeEnumLambda { + if targetType != elbv2.TargetTypeEnumLambda { input.Port = aws.Int64(int64(d.Get("port").(int))) - protocol := d.Get("protocol").(string) input.Protocol = aws.String(protocol) switch protocol { case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: @@ -393,8 +395,8 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta input.HealthCheckTimeoutSeconds = aws.Int64(int64(v)) } - protocol := tfMap["protocol"].(string) - if protocol != elbv2.ProtocolEnumTcp { + healthCheckProtocol := tfMap["protocol"].(string) + if healthCheckProtocol != elbv2.ProtocolEnumTcp { if v, ok := tfMap["path"].(string); ok && v != "" { input.HealthCheckPath = aws.String(v) } @@ -412,9 +414,9 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta } } - if targetType := d.Get("target_type").(string); targetType != elbv2.TargetTypeEnumLambda { + if targetType != elbv2.TargetTypeEnumLambda { input.HealthCheckPort = aws.String(tfMap["port"].(string)) - input.HealthCheckProtocol = aws.String(protocol) + input.HealthCheckProtocol = aws.String(healthCheckProtocol) } } @@ -449,65 +451,69 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "waiting for ELBv2 Target Group (%s) create: %s", d.Id(), err) } - var attrs []*elbv2.TargetGroupAttribute + var attributes []*elbv2.TargetGroupAttribute - switch d.Get("target_type").(string) { + switch targetType { case elbv2.TargetTypeEnumInstance, elbv2.TargetTypeEnumIp: + if v, ok := d.GetOk("stickiness"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + attributes = append(attributes, expandTargetGroupStickinessAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) + } + if v, null, _ := nullable.Int(d.Get("deregistration_delay").(string)).Value(); !null { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("deregistration_delay.timeout_seconds"), Value: aws.String(fmt.Sprintf("%d", v)), }) } if v, ok := d.GetOk("load_balancing_algorithm_type"); ok { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("load_balancing.algorithm.type"), Value: aws.String(v.(string)), }) } if v, ok := d.GetOk("load_balancing_cross_zone_enabled"); ok { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("load_balancing.cross_zone.enabled"), Value: aws.String(v.(string)), }) } if v, ok := d.GetOk("preserve_client_ip"); ok { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("preserve_client_ip.enabled"), Value: aws.String(v.(string)), }) } if v, ok := d.GetOk("proxy_protocol_v2"); ok { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("proxy_protocol_v2.enabled"), Value: aws.String(strconv.FormatBool(v.(bool))), }) } if v, ok := d.GetOk("connection_termination"); ok { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("deregistration_delay.connection_termination.enabled"), Value: aws.String(strconv.FormatBool(v.(bool))), }) } if v, ok := d.GetOk("slow_start"); ok { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("slow_start.duration_seconds"), Value: aws.String(fmt.Sprintf("%d", v.(int))), }) } // Only supported for GWLB - if v, ok := d.Get("protocol").(string); ok && v == elbv2.ProtocolEnumGeneve { + if protocol == elbv2.ProtocolEnumGeneve { if v, ok := d.GetOk("target_failover"); ok { failoverBlock := v.([]interface{}) failover := failoverBlock[0].(map[string]interface{}) - attrs = append(attrs, + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("target_failover.on_deregistration"), Value: aws.String(failover["on_deregistration"].(string)), @@ -526,7 +532,7 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta 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, + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("target_health_state.unhealthy.connection_termination.enabled"), Value: aws.String(strconv.FormatBool(targetHealthState["enable_unhealthy_connection_termination"].(bool))), @@ -535,64 +541,25 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta } } } - - if v, ok := d.GetOk("stickiness"); ok && len(v.([]interface{})) > 0 { - stickinessBlocks := v.([]interface{}) - stickiness := stickinessBlocks[0].(map[string]interface{}) - - attrs = append(attrs, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.enabled"), - Value: aws.String(strconv.FormatBool(stickiness["enabled"].(bool))), - }, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.type"), - Value: aws.String(stickiness["type"].(string)), - }) - - switch d.Get("protocol").(string) { - case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: - 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": - attrs = append(attrs, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.app_cookie.duration_seconds"), - Value: aws.String(fmt.Sprintf("%d", stickiness["cookie_duration"].(int))), - }, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.app_cookie.cookie_name"), - Value: aws.String(stickiness["cookie_name"].(string)), - }) - default: - log.Printf("[WARN] Unexpected stickiness type. Expected lb_cookie or app_cookie, got %s", stickiness["type"].(string)) - } - } - } case elbv2.TargetTypeEnumLambda: if v, ok := d.GetOk("lambda_multi_value_headers_enabled"); ok { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("lambda.multi_value_headers.enabled"), Value: aws.String(strconv.FormatBool(v.(bool))), }) } } - if len(attrs) > 0 { - params := &elbv2.ModifyTargetGroupAttributesInput{ + if len(attributes) > 0 { + input := &elbv2.ModifyTargetGroupAttributesInput{ + Attributes: attributes, TargetGroupArn: aws.String(d.Id()), - Attributes: attrs, } - _, err := conn.ModifyTargetGroupAttributesWithContext(ctx, params) + _, err := conn.ModifyTargetGroupAttributesWithContext(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying Target Group Attributes: %s", err) + return sdkdiag.AppendErrorf(diags, "modifying ELBv2 Target Group (%s) attributes: %s", d.Id(), err) } } @@ -645,6 +612,9 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + protocol := d.Get("protocol").(string) + targetType := d.Get("target_type").(string) + if d.HasChange("health_check") { if v, ok := d.GetOk("health_check"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { tfMap := v.([]interface{})[0].(map[string]interface{}) @@ -661,8 +631,8 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta input.HealthCheckTimeoutSeconds = aws.Int64(int64(v)) } - protocol := tfMap["protocol"].(string) - if protocol != elbv2.ProtocolEnumTcp { + healthCheckProtocol := tfMap["protocol"].(string) + if healthCheckProtocol != elbv2.ProtocolEnumTcp { if v, ok := tfMap["matcher"].(string); ok { if protocolVersion := d.Get("protocol_version").(string); protocolVersion == protocolVersionGRPC { input.Matcher = &elbv2.Matcher{ @@ -677,9 +647,9 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta input.HealthCheckPath = aws.String(tfMap["path"].(string)) } - if targetType := d.Get("target_type").(string); targetType != elbv2.TargetTypeEnumLambda { + if targetType != elbv2.TargetTypeEnumLambda { input.HealthCheckPort = aws.String(tfMap["port"].(string)) - input.HealthCheckProtocol = aws.String(protocol) + input.HealthCheckProtocol = aws.String(healthCheckProtocol) } _, err := conn.ModifyTargetGroupWithContext(ctx, input) @@ -690,13 +660,24 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta } } - var attrs []*elbv2.TargetGroupAttribute + var attributes []*elbv2.TargetGroupAttribute - switch d.Get("target_type").(string) { + switch targetType { case elbv2.TargetTypeEnumInstance, elbv2.TargetTypeEnumIp: + if d.HasChange("stickiness") { + if v, ok := d.GetOk("stickiness"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + attributes = append(attributes, expandTargetGroupStickinessAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) + } else { + attributes = append(attributes, &elbv2.TargetGroupAttribute{ + Key: aws.String(targetGroupAttributeStickinessEnabled), + Value: flex.BoolValueToString(false), + }) + } + } + if d.HasChange("deregistration_delay") { if v, null, _ := nullable.Int(d.Get("deregistration_delay").(string)).Value(); !null { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("deregistration_delay.timeout_seconds"), Value: aws.String(fmt.Sprintf("%d", v)), }) @@ -704,91 +685,42 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta } if d.HasChange("slow_start") { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("slow_start.duration_seconds"), Value: aws.String(fmt.Sprintf("%d", d.Get("slow_start").(int))), }) } if d.HasChange("proxy_protocol_v2") { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("proxy_protocol_v2.enabled"), Value: aws.String(strconv.FormatBool(d.Get("proxy_protocol_v2").(bool))), }) } if d.HasChange("connection_termination") { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("deregistration_delay.connection_termination.enabled"), Value: aws.String(strconv.FormatBool(d.Get("connection_termination").(bool))), }) } if d.HasChange("preserve_client_ip") { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("preserve_client_ip.enabled"), Value: aws.String(d.Get("preserve_client_ip").(string)), }) } - if d.HasChange("stickiness") { - stickinessBlocks := d.Get("stickiness").([]interface{}) - if len(stickinessBlocks) == 1 { - stickiness := stickinessBlocks[0].(map[string]interface{}) - attrs = append(attrs, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.enabled"), - Value: aws.String(strconv.FormatBool(stickiness["enabled"].(bool))), - }, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.type"), - Value: aws.String(stickiness["type"].(string)), - }) - - switch d.Get("protocol").(string) { - case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: - 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))), - }, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.app_cookie.cookie_name"), - Value: aws.String(stickiness["cookie_name"].(string)), - }) - case "app_cookie": - attrs = append(attrs, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.app_cookie.duration_seconds"), - Value: aws.String(fmt.Sprintf("%d", stickiness["cookie_duration"].(int))), - }, - &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.app_cookie.cookie_name"), - Value: aws.String(stickiness["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 { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ - Key: aws.String("stickiness.enabled"), - Value: aws.String("false"), - }) - } - } - if d.HasChange("load_balancing_algorithm_type") { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("load_balancing.algorithm.type"), Value: aws.String(d.Get("load_balancing_algorithm_type").(string)), }) } if d.HasChange("load_balancing_cross_zone_enabled") { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("load_balancing.cross_zone.enabled"), Value: aws.String(d.Get("load_balancing_cross_zone_enabled").(string)), }) @@ -798,7 +730,7 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta targetHealthStateBlock := d.Get("target_health_state").([]interface{}) if len(targetHealthStateBlock) == 1 { targetHealthState := targetHealthStateBlock[0].(map[string]interface{}) - attrs = append(attrs, + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("target_health_state.unhealthy.connection_termination.enabled"), Value: aws.String(strconv.FormatBool(targetHealthState["enable_unhealthy_connection_termination"].(bool))), @@ -810,7 +742,7 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta failoverBlock := d.Get("target_failover").([]interface{}) if len(failoverBlock) == 1 { failover := failoverBlock[0].(map[string]interface{}) - attrs = append(attrs, + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("target_failover.on_deregistration"), Value: aws.String(failover["on_deregistration"].(string)), @@ -825,22 +757,23 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta case elbv2.TargetTypeEnumLambda: if d.HasChange("lambda_multi_value_headers_enabled") { - attrs = append(attrs, &elbv2.TargetGroupAttribute{ + attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("lambda.multi_value_headers.enabled"), Value: aws.String(strconv.FormatBool(d.Get("lambda_multi_value_headers_enabled").(bool))), }) } } - if len(attrs) > 0 { - params := &elbv2.ModifyTargetGroupAttributesInput{ + if len(attributes) > 0 { + input := &elbv2.ModifyTargetGroupAttributesInput{ + Attributes: attributes, TargetGroupArn: aws.String(d.Id()), - Attributes: attrs, } - _, err := conn.ModifyTargetGroupAttributesWithContext(ctx, params) + _, err := conn.ModifyTargetGroupAttributesWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying Target Group Attributes: %s", err) + return sdkdiag.AppendErrorf(diags, "modifying ELBv2 Target Group (%s) attributes: %s", d.Id(), err) } } @@ -1003,6 +936,47 @@ func TargetGroupSuffixFromARN(arn *string) string { return "" } +func expandTargetGroupStickinessAttributes(tfMap map[string]interface{}, protocol string) []*elbv2.TargetGroupAttribute { + if tfMap == nil { + return nil + } + + apiObjects := []*elbv2.TargetGroupAttribute{ + { + Key: aws.String(targetGroupAttributeStickinessEnabled), + Value: flex.BoolValueToString(tfMap["enabled"].(bool)), + }, + { + Key: aws.String(targetGroupAttributeStickinessType), + Value: aws.String(tfMap["type"].(string)), + }, + } + + switch protocol { + case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: + switch stickinessType := tfMap["type"].(string); stickinessType { + case stickinessTypeLBCookie: + apiObjects = append(apiObjects, + &elbv2.TargetGroupAttribute{ + Key: aws.String(targetGroupAttributeStickinessLBCookieDurationSeconds), + Value: flex.IntValueToString(tfMap["cookie_duration"].(int)), + }) + case stickinessTypeAppCookie: + apiObjects = append(apiObjects, + &elbv2.TargetGroupAttribute{ + Key: aws.String(targetGroupAttributeStickinessAppCookieCookieName), + Value: aws.String(tfMap["cookie_name"].(string)), + }, + &elbv2.TargetGroupAttribute{ + Key: aws.String(targetGroupAttributeStickinessAppCookieDurationSeconds), + Value: flex.IntValueToString(tfMap["cookie_duration"].(int)), + }) + } + } + + return apiObjects +} + // flattenTargetGroupResource takes a *elbv2.TargetGroup and populates all respective resource fields. func flattenTargetGroupResource(ctx context.Context, d *schema.ResourceData, meta interface{}, targetGroup *elbv2.TargetGroup) error { conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) From e23d2f2bee92cf3520c9490b87590ef0e20ce77f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 10:24:27 -0500 Subject: [PATCH 12/31] r/aws_lb_target_group: Tidy up Read. --- internal/service/elbv2/target_group.go | 242 +++++++++++++------------ 1 file changed, 127 insertions(+), 115 deletions(-) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 9ee89eb7a047..2a95c61a3a85 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -584,9 +584,7 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - outputRaw, err := tfresource.RetryWhenNewResourceNotFound(ctx, propagationTimeout, func() (interface{}, error) { - return FindTargetGroupByARN(ctx, conn, d.Id()) - }, d.IsNewResource()) + targetGroup, err := FindTargetGroupByARN(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] ELBv2 Target Group %s not found, removing from state", d.Id()) @@ -602,9 +600,108 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i runtimeValidations(d, &diags) } - if err := flattenTargetGroupResource(ctx, d, meta, outputRaw.(*elbv2.TargetGroup)); err != nil { - return sdkdiag.AppendFromErr(diags, err) + targetType := aws.StringValue(targetGroup.TargetType) + + d.Set("arn", targetGroup.TargetGroupArn) + d.Set("arn_suffix", TargetGroupSuffixFromARN(targetGroup.TargetGroupArn)) + d.Set("ip_address_type", targetGroup.IpAddressType) + d.Set("name", targetGroup.TargetGroupName) + d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(targetGroup.TargetGroupName))) + d.Set("target_type", targetType) + + if err := d.Set("health_check", flattenLbTargetGroupHealthCheck(targetGroup)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting health_check: %s", err) + } + + if _, ok := d.GetOk("port"); targetGroup.Port != nil || ok { + d.Set("port", targetGroup.Port) + } + if _, ok := d.GetOk("protocol"); targetGroup.Protocol != nil || ok { + d.Set("protocol", targetGroup.Protocol) + } + if _, ok := d.GetOk("protocol_version"); targetGroup.ProtocolVersion != nil || ok { + d.Set("protocol_version", targetGroup.ProtocolVersion) + } + if _, ok := d.GetOk("vpc_id"); targetGroup.VpcId != nil || ok { + d.Set("vpc_id", targetGroup.VpcId) + } + + attributes, err := findTargetGroupAttributesByARN(ctx, conn, d.Id()) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELBv2 Target Group (%s) attributes: %s", d.Id(), err) + } + + for _, attr := range attributes { + switch aws.StringValue(attr.Key) { + case "deregistration_delay.timeout_seconds": + d.Set("deregistration_delay", attr.Value) + case "lambda.multi_value_headers.enabled": + enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "converting lambda.multi_value_headers.enabled to bool: %s", aws.StringValue(attr.Value)) + } + d.Set("lambda_multi_value_headers_enabled", enabled) + case "proxy_protocol_v2.enabled": + enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "converting proxy_protocol_v2.enabled to bool: %s", aws.StringValue(attr.Value)) + } + d.Set("proxy_protocol_v2", enabled) + case "deregistration_delay.connection_termination.enabled": + enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "converting deregistration_delay.connection_termination.enabled to bool: %s", aws.StringValue(attr.Value)) + } + d.Set("connection_termination", enabled) + case "slow_start.duration_seconds": + slowStart, err := strconv.Atoi(aws.StringValue(attr.Value)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "converting slow_start.duration_seconds to int: %s", aws.StringValue(attr.Value)) + } + d.Set("slow_start", slowStart) + case "load_balancing.algorithm.type": + loadBalancingAlgorithm := aws.StringValue(attr.Value) + d.Set("load_balancing_algorithm_type", loadBalancingAlgorithm) + case "load_balancing.cross_zone.enabled": + loadBalancingCrossZoneEnabled := aws.StringValue(attr.Value) + d.Set("load_balancing_cross_zone_enabled", loadBalancingCrossZoneEnabled) + case "preserve_client_ip.enabled": + _, err := strconv.ParseBool(aws.StringValue(attr.Value)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "converting preserve_client_ip.enabled to bool: %s", aws.StringValue(attr.Value)) + } + d.Set("preserve_client_ip", attr.Value) + } + } + + stickinessAttr, err := flattenTargetGroupStickiness(attributes) + if err != nil { + return sdkdiag.AppendErrorf(diags, "flattening stickiness: %s", err) + } + + if err := d.Set("stickiness", stickinessAttr); err != nil { + return sdkdiag.AppendErrorf(diags, "setting stickiness: %s", err) + } + + targetHealthStateAttr, err := flattenTargetHealthState(attributes) + if err != nil { + return sdkdiag.AppendErrorf(diags, "flattening target health state: %s", err) + } + if err := d.Set("target_health_state", targetHealthStateAttr); err != nil { + return sdkdiag.AppendErrorf(diags, "setting target health state: %s", err) + } + + // Set target failover attributes for GWLB + targetFailoverAttr := flattenTargetGroupFailover(attributes) + if err != nil { + return sdkdiag.AppendErrorf(diags, "flattening target failover: %s", err) + } + + if err := d.Set("target_failover", targetFailoverAttr); err != nil { + return sdkdiag.AppendErrorf(diags, "setting target failover: %s", err) } + return diags } @@ -881,6 +978,31 @@ func findTargetGroups(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.Descr return output, nil } +func findTargetGroupAttributesByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) ([]*elbv2.TargetGroupAttribute, error) { + input := &elbv2.DescribeTargetGroupAttributesInput{ + TargetGroupArn: aws.String(arn), + } + + output, err := conn.DescribeTargetGroupAttributesWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTargetGroupNotFoundException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Attributes, nil +} + func validTargetGroupHealthCheckPath(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if !strings.HasPrefix(value, "/") { @@ -977,116 +1099,6 @@ func expandTargetGroupStickinessAttributes(tfMap map[string]interface{}, protoco return apiObjects } -// flattenTargetGroupResource takes a *elbv2.TargetGroup and populates all respective resource fields. -func flattenTargetGroupResource(ctx context.Context, d *schema.ResourceData, meta interface{}, targetGroup *elbv2.TargetGroup) error { - conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - - targetType := aws.StringValue(targetGroup.TargetType) - - d.Set("arn", targetGroup.TargetGroupArn) - d.Set("arn_suffix", TargetGroupSuffixFromARN(targetGroup.TargetGroupArn)) - d.Set("ip_address_type", targetGroup.IpAddressType) - d.Set("name", targetGroup.TargetGroupName) - d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(targetGroup.TargetGroupName))) - d.Set("target_type", targetType) - - if err := d.Set("health_check", flattenLbTargetGroupHealthCheck(targetGroup)); err != nil { - return fmt.Errorf("setting health_check: %w", err) - } - - if _, ok := d.GetOk("port"); targetGroup.Port != nil || ok { - d.Set("port", targetGroup.Port) - } - if _, ok := d.GetOk("protocol"); targetGroup.Protocol != nil || ok { - d.Set("protocol", targetGroup.Protocol) - } - if _, ok := d.GetOk("protocol_version"); targetGroup.ProtocolVersion != nil || ok { - d.Set("protocol_version", targetGroup.ProtocolVersion) - } - if _, ok := d.GetOk("vpc_id"); targetGroup.VpcId != nil || ok { - d.Set("vpc_id", targetGroup.VpcId) - } - - attrResp, err := conn.DescribeTargetGroupAttributesWithContext(ctx, &elbv2.DescribeTargetGroupAttributesInput{ - TargetGroupArn: aws.String(d.Id()), - }) - if err != nil { - return fmt.Errorf("retrieving Target Group Attributes: %w", err) - } - - for _, attr := range attrResp.Attributes { - switch aws.StringValue(attr.Key) { - case "deregistration_delay.timeout_seconds": - d.Set("deregistration_delay", attr.Value) - case "lambda.multi_value_headers.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return fmt.Errorf("converting lambda.multi_value_headers.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("lambda_multi_value_headers_enabled", enabled) - case "proxy_protocol_v2.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return fmt.Errorf("converting proxy_protocol_v2.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("proxy_protocol_v2", enabled) - case "deregistration_delay.connection_termination.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return fmt.Errorf("converting deregistration_delay.connection_termination.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("connection_termination", enabled) - case "slow_start.duration_seconds": - slowStart, err := strconv.Atoi(aws.StringValue(attr.Value)) - if err != nil { - return fmt.Errorf("converting slow_start.duration_seconds to int: %s", aws.StringValue(attr.Value)) - } - d.Set("slow_start", slowStart) - case "load_balancing.algorithm.type": - loadBalancingAlgorithm := aws.StringValue(attr.Value) - d.Set("load_balancing_algorithm_type", loadBalancingAlgorithm) - case "load_balancing.cross_zone.enabled": - loadBalancingCrossZoneEnabled := aws.StringValue(attr.Value) - d.Set("load_balancing_cross_zone_enabled", loadBalancingCrossZoneEnabled) - case "preserve_client_ip.enabled": - _, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return fmt.Errorf("converting preserve_client_ip.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("preserve_client_ip", attr.Value) - } - } - - stickinessAttr, err := flattenTargetGroupStickiness(attrResp.Attributes) - if err != nil { - return fmt.Errorf("flattening stickiness: %w", err) - } - - if err := d.Set("stickiness", stickinessAttr); err != nil { - 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 { - return fmt.Errorf("flattening target failover: %w", err) - } - - if err := d.Set("target_failover", targetFailoverAttr); err != nil { - return fmt.Errorf("setting target failover: %w", err) - } - - return nil -} - func flattenTargetHealthState(attributes []*elbv2.TargetGroupAttribute) ([]interface{}, error) { if len(attributes) == 0 { return []interface{}{}, nil From c3513572b1a02bc5899bf3e100f31e115420251a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 10:50:08 -0500 Subject: [PATCH 13/31] r/aws_lb_target_group: Tidy up 'flattenTargetGroupStickinessAttributes'. --- internal/service/elbv2/target_group.go | 91 +++++++++---------- .../service/elbv2/target_group_data_source.go | 24 ++--- 2 files changed, 51 insertions(+), 64 deletions(-) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 2a95c61a3a85..a0a8d0ce2297 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -600,13 +600,12 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i runtimeValidations(d, &diags) } - targetType := aws.StringValue(targetGroup.TargetType) - d.Set("arn", targetGroup.TargetGroupArn) d.Set("arn_suffix", TargetGroupSuffixFromARN(targetGroup.TargetGroupArn)) d.Set("ip_address_type", targetGroup.IpAddressType) d.Set("name", targetGroup.TargetGroupName) d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(targetGroup.TargetGroupName))) + targetType := aws.StringValue(targetGroup.TargetType) d.Set("target_type", targetType) if err := d.Set("health_check", flattenLbTargetGroupHealthCheck(targetGroup)); err != nil { @@ -616,8 +615,10 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i if _, ok := d.GetOk("port"); targetGroup.Port != nil || ok { d.Set("port", targetGroup.Port) } + var protocol string if _, ok := d.GetOk("protocol"); targetGroup.Protocol != nil || ok { - d.Set("protocol", targetGroup.Protocol) + protocol = aws.StringValue(targetGroup.Protocol) + d.Set("protocol", protocol) } if _, ok := d.GetOk("protocol_version"); targetGroup.ProtocolVersion != nil || ok { d.Set("protocol_version", targetGroup.ProtocolVersion) @@ -675,12 +676,7 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i } } - stickinessAttr, err := flattenTargetGroupStickiness(attributes) - if err != nil { - return sdkdiag.AppendErrorf(diags, "flattening stickiness: %s", err) - } - - if err := d.Set("stickiness", stickinessAttr); err != nil { + if err := d.Set("stickiness", []interface{}{flattenTargetGroupStickinessAttributes(attributes, protocol)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting stickiness: %s", err) } @@ -1099,6 +1095,42 @@ func expandTargetGroupStickinessAttributes(tfMap map[string]interface{}, protoco return apiObjects } +func flattenTargetGroupStickinessAttributes(apiObjects []*elbv2.TargetGroupAttribute, protocol string) map[string]interface{} { + if len(apiObjects) == 0 { + return nil + } + + tfMap := map[string]interface{}{} + + var stickinessType string + for _, apiObject := range apiObjects { + switch k, v := aws.StringValue(apiObject.Key), apiObject.Value; k { + case targetGroupAttributeStickinessEnabled: + tfMap["enabled"] = flex.StringToBoolValue(v) + case targetGroupAttributeStickinessType: + stickinessType = aws.StringValue(v) + tfMap["type"] = stickinessType + } + } + + switch protocol { + case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: + for _, apiObject := range apiObjects { + k, v := aws.StringValue(apiObject.Key), apiObject.Value + switch { + case k == targetGroupAttributeStickinessLBCookieDurationSeconds && stickinessType == stickinessTypeLBCookie: + tfMap["cookie_duration"] = flex.StringToIntValue(v) + case k == targetGroupAttributeStickinessAppCookieCookieName && stickinessType == stickinessTypeAppCookie: + tfMap["cookie_name"] = aws.StringValue(v) + case k == targetGroupAttributeStickinessAppCookieDurationSeconds && stickinessType == stickinessTypeAppCookie: + tfMap["cookie_duration"] = flex.StringToIntValue(v) + } + } + } + + return tfMap +} + func flattenTargetHealthState(attributes []*elbv2.TargetGroupAttribute) ([]interface{}, error) { if len(attributes) == 0 { return []interface{}{}, nil @@ -1139,47 +1171,6 @@ func flattenTargetGroupFailover(attributes []*elbv2.TargetGroupAttribute) []inte return []interface{}{m} } -func flattenTargetGroupStickiness(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 "stickiness.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return nil, fmt.Errorf("converting stickiness.enabled to bool: %s", aws.StringValue(attr.Value)) - } - m["enabled"] = enabled - case "stickiness.type": - m["type"] = aws.StringValue(attr.Value) - case "stickiness.lb_cookie.duration_seconds": - if sType, ok := m["type"].(string); !ok || sType == "lb_cookie" { - duration, err := strconv.Atoi(aws.StringValue(attr.Value)) - if err != nil { - return nil, fmt.Errorf("converting stickiness.lb_cookie.duration_seconds to int: %s", aws.StringValue(attr.Value)) - } - m["cookie_duration"] = duration - } - case "stickiness.app_cookie.cookie_name": - m["cookie_name"] = aws.StringValue(attr.Value) - case "stickiness.app_cookie.duration_seconds": - if sType, ok := m["type"].(string); !ok || sType == "app_cookie" { - duration, err := strconv.Atoi(aws.StringValue(attr.Value)) - if err != nil { - return nil, fmt.Errorf("converting stickiness.app_cookie.duration_seconds to int: %s", aws.StringValue(attr.Value)) - } - m["cookie_duration"] = duration - } - } - } - - return []interface{}{m}, nil -} - func resourceTargetGroupCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, meta any) error { healthCheck := make(map[string]any) if healthChecks := diff.Get("health_check").([]interface{}); len(healthChecks) == 1 { diff --git a/internal/service/elbv2/target_group_data_source.go b/internal/service/elbv2/target_group_data_source.go index 1dd94c9293cd..85b4dd1b326a 100644 --- a/internal/service/elbv2/target_group_data_source.go +++ b/internal/service/elbv2/target_group_data_source.go @@ -232,24 +232,25 @@ func dataSourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "setting health_check: %s", err) } + var protocol string if v, _ := d.Get("target_type").(string); v != elbv2.TargetTypeEnumLambda { - d.Set("vpc_id", targetGroup.VpcId) d.Set("port", targetGroup.Port) - d.Set("protocol", targetGroup.Protocol) + protocol = aws.StringValue(targetGroup.Protocol) + d.Set("protocol", protocol) + d.Set("vpc_id", targetGroup.VpcId) } - switch d.Get("protocol").(string) { + switch protocol { case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: d.Set("protocol_version", targetGroup.ProtocolVersion) } - attrResp, err := conn.DescribeTargetGroupAttributesWithContext(ctx, &elbv2.DescribeTargetGroupAttributesInput{ - TargetGroupArn: aws.String(d.Id()), - }) + attributes, err := findTargetGroupAttributesByARN(ctx, conn, d.Id()) + if err != nil { - return sdkdiag.AppendErrorf(diags, "retrieving Target Group Attributes: %s", err) + return sdkdiag.AppendErrorf(diags, "reading ELBv2 Target Group (%s) attributes: %s", d.Id(), err) } - for _, attr := range attrResp.Attributes { + for _, attr := range attributes { switch aws.StringValue(attr.Key) { case "deregistration_delay.connection_termination.enabled": enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) @@ -296,12 +297,7 @@ func dataSourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta } } - stickinessAttr, err := flattenTargetGroupStickiness(attrResp.Attributes) - if err != nil { - return sdkdiag.AppendErrorf(diags, "flattening stickiness: %s", err) - } - - if err := d.Set("stickiness", stickinessAttr); err != nil { + if err := d.Set("stickiness", []interface{}{flattenTargetGroupStickinessAttributes(attributes, protocol)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting stickiness: %s", err) } From 3d890581eeb43ef5a42ee97e1a8ff2a1fa6ec780 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 10:57:48 -0500 Subject: [PATCH 14/31] Fixup 'TestAccELBV2TargetGroup_Stickiness_updateStickinessType'. --- internal/service/elbv2/target_group_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/service/elbv2/target_group_test.go b/internal/service/elbv2/target_group_test.go index 5a5f81c053a2..602b7188380b 100644 --- a/internal/service/elbv2/target_group_test.go +++ b/internal/service/elbv2/target_group_test.go @@ -1545,6 +1545,7 @@ func TestAccELBV2TargetGroup_Stickiness_updateStickinessType(t *testing.T) { 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_name", ""), resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "10000"), resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), resource.TestCheckResourceAttr(resourceName, "health_check.0.path", "/health2"), @@ -1596,7 +1597,7 @@ func TestAccELBV2TargetGroup_Stickiness_updateStickinessType(t *testing.T) { 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_name", "Cookie"), + resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_name", ""), resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "10000"), resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), resource.TestCheckResourceAttr(resourceName, "health_check.0.path", "/health2"), From 36f3c805c9297e1ce1cbf11aa974c02e5383f52e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 11:00:07 -0500 Subject: [PATCH 15/31] Tweak CHANGELOG entry. --- .changelog/31436.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/31436.txt b/.changelog/31436.txt index c36d19086b48..cd5e71e36c01 100644 --- a/.changelog/31436.txt +++ b/.changelog/31436.txt @@ -1,3 +1,3 @@ ```release-note:bug -resource/aws_lb_target_group: Persist `stickiness.app_cookie.cookie_name` across changes between app_cookie and lb_cookie ALB stickiness +resource/aws_lb_target_group: Fix diff on `stickiness.cookie_name` when `stickiness.type` is `lb_cookie` ``` \ No newline at end of file From 6c80e0370084ab8d93aa9dcde62f77e9941e64ea Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 11:13:32 -0500 Subject: [PATCH 16/31] r/aws_lb_target_group: Tidy up 'flattenTargetGroupHealthStateAttributes'. --- internal/service/elbv2/target_group.go | 88 ++++++++++++-------------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index a0a8d0ce2297..92e49af41e6e 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -459,6 +459,10 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta attributes = append(attributes, expandTargetGroupStickinessAttributes(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, expandTargetGroupHealthStateAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) + } + if v, null, _ := nullable.Int(d.Get("deregistration_delay").(string)).Value(); !null { attributes = append(attributes, &elbv2.TargetGroupAttribute{ Key: aws.String("deregistration_delay.timeout_seconds"), @@ -525,22 +529,6 @@ 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{}) - attributes = append(attributes, - &elbv2.TargetGroupAttribute{ - Key: aws.String("target_health_state.unhealthy.connection_termination.enabled"), - Value: aws.String(strconv.FormatBool(targetHealthState["enable_unhealthy_connection_termination"].(bool))), - }, - ) - } - } - } case elbv2.TargetTypeEnumLambda: if v, ok := d.GetOk("lambda_multi_value_headers_enabled"); ok { attributes = append(attributes, &elbv2.TargetGroupAttribute{ @@ -680,12 +668,8 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i return sdkdiag.AppendErrorf(diags, "setting stickiness: %s", err) } - targetHealthStateAttr, err := flattenTargetHealthState(attributes) - if err != nil { - return sdkdiag.AppendErrorf(diags, "flattening target health state: %s", err) - } - if err := d.Set("target_health_state", targetHealthStateAttr); err != nil { - return sdkdiag.AppendErrorf(diags, "setting target health state: %s", err) + if err := d.Set("target_health_state", []interface{}{flattenTargetGroupHealthStateAttributes(attributes, protocol)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting target_health_state: %s", err) } // Set target failover attributes for GWLB @@ -768,6 +752,12 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta } } + 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, expandTargetGroupHealthStateAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) + } + } + if d.HasChange("deregistration_delay") { if v, null, _ := nullable.Int(d.Get("deregistration_delay").(string)).Value(); !null { attributes = append(attributes, &elbv2.TargetGroupAttribute{ @@ -819,18 +809,6 @@ 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{}) - attributes = append(attributes, - &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 { @@ -1131,25 +1109,43 @@ func flattenTargetGroupStickinessAttributes(apiObjects []*elbv2.TargetGroupAttri return tfMap } -func flattenTargetHealthState(attributes []*elbv2.TargetGroupAttribute) ([]interface{}, error) { - if len(attributes) == 0 { - return []interface{}{}, nil +func expandTargetGroupHealthStateAttributes(tfMap map[string]interface{}, protocol string) []*elbv2.TargetGroupAttribute { + if tfMap == nil { + return nil } - m := make(map[string]interface{}) + var apiObjects []*elbv2.TargetGroupAttribute - 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)) + switch protocol { + case elbv2.ProtocolEnumTcp, elbv2.ProtocolEnumTls: + apiObjects = append(apiObjects, + &elbv2.TargetGroupAttribute{ + Key: aws.String(targetGroupAttributeTargetHealthStateUnhealthyConnectionTerminationEnabled), + Value: flex.BoolValueToString(tfMap["enable_unhealthy_connection_termination"].(bool)), + }) + } + + return apiObjects +} + +func flattenTargetGroupHealthStateAttributes(apiObjects []*elbv2.TargetGroupAttribute, protocol string) map[string]interface{} { + if len(apiObjects) == 0 { + return nil + } + + tfMap := map[string]interface{}{} + + switch protocol { + case elbv2.ProtocolEnumTcp, elbv2.ProtocolEnumTls: + for _, apiObject := range apiObjects { + switch k, v := aws.StringValue(apiObject.Key), apiObject.Value; k { + case targetGroupAttributeTargetHealthStateUnhealthyConnectionTerminationEnabled: + tfMap["enable_unhealthy_connection_termination"] = flex.StringToBoolValue(v) } - m["enable_unhealthy_connection_termination"] = enabled } } - return []interface{}{m}, nil + return tfMap } func flattenTargetGroupFailover(attributes []*elbv2.TargetGroupAttribute) []interface{} { From 5c710890bd3360741257ecfaf66c726d02d6606f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 11:24:02 -0500 Subject: [PATCH 17/31] r/aws_lb_target_group: Tidy up 'flattenTargetGroupTargetFailoverAttributes'. --- internal/service/elbv2/target_group.go | 144 ++++++++++++------------- 1 file changed, 69 insertions(+), 75 deletions(-) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 92e49af41e6e..6418067aba77 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -459,8 +459,12 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta attributes = append(attributes, expandTargetGroupStickinessAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) } + if v, ok := d.GetOk("target_failover"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + attributes = append(attributes, expandTargetGroupTargetFailoverAttributes(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, expandTargetGroupHealthStateAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) + attributes = append(attributes, expandTargetGroupTargetHealthStateAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) } if v, null, _ := nullable.Int(d.Get("deregistration_delay").(string)).Value(); !null { @@ -511,24 +515,6 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta Value: aws.String(fmt.Sprintf("%d", v.(int))), }) } - - // Only supported for GWLB - if protocol == elbv2.ProtocolEnumGeneve { - if v, ok := d.GetOk("target_failover"); ok { - failoverBlock := v.([]interface{}) - failover := failoverBlock[0].(map[string]interface{}) - attributes = append(attributes, - &elbv2.TargetGroupAttribute{ - Key: aws.String("target_failover.on_deregistration"), - Value: aws.String(failover["on_deregistration"].(string)), - }, - &elbv2.TargetGroupAttribute{ - Key: aws.String("target_failover.on_unhealthy"), - Value: aws.String(failover["on_unhealthy"].(string)), - }, - ) - } - } case elbv2.TargetTypeEnumLambda: if v, ok := d.GetOk("lambda_multi_value_headers_enabled"); ok { attributes = append(attributes, &elbv2.TargetGroupAttribute{ @@ -621,6 +607,18 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i return sdkdiag.AppendErrorf(diags, "reading ELBv2 Target Group (%s) attributes: %s", d.Id(), err) } + if err := d.Set("stickiness", []interface{}{flattenTargetGroupStickinessAttributes(attributes, protocol)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting stickiness: %s", err) + } + + if err := d.Set("target_failover", []interface{}{flattenTargetGroupTargetFailoverAttributes(attributes, protocol)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting target_failover: %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) + } + for _, attr := range attributes { switch aws.StringValue(attr.Key) { case "deregistration_delay.timeout_seconds": @@ -664,24 +662,6 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i } } - if err := d.Set("stickiness", []interface{}{flattenTargetGroupStickinessAttributes(attributes, protocol)}); err != nil { - return sdkdiag.AppendErrorf(diags, "setting stickiness: %s", err) - } - - if err := d.Set("target_health_state", []interface{}{flattenTargetGroupHealthStateAttributes(attributes, protocol)}); err != nil { - return sdkdiag.AppendErrorf(diags, "setting target_health_state: %s", err) - } - - // Set target failover attributes for GWLB - targetFailoverAttr := flattenTargetGroupFailover(attributes) - if err != nil { - return sdkdiag.AppendErrorf(diags, "flattening target failover: %s", err) - } - - if err := d.Set("target_failover", targetFailoverAttr); err != nil { - return sdkdiag.AppendErrorf(diags, "setting target failover: %s", err) - } - return diags } @@ -752,9 +732,15 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta } } + if d.HasChange("target_failover") { + if v, ok := d.GetOk("target_failover"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + attributes = append(attributes, expandTargetGroupTargetFailoverAttributes(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, expandTargetGroupHealthStateAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) + attributes = append(attributes, expandTargetGroupTargetHealthStateAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) } } @@ -808,24 +794,6 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta Value: aws.String(d.Get("load_balancing_cross_zone_enabled").(string)), }) } - - if d.HasChange("target_failover") { - failoverBlock := d.Get("target_failover").([]interface{}) - if len(failoverBlock) == 1 { - failover := failoverBlock[0].(map[string]interface{}) - attributes = append(attributes, - &elbv2.TargetGroupAttribute{ - Key: aws.String("target_failover.on_deregistration"), - Value: aws.String(failover["on_deregistration"].(string)), - }, - &elbv2.TargetGroupAttribute{ - Key: aws.String("target_failover.on_unhealthy"), - Value: aws.String(failover["on_unhealthy"].(string)), - }, - ) - } - } - case elbv2.TargetTypeEnumLambda: if d.HasChange("lambda_multi_value_headers_enabled") { attributes = append(attributes, &elbv2.TargetGroupAttribute{ @@ -1109,7 +1077,7 @@ func flattenTargetGroupStickinessAttributes(apiObjects []*elbv2.TargetGroupAttri return tfMap } -func expandTargetGroupHealthStateAttributes(tfMap map[string]interface{}, protocol string) []*elbv2.TargetGroupAttribute { +func expandTargetGroupTargetFailoverAttributes(tfMap map[string]interface{}, protocol string) []*elbv2.TargetGroupAttribute { if tfMap == nil { return nil } @@ -1117,18 +1085,22 @@ func expandTargetGroupHealthStateAttributes(tfMap map[string]interface{}, protoc var apiObjects []*elbv2.TargetGroupAttribute switch protocol { - case elbv2.ProtocolEnumTcp, elbv2.ProtocolEnumTls: + case elbv2.ProtocolEnumGeneve: apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ - Key: aws.String(targetGroupAttributeTargetHealthStateUnhealthyConnectionTerminationEnabled), - Value: flex.BoolValueToString(tfMap["enable_unhealthy_connection_termination"].(bool)), + Key: aws.String(targetGroupAttributeTargetFailoverOnDeregistration), + Value: aws.String(tfMap["on_deregistration"].(string)), + }, + &elbv2.TargetGroupAttribute{ + Key: aws.String(targetGroupAttributeTargetFailoverOnUnhealthy), + Value: aws.String(tfMap["on_unhealthy"].(string)), }) } return apiObjects } -func flattenTargetGroupHealthStateAttributes(apiObjects []*elbv2.TargetGroupAttribute, protocol string) map[string]interface{} { +func flattenTargetGroupTargetFailoverAttributes(apiObjects []*elbv2.TargetGroupAttribute, protocol string) map[string]interface{} { if len(apiObjects) == 0 { return nil } @@ -1136,11 +1108,13 @@ func flattenTargetGroupHealthStateAttributes(apiObjects []*elbv2.TargetGroupAttr tfMap := map[string]interface{}{} switch protocol { - case elbv2.ProtocolEnumTcp, elbv2.ProtocolEnumTls: + case elbv2.ProtocolEnumGeneve: for _, apiObject := range apiObjects { switch k, v := aws.StringValue(apiObject.Key), apiObject.Value; k { - case targetGroupAttributeTargetHealthStateUnhealthyConnectionTerminationEnabled: - tfMap["enable_unhealthy_connection_termination"] = flex.StringToBoolValue(v) + case targetGroupAttributeTargetFailoverOnDeregistration: + tfMap["on_deregistration"] = aws.StringValue(v) + case targetGroupAttributeTargetFailoverOnUnhealthy: + tfMap["on_unhealthy"] = aws.StringValue(v) } } } @@ -1148,23 +1122,43 @@ func flattenTargetGroupHealthStateAttributes(apiObjects []*elbv2.TargetGroupAttr return tfMap } -func flattenTargetGroupFailover(attributes []*elbv2.TargetGroupAttribute) []interface{} { - if len(attributes) == 0 { - return []interface{}{} +func expandTargetGroupTargetHealthStateAttributes(tfMap map[string]interface{}, protocol string) []*elbv2.TargetGroupAttribute { + if tfMap == nil { + return nil } - m := make(map[string]interface{}) + var apiObjects []*elbv2.TargetGroupAttribute - for _, attr := range attributes { - switch aws.StringValue(attr.Key) { - case "target_failover.on_deregistration": - m["on_deregistration"] = aws.StringValue(attr.Value) - case "target_failover.on_unhealthy": - m["on_unhealthy"] = aws.StringValue(attr.Value) + switch protocol { + case elbv2.ProtocolEnumTcp, elbv2.ProtocolEnumTls: + apiObjects = append(apiObjects, + &elbv2.TargetGroupAttribute{ + Key: aws.String(targetGroupAttributeTargetHealthStateUnhealthyConnectionTerminationEnabled), + Value: flex.BoolValueToString(tfMap["enable_unhealthy_connection_termination"].(bool)), + }) + } + + return apiObjects +} + +func flattenTargetGroupTargetHealthStateAttributes(apiObjects []*elbv2.TargetGroupAttribute, protocol string) map[string]interface{} { + if len(apiObjects) == 0 { + return nil + } + + tfMap := map[string]interface{}{} + + switch protocol { + case elbv2.ProtocolEnumTcp, elbv2.ProtocolEnumTls: + for _, apiObject := range apiObjects { + switch k, v := aws.StringValue(apiObject.Key), apiObject.Value; k { + case targetGroupAttributeTargetHealthStateUnhealthyConnectionTerminationEnabled: + tfMap["enable_unhealthy_connection_termination"] = flex.StringToBoolValue(v) + } } } - return []interface{}{m} + return tfMap } func resourceTargetGroupCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, meta any) error { From d04f929845e551669413f1a9aed54bea0822a354 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 11:56:36 -0500 Subject: [PATCH 18/31] Add 'flex.Int64ValueToString'. --- internal/flex/flex.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/flex/flex.go b/internal/flex/flex.go index 34c417583b10..171c2e120bc7 100644 --- a/internal/flex/flex.go +++ b/internal/flex/flex.go @@ -305,6 +305,11 @@ func IntValueToString(v int) *string { return aws.String(strconv.Itoa(v)) } +// Int64ValueToString converts a Go int64 value to a string pointer. +func Int64ValueToString(v int64) *string { + return aws.String(strconv.FormatInt(v, 10)) +} + // StringToIntValue converts a string pointer to a Go int value. // Invalid integer strings are converted to 0. func StringToIntValue(v *string) int { From 2ab3f1371ef67b8649e4ee5702c279a2d6cf450c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 12:04:20 -0500 Subject: [PATCH 19/31] r/aws_lb_target_group: Add and use 'targetGroupAttributeMap'. --- internal/service/elbv2/target_group.go | 321 +++++++++++++------------ 1 file changed, 165 insertions(+), 156 deletions(-) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 6418067aba77..d523f6edfd99 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -31,6 +31,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/types/nullable" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" + "golang.org/x/exp/slices" ) // @SDKResource("aws_alb_target_group", name="Target Group") @@ -466,64 +467,10 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta 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)...) } - - if v, null, _ := nullable.Int(d.Get("deregistration_delay").(string)).Value(); !null { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("deregistration_delay.timeout_seconds"), - Value: aws.String(fmt.Sprintf("%d", v)), - }) - } - - if v, ok := d.GetOk("load_balancing_algorithm_type"); ok { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("load_balancing.algorithm.type"), - Value: aws.String(v.(string)), - }) - } - - if v, ok := d.GetOk("load_balancing_cross_zone_enabled"); ok { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("load_balancing.cross_zone.enabled"), - Value: aws.String(v.(string)), - }) - } - - if v, ok := d.GetOk("preserve_client_ip"); ok { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("preserve_client_ip.enabled"), - Value: aws.String(v.(string)), - }) - } - - if v, ok := d.GetOk("proxy_protocol_v2"); ok { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("proxy_protocol_v2.enabled"), - Value: aws.String(strconv.FormatBool(v.(bool))), - }) - } - - if v, ok := d.GetOk("connection_termination"); ok { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("deregistration_delay.connection_termination.enabled"), - Value: aws.String(strconv.FormatBool(v.(bool))), - }) - } - - if v, ok := d.GetOk("slow_start"); ok { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("slow_start.duration_seconds"), - Value: aws.String(fmt.Sprintf("%d", v.(int))), - }) - } - case elbv2.TargetTypeEnumLambda: - if v, ok := d.GetOk("lambda_multi_value_headers_enabled"); ok { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("lambda.multi_value_headers.enabled"), - Value: aws.String(strconv.FormatBool(v.(bool))), - }) - } } + attributes = append(attributes, targetGroupAttributes.expand(d, targetType, false)...) + if len(attributes) > 0 { input := &elbv2.ModifyTargetGroupAttributesInput{ Attributes: attributes, @@ -619,48 +566,7 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i return sdkdiag.AppendErrorf(diags, "setting target_health_state: %s", err) } - for _, attr := range attributes { - switch aws.StringValue(attr.Key) { - case "deregistration_delay.timeout_seconds": - d.Set("deregistration_delay", attr.Value) - case "lambda.multi_value_headers.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting lambda.multi_value_headers.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("lambda_multi_value_headers_enabled", enabled) - case "proxy_protocol_v2.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting proxy_protocol_v2.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("proxy_protocol_v2", enabled) - case "deregistration_delay.connection_termination.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting deregistration_delay.connection_termination.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("connection_termination", enabled) - case "slow_start.duration_seconds": - slowStart, err := strconv.Atoi(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting slow_start.duration_seconds to int: %s", aws.StringValue(attr.Value)) - } - d.Set("slow_start", slowStart) - case "load_balancing.algorithm.type": - loadBalancingAlgorithm := aws.StringValue(attr.Value) - d.Set("load_balancing_algorithm_type", loadBalancingAlgorithm) - case "load_balancing.cross_zone.enabled": - loadBalancingCrossZoneEnabled := aws.StringValue(attr.Value) - d.Set("load_balancing_cross_zone_enabled", loadBalancingCrossZoneEnabled) - case "preserve_client_ip.enabled": - _, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting preserve_client_ip.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("preserve_client_ip", attr.Value) - } - } + targetGroupAttributes.flatten(d, targetType, attributes) return diags } @@ -743,66 +649,10 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta attributes = append(attributes, expandTargetGroupTargetHealthStateAttributes(v.([]interface{})[0].(map[string]interface{}), protocol)...) } } - - if d.HasChange("deregistration_delay") { - if v, null, _ := nullable.Int(d.Get("deregistration_delay").(string)).Value(); !null { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("deregistration_delay.timeout_seconds"), - Value: aws.String(fmt.Sprintf("%d", v)), - }) - } - } - - if d.HasChange("slow_start") { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("slow_start.duration_seconds"), - Value: aws.String(fmt.Sprintf("%d", d.Get("slow_start").(int))), - }) - } - - if d.HasChange("proxy_protocol_v2") { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("proxy_protocol_v2.enabled"), - Value: aws.String(strconv.FormatBool(d.Get("proxy_protocol_v2").(bool))), - }) - } - - if d.HasChange("connection_termination") { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("deregistration_delay.connection_termination.enabled"), - Value: aws.String(strconv.FormatBool(d.Get("connection_termination").(bool))), - }) - } - - if d.HasChange("preserve_client_ip") { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("preserve_client_ip.enabled"), - Value: aws.String(d.Get("preserve_client_ip").(string)), - }) - } - - if d.HasChange("load_balancing_algorithm_type") { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("load_balancing.algorithm.type"), - Value: aws.String(d.Get("load_balancing_algorithm_type").(string)), - }) - } - - if d.HasChange("load_balancing_cross_zone_enabled") { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("load_balancing.cross_zone.enabled"), - Value: aws.String(d.Get("load_balancing_cross_zone_enabled").(string)), - }) - } - case elbv2.TargetTypeEnumLambda: - if d.HasChange("lambda_multi_value_headers_enabled") { - attributes = append(attributes, &elbv2.TargetGroupAttribute{ - Key: aws.String("lambda.multi_value_headers.enabled"), - Value: aws.String(strconv.FormatBool(d.Get("lambda_multi_value_headers_enabled").(bool))), - }) - } } + attributes = append(attributes, targetGroupAttributes.expand(d, targetType, true)...) + if len(attributes) > 0 { input := &elbv2.ModifyTargetGroupAttributesInput{ Attributes: attributes, @@ -837,6 +687,165 @@ func resourceTargetGroupDelete(ctx context.Context, d *schema.ResourceData, meta return diags } +type targetGroupAttributeInfo struct { + apiAttributeKey string + tfType schema.ValueType + tfNullableType schema.ValueType + targetTypesSupported []string +} + +type targetGroupAttributeMap map[string]targetGroupAttributeInfo + +var targetGroupAttributes = targetGroupAttributeMap(map[string]targetGroupAttributeInfo{ + "connection_termination": { + apiAttributeKey: targetGroupAttributeDeregistrationDelayConnectionTerminationEnabled, + tfType: schema.TypeBool, + targetTypesSupported: []string{elbv2.TargetTypeEnumInstance, elbv2.TargetTypeEnumIp}, + }, + "deregistration_delay": { + apiAttributeKey: targetGroupAttributeDeregistrationDelayTimeoutSeconds, + tfType: schema.TypeString, + tfNullableType: schema.TypeInt, + targetTypesSupported: []string{elbv2.TargetTypeEnumInstance, elbv2.TargetTypeEnumIp}, + }, + "lambda_multi_value_headers_enabled": { + apiAttributeKey: targetGroupAttributeLambdaMultiValueHeadersEnabled, + tfType: schema.TypeBool, + targetTypesSupported: []string{elbv2.TargetTypeEnumLambda}, + }, + "load_balancing_algorithm_type": { + apiAttributeKey: targetGroupAttributeLoadBalancingAlgorithmType, + tfType: schema.TypeString, + targetTypesSupported: []string{elbv2.TargetTypeEnumInstance, elbv2.TargetTypeEnumIp}, + }, + "load_balancing_cross_zone_enabled": { + apiAttributeKey: targetGroupAttributeLoadBalancingCrossZoneEnabled, + tfType: schema.TypeString, + targetTypesSupported: []string{elbv2.TargetTypeEnumInstance, elbv2.TargetTypeEnumIp}, + }, + "preserve_client_ip": { + apiAttributeKey: targetGroupAttributePreserveClientIPEnabled, + tfType: schema.TypeString, + tfNullableType: schema.TypeBool, + targetTypesSupported: []string{elbv2.TargetTypeEnumInstance, elbv2.TargetTypeEnumIp}, + }, + "proxy_protocol_v2": { + apiAttributeKey: targetGroupAttributeProxyProtocolV2Enabled, + tfType: schema.TypeBool, + targetTypesSupported: []string{elbv2.TargetTypeEnumInstance, elbv2.TargetTypeEnumIp}, + }, + "slow_start": { + apiAttributeKey: targetGroupAttributeSlowStartDurationSeconds, + tfType: schema.TypeInt, + targetTypesSupported: []string{elbv2.TargetTypeEnumInstance, elbv2.TargetTypeEnumIp}, + }, +}) + +func (m targetGroupAttributeMap) expand(d *schema.ResourceData, targetType string, update bool) []*elbv2.TargetGroupAttribute { + var apiObjects []*elbv2.TargetGroupAttribute + + for tfAttributeName, attributeInfo := range m { + if update && !d.HasChange(tfAttributeName) { + continue + } + + if !slices.Contains(attributeInfo.targetTypesSupported, targetType) { + continue + } + + switch v, nt, k := d.Get(tfAttributeName), attributeInfo.tfNullableType, aws.String(attributeInfo.apiAttributeKey); nt { + case schema.TypeBool: + v := v.(string) + if v, null, _ := nullable.Bool(v).Value(); !null { + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: flex.BoolValueToString(v), + }) + } + case schema.TypeInt: + v := v.(string) + if v, null, _ := nullable.Int(v).Value(); !null { + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: flex.Int64ValueToString(v), + }) + } + default: + switch attributeInfo.tfType { + case schema.TypeBool: + v := v.(bool) + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: flex.BoolValueToString(v), + }) + case schema.TypeInt: + v := v.(int) + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: flex.IntValueToString(v), + }) + case schema.TypeString: + if v := v.(string); v != "" { + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: aws.String(v), + }) + } + } + } + + switch v, t, k := d.Get(tfAttributeName), attributeInfo.tfType, aws.String(attributeInfo.apiAttributeKey); t { + case schema.TypeBool: + v := v.(bool) + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: flex.BoolValueToString(v), + }) + case schema.TypeInt: + v := v.(int) + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: flex.IntValueToString(v), + }) + case schema.TypeString: + if v := v.(string); v != "" { + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: aws.String(v), + }) + } + } + } + + return apiObjects +} + +func (m targetGroupAttributeMap) flatten(d *schema.ResourceData, targetType string, apiObjects []*elbv2.TargetGroupAttribute) { + for tfAttributeName, attributeInfo := range m { + if !slices.Contains(attributeInfo.targetTypesSupported, targetType) { + continue + } + + k := attributeInfo.apiAttributeKey + i := slices.IndexFunc(apiObjects, func(v *elbv2.TargetGroupAttribute) bool { + return aws.StringValue(v.Key) == k + }) + + if i == -1 { + continue + } + + switch v, t := apiObjects[i].Value, attributeInfo.tfType; t { + case schema.TypeBool: + d.Set(tfAttributeName, flex.StringToBoolValue(v)) + case schema.TypeInt: + d.Set(tfAttributeName, flex.StringToIntValue(v)) + case schema.TypeString: + d.Set(tfAttributeName, v) + } + } +} + func FindTargetGroupByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) (*elbv2.TargetGroup, error) { input := &elbv2.DescribeTargetGroupsInput{ TargetGroupArns: aws.StringSlice([]string{arn}), From 03a55b2bc80e5eeb77347f7a7a5738f16e9fbfc3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 12:30:35 -0500 Subject: [PATCH 20/31] r/aws_lb_target_group: Corrections. --- internal/service/elbv2/target_group.go | 46 ++++++--------------- internal/service/elbv2/target_group_test.go | 4 -- 2 files changed, 13 insertions(+), 37 deletions(-) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index d523f6edfd99..dcfdaed20a92 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -773,19 +773,21 @@ func (m targetGroupAttributeMap) expand(d *schema.ResourceData, targetType strin default: switch attributeInfo.tfType { case schema.TypeBool: - v := v.(bool) - apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ - Key: k, - Value: flex.BoolValueToString(v), - }) + if v := v.(bool); v || update { + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: flex.BoolValueToString(v), + }) + } case schema.TypeInt: - v := v.(int) - apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ - Key: k, - Value: flex.IntValueToString(v), - }) + if v := v.(int); v > 0 || update { + apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ + Key: k, + Value: flex.IntValueToString(v), + }) + } case schema.TypeString: - if v := v.(string); v != "" { + if v := v.(string); v != "" || update { apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ Key: k, Value: aws.String(v), @@ -793,28 +795,6 @@ func (m targetGroupAttributeMap) expand(d *schema.ResourceData, targetType strin } } } - - switch v, t, k := d.Get(tfAttributeName), attributeInfo.tfType, aws.String(attributeInfo.apiAttributeKey); t { - case schema.TypeBool: - v := v.(bool) - apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ - Key: k, - Value: flex.BoolValueToString(v), - }) - case schema.TypeInt: - v := v.(int) - apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ - Key: k, - Value: flex.IntValueToString(v), - }) - case schema.TypeString: - if v := v.(string); v != "" { - apiObjects = append(apiObjects, &elbv2.TargetGroupAttribute{ - Key: k, - Value: aws.String(v), - }) - } - } } return apiObjects diff --git a/internal/service/elbv2/target_group_test.go b/internal/service/elbv2/target_group_test.go index 602b7188380b..14ae03e63d03 100644 --- a/internal/service/elbv2/target_group_test.go +++ b/internal/service/elbv2/target_group_test.go @@ -4083,10 +4083,6 @@ func testAccCheckTargetGroupExists(ctx context.Context, n string, v *elbv2.Targe return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return errors.New("No ELBv2 Target Group ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) output, err := tfelbv2.FindTargetGroupByARN(ctx, conn, rs.Primary.ID) From 8638cb636d9f9c9c8f87be4cb1c56798e6ff1490 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 12:45:05 -0500 Subject: [PATCH 21/31] Add 'verify.StringHasPrefix'. --- internal/verify/validate.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/internal/verify/validate.go b/internal/verify/validate.go index 57a0d9752e21..cf2390c83cdb 100644 --- a/internal/verify/validate.go +++ b/internal/verify/validate.go @@ -460,6 +460,23 @@ func FloatGreaterThan(threshold float64) schema.SchemaValidateFunc { } } +func StringHasPrefix(prefix string) schema.SchemaValidateFunc { + return func(v interface{}, k string) (warnings []string, errors []error) { + s, ok := v.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if !strings.HasPrefix(s, prefix) { + errors = append(errors, fmt.Errorf("expected %s to have prefix %s, got %s", k, prefix, s)) + return + } + + return warnings, errors + } +} + func ValidServicePrincipal(v interface{}, k string) (ws []string, errors []error) { value := v.(string) From fc5f6a0d81aa8c50e980395e84605787be740d34 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 13:41:36 -0500 Subject: [PATCH 22/31] r/aws_lb_target_group: Tidy up 'flattenTargetGroupHealthCheck'. --- internal/service/elbv2/load_balancer.go | 12 +- internal/service/elbv2/target_group.go | 309 +++++++++--------- .../service/elbv2/target_group_data_source.go | 62 +--- 3 files changed, 159 insertions(+), 224 deletions(-) diff --git a/internal/service/elbv2/load_balancer.go b/internal/service/elbv2/load_balancer.go index 849b31031a71..45d7f76ba43a 100644 --- a/internal/service/elbv2/load_balancer.go +++ b/internal/service/elbv2/load_balancer.go @@ -51,9 +51,9 @@ func ResourceLoadBalancer() *schema.Resource { }, CustomizeDiff: customdiff.Sequence( - customizeDiffALB, - customizeDiffNLB, - customizeDiffGWLB, + customizeDiffLoadBalancerALB, + customizeDiffLoadBalancerNLB, + customizeDiffLoadBalancerGWLB, verify.SetTagsDiff, ), @@ -1062,7 +1062,7 @@ func SuffixFromARN(arn *string) string { // cannot have security groups added if none are present, and cannot have // all security groups removed. If the type is 'network' and any of these // conditions are met, mark the diff as a ForceNew operation. -func customizeDiffNLB(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { +func customizeDiffLoadBalancerNLB(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { // The current criteria for determining if the operation should be ForceNew: // - lb of type "network" // - existing resource (id is not "") @@ -1152,7 +1152,7 @@ func customizeDiffNLB(_ context.Context, diff *schema.ResourceDiff, v interface{ return nil } -func customizeDiffALB(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { +func customizeDiffLoadBalancerALB(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { if lbType := diff.Get("load_balancer_type").(string); lbType != elbv2.LoadBalancerTypeEnumApplication { return nil } @@ -1208,7 +1208,7 @@ func customizeDiffALB(_ context.Context, diff *schema.ResourceDiff, v interface{ return nil } -func customizeDiffGWLB(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { +func customizeDiffLoadBalancerGWLB(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { if lbType := diff.Get("load_balancer_type").(string); lbType != elbv2.LoadBalancerTypeEnumGateway { return nil } diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index dcfdaed20a92..0a5007286e80 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -50,8 +50,8 @@ func ResourceTargetGroup() *schema.Resource { CustomizeDiff: customdiff.Sequence( resourceTargetGroupCustomizeDiff, - lambdaTargetHealthCheckProtocolCustomizeDiff, - nonLambdaValidationCustomizeDiff, + customizeDiffTargetGroupTargetTypeLambda, + customizeDiffTargetGroupTargetTypeNotLambda, verify.SetTagsDiff, ), @@ -110,7 +110,7 @@ func ResourceTargetGroup() *schema.Resource { Computed: true, ValidateFunc: validation.All( validation.StringLenBetween(1, 1024), - validTargetGroupHealthCheckPath, + verify.StringHasPrefix("/"), ), }, "port": { @@ -236,10 +236,13 @@ func ResourceTargetGroup() *schema.Resource { Default: false, }, "slow_start": { - Type: schema.TypeInt, - Optional: true, - Default: 0, - ValidateFunc: validateSlowStart, + Type: schema.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.Any( + validation.IntBetween(0, 0), + validation.IntBetween(30, 900), + ), }, "stickiness": { Type: schema.TypeList, @@ -523,16 +526,15 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("arn", targetGroup.TargetGroupArn) d.Set("arn_suffix", TargetGroupSuffixFromARN(targetGroup.TargetGroupArn)) + if err := d.Set("health_check", flattenTargetGroupHealthCheck(targetGroup)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting health_check: %s", err) + } d.Set("ip_address_type", targetGroup.IpAddressType) d.Set("name", targetGroup.TargetGroupName) d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(targetGroup.TargetGroupName))) targetType := aws.StringValue(targetGroup.TargetType) d.Set("target_type", targetType) - if err := d.Set("health_check", flattenLbTargetGroupHealthCheck(targetGroup)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting health_check: %s", err) - } - if _, ok := d.GetOk("port"); targetGroup.Port != nil || ok { d.Set("port", targetGroup.Port) } @@ -934,28 +936,6 @@ func findTargetGroupAttributesByARN(ctx context.Context, conn *elbv2.ELBV2, arn return output.Attributes, nil } -func validTargetGroupHealthCheckPath(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if !strings.HasPrefix(value, "/") { - errors = append(errors, fmt.Errorf( - "%q must begin with a '/' character, got %q", k, value)) - } - return -} - -func validateSlowStart(v interface{}, k string) (ws []string, errors []error) { - value := v.(int) - - // Check if the value is between 30-900 or 0 (seconds). - if value != 0 && !(value >= 30 && value <= 900) { - errors = append(errors, fmt.Errorf( - "%q contains an invalid Slow Start Duration \"%d\". "+ - "Valid intervals are 30-900 or 0 to disable.", - k, value)) - } - return -} - func validTargetGroupHealthCheckPort(v interface{}, k string) (ws []string, errors []error) { value := v.(string) @@ -989,6 +969,139 @@ func TargetGroupSuffixFromARN(arn *string) string { return "" } +func resourceTargetGroupCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, meta any) error { + healthCheck := make(map[string]any) + if healthChecks := diff.Get("health_check").([]interface{}); len(healthChecks) == 1 { + healthCheck = healthChecks[0].(map[string]interface{}) + } + + if p, ok := healthCheck["protocol"].(string); ok && strings.ToUpper(p) == elbv2.ProtocolEnumTcp { + if m := healthCheck["matcher"].(string); m != "" { + return fmt.Errorf("Attribute %q cannot be specified when %q is %q.", + "health_check.matcher", + "health_check.protocol", + elbv2.ProtocolEnumTcp, + ) + } + + if m := healthCheck["path"].(string); m != "" { + return fmt.Errorf("Attribute %q cannot be specified when %q is %q.", + "health_check.path", + "health_check.protocol", + elbv2.ProtocolEnumTcp, + ) + } + } + + protocol := diff.Get("protocol").(string) + + switch protocol { + case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: + if p, ok := healthCheck["protocol"].(string); ok && strings.ToUpper(p) == elbv2.ProtocolEnumTcp { + return fmt.Errorf("Attribute %q cannot have value %q when %q is %q.", + "health_check.protocol", + elbv2.ProtocolEnumTcp, + "protocol", + protocol, + ) + } + } + + if diff.Id() == "" { + return nil + } + + return nil +} + +func customizeDiffTargetGroupTargetTypeLambda(_ context.Context, diff *schema.ResourceDiff, meta any) error { + if diff.Get("target_type").(string) != elbv2.TargetTypeEnumLambda { + return nil + } + + if healthChecks := diff.Get("health_check").([]interface{}); len(healthChecks) == 1 { + healthCheck := healthChecks[0].(map[string]interface{}) + healthCheckProtocol := healthCheck["protocol"].(string) + + if healthCheckProtocol == elbv2.ProtocolEnumTcp { + return fmt.Errorf("Attribute %q cannot have value %q when %q is %q.", + "health_check.protocol", + elbv2.ProtocolEnumTcp, + "target_type", + elbv2.TargetTypeEnumLambda, + ) + } + } + + return nil +} + +func customizeDiffTargetGroupTargetTypeNotLambda(_ context.Context, diff *schema.ResourceDiff, meta any) error { + targetType := diff.Get("target_type").(string) + if targetType == elbv2.TargetTypeEnumLambda { + return nil + } + + config := diff.GetRawConfig() + + if v := config.GetAttr("port"); v.IsKnown() && v.IsNull() { + return fmt.Errorf("Attribute %q must be specified when %q is %q.", + "port", + "target_type", + targetType, + ) + } + + if v := config.GetAttr("protocol"); v.IsKnown() && v.IsNull() { + return fmt.Errorf("Attribute %q must be specified when %q is %q.", + "protocol", + "target_type", + targetType, + ) + } + + if v := config.GetAttr("vpc_id"); v.IsKnown() && v.IsNull() { + return fmt.Errorf("Attribute %q must be specified when %q is %q.", + "vpc_id", + "target_type", + targetType, + ) + } + + return nil +} + +func flattenTargetGroupHealthCheck(apiObject *elbv2.TargetGroup) []interface{} { + if apiObject == nil { + return []interface{}{} + } + + tfMap := map[string]interface{}{ + "enabled": aws.BoolValue(apiObject.HealthCheckEnabled), + "healthy_threshold": int(aws.Int64Value(apiObject.HealthyThresholdCount)), + "interval": int(aws.Int64Value(apiObject.HealthCheckIntervalSeconds)), + "port": aws.StringValue(apiObject.HealthCheckPort), + "protocol": aws.StringValue(apiObject.HealthCheckProtocol), + "timeout": int(aws.Int64Value(apiObject.HealthCheckTimeoutSeconds)), + "unhealthy_threshold": int(aws.Int64Value(apiObject.UnhealthyThresholdCount)), + } + + if v := apiObject.HealthCheckPath; v != nil { + tfMap["path"] = aws.StringValue(v) + } + + if apiObject := apiObject.Matcher; apiObject != nil { + if v := apiObject.HttpCode; v != nil { + tfMap["matcher"] = aws.StringValue(v) + } + if v := apiObject.GrpcCode; v != nil { + tfMap["matcher"] = aws.StringValue(v) + } + } + + return []interface{}{tfMap} +} + func expandTargetGroupStickinessAttributes(tfMap map[string]interface{}, protocol string) []*elbv2.TargetGroupAttribute { if tfMap == nil { return nil @@ -1150,136 +1263,6 @@ func flattenTargetGroupTargetHealthStateAttributes(apiObjects []*elbv2.TargetGro return tfMap } -func resourceTargetGroupCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, meta any) error { - healthCheck := make(map[string]any) - if healthChecks := diff.Get("health_check").([]interface{}); len(healthChecks) == 1 { - healthCheck = healthChecks[0].(map[string]interface{}) - } - - if p, ok := healthCheck["protocol"].(string); ok && strings.ToUpper(p) == elbv2.ProtocolEnumTcp { - if m := healthCheck["matcher"].(string); m != "" { - return fmt.Errorf("Attribute %q cannot be specified when %q is %q.", - "health_check.matcher", - "health_check.protocol", - elbv2.ProtocolEnumTcp, - ) - } - - if m := healthCheck["path"].(string); m != "" { - return fmt.Errorf("Attribute %q cannot be specified when %q is %q.", - "health_check.path", - "health_check.protocol", - elbv2.ProtocolEnumTcp, - ) - } - } - - protocol := diff.Get("protocol").(string) - - switch protocol { - case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: - if p, ok := healthCheck["protocol"].(string); ok && strings.ToUpper(p) == elbv2.ProtocolEnumTcp { - return fmt.Errorf("Attribute %q cannot have value %q when %q is %q.", - "health_check.protocol", - elbv2.ProtocolEnumTcp, - "protocol", - protocol, - ) - } - } - - if diff.Id() == "" { - return nil - } - - return nil -} - -func lambdaTargetHealthCheckProtocolCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, meta any) error { - if diff.Get("target_type").(string) != elbv2.TargetTypeEnumLambda { - return nil - } - - if healthChecks := diff.Get("health_check").([]interface{}); len(healthChecks) == 1 { - healthCheck := healthChecks[0].(map[string]interface{}) - healthCheckProtocol := healthCheck["protocol"].(string) - - if healthCheckProtocol == elbv2.ProtocolEnumTcp { - return fmt.Errorf("Attribute %q cannot have value %q when %q is %q.", - "health_check.protocol", - elbv2.ProtocolEnumTcp, - "target_type", - elbv2.TargetTypeEnumLambda, - ) - } - } - - return nil -} - -func nonLambdaValidationCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, meta any) error { - targetType := diff.Get("target_type").(string) - if targetType == elbv2.TargetTypeEnumLambda { - return nil - } - - config := diff.GetRawConfig() - - if v := config.GetAttr("port"); v.IsKnown() && v.IsNull() { - return fmt.Errorf("Attribute %q must be specified when %q is %q.", - "port", - "target_type", - targetType, - ) - } - - if v := config.GetAttr("protocol"); v.IsKnown() && v.IsNull() { - return fmt.Errorf("Attribute %q must be specified when %q is %q.", - "protocol", - "target_type", - targetType, - ) - } - - if v := config.GetAttr("vpc_id"); v.IsKnown() && v.IsNull() { - return fmt.Errorf("Attribute %q must be specified when %q is %q.", - "vpc_id", - "target_type", - targetType, - ) - } - - return nil -} - -func flattenLbTargetGroupHealthCheck(targetGroup *elbv2.TargetGroup) []interface{} { - if targetGroup == nil { - return []interface{}{} - } - - m := map[string]interface{}{ - "enabled": aws.BoolValue(targetGroup.HealthCheckEnabled), - "healthy_threshold": int(aws.Int64Value(targetGroup.HealthyThresholdCount)), - "interval": int(aws.Int64Value(targetGroup.HealthCheckIntervalSeconds)), - "port": aws.StringValue(targetGroup.HealthCheckPort), - "protocol": aws.StringValue(targetGroup.HealthCheckProtocol), - "timeout": int(aws.Int64Value(targetGroup.HealthCheckTimeoutSeconds)), - "unhealthy_threshold": int(aws.Int64Value(targetGroup.UnhealthyThresholdCount)), - } - - if targetGroup.HealthCheckPath != nil { - m["path"] = aws.StringValue(targetGroup.HealthCheckPath) - } - if targetGroup.Matcher != nil && targetGroup.Matcher.HttpCode != nil { - m["matcher"] = aws.StringValue(targetGroup.Matcher.HttpCode) - } - if targetGroup.Matcher != nil && targetGroup.Matcher.GrpcCode != nil { - m["matcher"] = aws.StringValue(targetGroup.Matcher.GrpcCode) - } - - return []interface{}{m} -} - func pathString(path cty.Path) string { var buf strings.Builder for i, step := range path { diff --git a/internal/service/elbv2/target_group_data_source.go b/internal/service/elbv2/target_group_data_source.go index 85b4dd1b326a..86b78ca2ebb2 100644 --- a/internal/service/elbv2/target_group_data_source.go +++ b/internal/service/elbv2/target_group_data_source.go @@ -6,7 +6,6 @@ package elbv2 import ( "context" "log" - "strconv" "time" "github.com/aws/aws-sdk-go/aws" @@ -220,20 +219,18 @@ func dataSourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta } targetGroup := results[0] - d.SetId(aws.StringValue(targetGroup.TargetGroupArn)) - d.Set("arn", targetGroup.TargetGroupArn) d.Set("arn_suffix", TargetGroupSuffixFromARN(targetGroup.TargetGroupArn)) - d.Set("name", targetGroup.TargetGroupName) - d.Set("target_type", targetGroup.TargetType) - - if err := d.Set("health_check", flattenLbTargetGroupHealthCheck(targetGroup)); err != nil { + if err := d.Set("health_check", flattenTargetGroupHealthCheck(targetGroup)); err != nil { return sdkdiag.AppendErrorf(diags, "setting health_check: %s", err) } + d.Set("name", targetGroup.TargetGroupName) + targetType := aws.StringValue(targetGroup.TargetType) + d.Set("target_type", targetType) var protocol string - if v, _ := d.Get("target_type").(string); v != elbv2.TargetTypeEnumLambda { + if targetType != elbv2.TargetTypeEnumLambda { d.Set("port", targetGroup.Port) protocol = aws.StringValue(targetGroup.Protocol) d.Set("protocol", protocol) @@ -250,57 +247,12 @@ func dataSourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "reading ELBv2 Target Group (%s) attributes: %s", d.Id(), err) } - for _, attr := range attributes { - switch aws.StringValue(attr.Key) { - case "deregistration_delay.connection_termination.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting deregistration_delay.connection_termination.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("connection_termination", enabled) - case "deregistration_delay.timeout_seconds": - timeout, err := strconv.Atoi(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting deregistration_delay.timeout_seconds to int: %s", aws.StringValue(attr.Value)) - } - d.Set("deregistration_delay", timeout) - case "lambda.multi_value_headers.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting lambda.multi_value_headers.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("lambda_multi_value_headers_enabled", enabled) - case "proxy_protocol_v2.enabled": - enabled, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting proxy_protocol_v2.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("proxy_protocol_v2", enabled) - case "slow_start.duration_seconds": - slowStart, err := strconv.Atoi(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting slow_start.duration_seconds to int: %s", aws.StringValue(attr.Value)) - } - d.Set("slow_start", slowStart) - case "load_balancing.algorithm.type": - loadBalancingAlgorithm := aws.StringValue(attr.Value) - d.Set("load_balancing_algorithm_type", loadBalancingAlgorithm) - case "load_balancing.cross_zone.enabled": - loadBalancingCrossZoneEnabled := aws.StringValue(attr.Value) - d.Set("load_balancing_cross_zone_enabled", loadBalancingCrossZoneEnabled) - case "preserve_client_ip.enabled": - _, err := strconv.ParseBool(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "converting preserve_client_ip.enabled to bool: %s", aws.StringValue(attr.Value)) - } - d.Set("preserve_client_ip", attr.Value) - } - } - if err := d.Set("stickiness", []interface{}{flattenTargetGroupStickinessAttributes(attributes, protocol)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting stickiness: %s", err) } + targetGroupAttributes.flatten(d, targetType, attributes) + tags, err := listTags(ctx, conn, d.Id()) if errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { From fddd2056de203818eb9c56d1282cafde07bd24fe Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 13:53:23 -0500 Subject: [PATCH 23/31] d/aws_lb_target_group: Change `deregistration_delay` from `TypeInt` to `TypeString`. --- .changelog/31436.txt | 4 ++++ internal/service/elbv2/target_group_data_source.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.changelog/31436.txt b/.changelog/31436.txt index cd5e71e36c01..f9bc005b5ff7 100644 --- a/.changelog/31436.txt +++ b/.changelog/31436.txt @@ -1,3 +1,7 @@ ```release-note:bug resource/aws_lb_target_group: Fix diff on `stickiness.cookie_name` when `stickiness.type` is `lb_cookie` +``` + +```release-note:bug +data-source/aws_lb_target_group: Change `deregistration_delay` from `TypeInt` to `TypeString` ``` \ No newline at end of file diff --git a/internal/service/elbv2/target_group_data_source.go b/internal/service/elbv2/target_group_data_source.go index 86b78ca2ebb2..f4fc4b882083 100644 --- a/internal/service/elbv2/target_group_data_source.go +++ b/internal/service/elbv2/target_group_data_source.go @@ -44,7 +44,7 @@ func DataSourceTargetGroup() *schema.Resource { Computed: true, }, "deregistration_delay": { - Type: schema.TypeInt, + Type: schema.TypeString, Computed: true, }, "health_check": { From cb79548414329a4d88755a7be85beb225b5ab7e3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 15:29:43 -0500 Subject: [PATCH 24/31] r/aws_lb_target_group_attachment: Alphabetize attributes. --- .../service/elbv2/target_group_attachment.go | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/internal/service/elbv2/target_group_attachment.go b/internal/service/elbv2/target_group_attachment.go index 70433ac3a2b4..13790803de93 100644 --- a/internal/service/elbv2/target_group_attachment.go +++ b/internal/service/elbv2/target_group_attachment.go @@ -30,29 +30,26 @@ func ResourceTargetGroupAttachment() *schema.Resource { DeleteWithoutTimeout: resourceAttachmentDelete, Schema: map[string]*schema.Schema{ + "availability_zone": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + }, "target_group_arn": { Type: schema.TypeString, ForceNew: true, Required: true, }, - "target_id": { Type: schema.TypeString, ForceNew: true, Required: true, }, - "port": { Type: schema.TypeInt, ForceNew: true, Optional: true, }, - - "availability_zone": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - }, }, } } @@ -107,35 +104,6 @@ func resourceAttachmentCreate(ctx context.Context, d *schema.ResourceData, meta return diags } -func resourceAttachmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - - target := &elbv2.TargetDescription{ - Id: aws.String(d.Get("target_id").(string)), - } - - if v, ok := d.GetOk("port"); ok { - target.Port = aws.Int64(int64(v.(int))) - } - - if v, ok := d.GetOk("availability_zone"); ok { - target.AvailabilityZone = aws.String(v.(string)) - } - - params := &elbv2.DeregisterTargetsInput{ - TargetGroupArn: aws.String(d.Get("target_group_arn").(string)), - Targets: []*elbv2.TargetDescription{target}, - } - - _, err := conn.DeregisterTargetsWithContext(ctx, params) - if err != nil && !tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTargetGroupNotFoundException) { - return sdkdiag.AppendErrorf(diags, "deregistering Targets: %s", err) - } - - return diags -} - // resourceAttachmentRead requires all of the fields in order to describe the correct // target, so there is no work to do beyond ensuring that the target and group still exist. func resourceAttachmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -204,3 +172,32 @@ func resourceAttachmentRead(ctx context.Context, d *schema.ResourceData, meta in return diags } + +func resourceAttachmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + + target := &elbv2.TargetDescription{ + Id: aws.String(d.Get("target_id").(string)), + } + + if v, ok := d.GetOk("port"); ok { + target.Port = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("availability_zone"); ok { + target.AvailabilityZone = aws.String(v.(string)) + } + + params := &elbv2.DeregisterTargetsInput{ + TargetGroupArn: aws.String(d.Get("target_group_arn").(string)), + Targets: []*elbv2.TargetDescription{target}, + } + + _, err := conn.DeregisterTargetsWithContext(ctx, params) + if err != nil && !tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTargetGroupNotFoundException) { + return sdkdiag.AppendErrorf(diags, "deregistering Targets: %s", err) + } + + return diags +} From b053503658aa426acae4b125b750b57281e96fae Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 15:35:00 -0500 Subject: [PATCH 25/31] r/aws_lb_target_group_attachment: Tidy up Create. --- .../service/elbv2/target_group_attachment.go | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/internal/service/elbv2/target_group_attachment.go b/internal/service/elbv2/target_group_attachment.go index 13790803de93..d344454236f4 100644 --- a/internal/service/elbv2/target_group_attachment.go +++ b/internal/service/elbv2/target_group_attachment.go @@ -5,7 +5,6 @@ package elbv2 import ( "context" - "fmt" "log" "time" @@ -14,7 +13,6 @@ import ( "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" @@ -58,48 +56,32 @@ func resourceAttachmentCreate(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - target := &elbv2.TargetDescription{ - Id: aws.String(d.Get("target_id").(string)), - } - - if v, ok := d.GetOk("port"); ok { - target.Port = aws.Int64(int64(v.(int))) + targetGroupARN := d.Get("target_group_arn").(string) + input := &elbv2.RegisterTargetsInput{ + TargetGroupArn: aws.String(targetGroupARN), + Targets: []*elbv2.TargetDescription{{ + Id: aws.String(d.Get("target_id").(string)), + }}, } if v, ok := d.GetOk("availability_zone"); ok { - target.AvailabilityZone = aws.String(v.(string)) + input.Targets[0].AvailabilityZone = aws.String(v.(string)) } - params := &elbv2.RegisterTargetsInput{ - TargetGroupArn: aws.String(d.Get("target_group_arn").(string)), - Targets: []*elbv2.TargetDescription{target}, + if v, ok := d.GetOk("port"); ok { + input.Targets[0].Port = aws.Int64(int64(v.(int))) } - log.Printf("[INFO] Registering Target %s with Target Group %s", d.Get("target_id").(string), - d.Get("target_group_arn").(string)) - - err := retry.RetryContext(ctx, 10*time.Minute, func() *retry.RetryError { - _, err := conn.RegisterTargetsWithContext(ctx, params) - - if tfawserr.ErrCodeEquals(err, "InvalidTarget") { - return retry.RetryableError(fmt.Errorf("attaching instance to LB, retrying: %s", err)) - } - - if err != nil { - return retry.NonRetryableError(err) - } + _, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, 10*time.Minute, func() (interface{}, error) { + return conn.RegisterTargetsWithContext(ctx, input) + }, elbv2.ErrCodeInvalidTargetException) - return nil - }) - if tfresource.TimedOut(err) { - _, err = conn.RegisterTargetsWithContext(ctx, params) - } if err != nil { - return sdkdiag.AppendErrorf(diags, "registering targets with target group: %s", err) + return sdkdiag.AppendErrorf(diags, "registering ELBv2 Target Group (%s) target: %s", targetGroupARN, err) } //lintignore:R016 // Allow legacy unstable ID usage in managed resource - d.SetId(id.PrefixedUniqueId(fmt.Sprintf("%s-", d.Get("target_group_arn")))) + d.SetId(id.PrefixedUniqueId(targetGroupARN + "-")) return diags } From b80ce96b41d5f3076e189d0bf276498bfd29b4cc Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 16:32:58 -0500 Subject: [PATCH 26/31] r/aws_lb_target_group_attachment: Tidy up Delete. --- .../service/elbv2/target_group_attachment.go | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/internal/service/elbv2/target_group_attachment.go b/internal/service/elbv2/target_group_attachment.go index d344454236f4..429a67252772 100644 --- a/internal/service/elbv2/target_group_attachment.go +++ b/internal/service/elbv2/target_group_attachment.go @@ -159,26 +159,31 @@ func resourceAttachmentDelete(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - target := &elbv2.TargetDescription{ - Id: aws.String(d.Get("target_id").(string)), + targetGroupARN := d.Get("target_group_arn").(string) + input := &elbv2.DeregisterTargetsInput{ + TargetGroupArn: aws.String(targetGroupARN), + Targets: []*elbv2.TargetDescription{{ + Id: aws.String(d.Get("target_id").(string)), + }}, } - if v, ok := d.GetOk("port"); ok { - target.Port = aws.Int64(int64(v.(int))) + if v, ok := d.GetOk("availability_zone"); ok { + input.Targets[0].AvailabilityZone = aws.String(v.(string)) } - if v, ok := d.GetOk("availability_zone"); ok { - target.AvailabilityZone = aws.String(v.(string)) + if v, ok := d.GetOk("port"); ok { + input.Targets[0].Port = aws.Int64(int64(v.(int))) } - params := &elbv2.DeregisterTargetsInput{ - TargetGroupArn: aws.String(d.Get("target_group_arn").(string)), - Targets: []*elbv2.TargetDescription{target}, + log.Printf("[DEBUG] Deleting ELBv2 Target Group Attachment: %s", d.Id()) + _, err := conn.DeregisterTargetsWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTargetGroupNotFoundException) { + return diags } - _, err := conn.DeregisterTargetsWithContext(ctx, params) - if err != nil && !tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTargetGroupNotFoundException) { - return sdkdiag.AppendErrorf(diags, "deregistering Targets: %s", err) + if err != nil { + return sdkdiag.AppendErrorf(diags, "deregistering ELBv2 Target Group (%s) target: %s", targetGroupARN, err) } return diags From 4dcf39cabb2df61e0527a1b9db29fdfbfa867a1b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 16:54:00 -0500 Subject: [PATCH 27/31] r/aws_lb_target_group_attachment: Tidy up Read. --- .../service/elbv2/target_group_attachment.go | 101 ++++++++++-------- 1 file changed, 57 insertions(+), 44 deletions(-) diff --git a/internal/service/elbv2/target_group_attachment.go b/internal/service/elbv2/target_group_attachment.go index 429a67252772..30be210e8dfa 100644 --- a/internal/service/elbv2/target_group_attachment.go +++ b/internal/service/elbv2/target_group_attachment.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" @@ -92,66 +93,47 @@ func resourceAttachmentRead(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - target := &elbv2.TargetDescription{ - Id: aws.String(d.Get("target_id").(string)), - } - - if v, ok := d.GetOk("port"); ok { - target.Port = aws.Int64(int64(v.(int))) + targetGroupARN := d.Get("target_group_arn").(string) + input := &elbv2.DescribeTargetHealthInput{ + TargetGroupArn: aws.String(targetGroupARN), + Targets: []*elbv2.TargetDescription{{ + Id: aws.String(d.Get("target_id").(string)), + }}, } if v, ok := d.GetOk("availability_zone"); ok { - target.AvailabilityZone = aws.String(v.(string)) + input.Targets[0].AvailabilityZone = aws.String(v.(string)) } - resp, err := conn.DescribeTargetHealthWithContext(ctx, &elbv2.DescribeTargetHealthInput{ - TargetGroupArn: aws.String(d.Get("target_group_arn").(string)), - Targets: []*elbv2.TargetDescription{target}, - }) - - if err != nil { - if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTargetGroupNotFoundException) { - log.Printf("[WARN] Target group does not exist, removing target attachment %s", d.Id()) - d.SetId("") - return diags - } - if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeInvalidTargetException) { - log.Printf("[WARN] Target does not exist, removing target attachment %s", d.Id()) - d.SetId("") - return diags - } - return sdkdiag.AppendErrorf(diags, "reading Target Health: %s", err) + if v, ok := d.GetOk("port"); ok { + input.Targets[0].Port = aws.Int64(int64(v.(int))) } - for _, targetDesc := range resp.TargetHealthDescriptions { - if targetDesc == nil || targetDesc.Target == nil { - continue - } + output, err := FindTargetHealthDescription(ctx, conn, input) - if aws.StringValue(targetDesc.Target.Id) == d.Get("target_id").(string) { - // These will catch targets being removed by hand (draining as we plan) or that have been removed for a while - // without trying to re-create ones that are just not in use. For example, a target can be `unused` if the - // target group isnt assigned to anything, a scenario where we don't want to continuously recreate the resource. - if targetDesc.TargetHealth == nil { - continue - } + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELBv2 Target Group Attachment %s not found, removing from state", d.Id()) + d.SetId("") + return diags + } - reason := aws.StringValue(targetDesc.TargetHealth.Reason) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELBv2 Target Group Attachment (%s): %s", d.Id(), err) + } - if reason == elbv2.TargetHealthReasonEnumTargetNotRegistered || reason == elbv2.TargetHealthReasonEnumTargetDeregistrationInProgress { - log.Printf("[WARN] Target Attachment does not exist, recreating attachment") + // This will catch targets being removed by hand (draining as we plan) or that have been removed for a while + // without trying to re-create ones that are just not in use. For example, a target can be `unused` if the + // target group isnt assigned to anything, a scenario where we don't want to continuously recreate the resource. + if v := output.TargetHealth; v != nil { + if reason := aws.StringValue(v.Reason); reason == elbv2.TargetHealthReasonEnumTargetNotRegistered || reason == elbv2.TargetHealthReasonEnumTargetDeregistrationInProgress { + if !d.IsNewResource() { + log.Printf("[WARN] ELBv2 Target Group Attachment %s not found, removing from state", d.Id()) d.SetId("") return diags } } } - if len(resp.TargetHealthDescriptions) != 1 { - log.Printf("[WARN] Target does not exist, removing target attachment %s", d.Id()) - d.SetId("") - return diags - } - return diags } @@ -188,3 +170,34 @@ func resourceAttachmentDelete(ctx context.Context, d *schema.ResourceData, meta return diags } + +func FindTargetHealthDescription(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTargetHealthInput) (*elbv2.TargetHealthDescription, error) { + output, err := findTargetHealthDescriptions(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSinglePtrResult(output) +} + +func findTargetHealthDescriptions(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTargetHealthInput) ([]*elbv2.TargetHealthDescription, error) { + output, err := conn.DescribeTargetHealthWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeInvalidTargetException, elbv2.ErrCodeTargetGroupNotFoundException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.TargetHealthDescriptions, nil +} From 767fd802893c4d960f87d03d8eeb5f3c49821a26 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 17:21:01 -0500 Subject: [PATCH 28/31] r/aws_lb_target_group_attachment: Tidy up Read. --- .../service/elbv2/target_group_attachment.go | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/internal/service/elbv2/target_group_attachment.go b/internal/service/elbv2/target_group_attachment.go index 30be210e8dfa..d58adbf1a318 100644 --- a/internal/service/elbv2/target_group_attachment.go +++ b/internal/service/elbv2/target_group_attachment.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) @@ -109,7 +110,7 @@ func resourceAttachmentRead(ctx context.Context, d *schema.ResourceData, meta in input.Targets[0].Port = aws.Int64(int64(v.(int))) } - output, err := FindTargetHealthDescription(ctx, conn, input) + _, err := FindTargetHealthDescription(ctx, conn, input) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] ELBv2 Target Group Attachment %s not found, removing from state", d.Id()) @@ -121,19 +122,6 @@ func resourceAttachmentRead(ctx context.Context, d *schema.ResourceData, meta in return sdkdiag.AppendErrorf(diags, "reading ELBv2 Target Group Attachment (%s): %s", d.Id(), err) } - // This will catch targets being removed by hand (draining as we plan) or that have been removed for a while - // without trying to re-create ones that are just not in use. For example, a target can be `unused` if the - // target group isnt assigned to anything, a scenario where we don't want to continuously recreate the resource. - if v := output.TargetHealth; v != nil { - if reason := aws.StringValue(v.Reason); reason == elbv2.TargetHealthReasonEnumTargetNotRegistered || reason == elbv2.TargetHealthReasonEnumTargetDeregistrationInProgress { - if !d.IsNewResource() { - log.Printf("[WARN] ELBv2 Target Group Attachment %s not found, removing from state", d.Id()) - d.SetId("") - return diags - } - } - } - return diags } @@ -172,7 +160,21 @@ func resourceAttachmentDelete(ctx context.Context, d *schema.ResourceData, meta } func FindTargetHealthDescription(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTargetHealthInput) (*elbv2.TargetHealthDescription, error) { - output, err := findTargetHealthDescriptions(ctx, conn, input) + output, err := findTargetHealthDescriptions(ctx, conn, input, func(v *elbv2.TargetHealthDescription) bool { + // This will catch targets being removed by hand (draining as we plan) or that have been removed for a while + // without trying to re-create ones that are just not in use. For example, a target can be `unused` if the + // target group isnt assigned to anything, a scenario where we don't want to continuously recreate the resource. + if v := v.TargetHealth; v != nil { + switch reason := aws.StringValue(v.Reason); reason { + case elbv2.TargetHealthReasonEnumTargetDeregistrationInProgress, elbv2.TargetHealthReasonEnumTargetNotRegistered: + return false + default: + return true + } + } + + return false + }) if err != nil { return nil, err @@ -181,7 +183,9 @@ func FindTargetHealthDescription(ctx context.Context, conn *elbv2.ELBV2, input * return tfresource.AssertSinglePtrResult(output) } -func findTargetHealthDescriptions(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTargetHealthInput) ([]*elbv2.TargetHealthDescription, error) { +func findTargetHealthDescriptions(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTargetHealthInput, filter tfslices.Predicate[*elbv2.TargetHealthDescription]) ([]*elbv2.TargetHealthDescription, error) { + var targetHealthDescriptions []*elbv2.TargetHealthDescription + output, err := conn.DescribeTargetHealthWithContext(ctx, input) if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeInvalidTargetException, elbv2.ErrCodeTargetGroupNotFoundException) { @@ -199,5 +203,11 @@ func findTargetHealthDescriptions(ctx context.Context, conn *elbv2.ELBV2, input return nil, tfresource.NewEmptyResultError(input) } - return output.TargetHealthDescriptions, nil + for _, v := range output.TargetHealthDescriptions { + if v != nil && filter(v) { + targetHealthDescriptions = append(targetHealthDescriptions, v) + } + } + + return targetHealthDescriptions, nil } From f8592d66ad5e102bd778a8a8c467859da00bf13a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 17:30:14 -0500 Subject: [PATCH 29/31] Add 'flex.StringValueToInt64'. --- internal/flex/flex.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/flex/flex.go b/internal/flex/flex.go index 171c2e120bc7..f7e12d1d02b8 100644 --- a/internal/flex/flex.go +++ b/internal/flex/flex.go @@ -317,6 +317,13 @@ func StringToIntValue(v *string) int { return i } +// StringValueToInt64 converts a string to a Go int64 pointer value. +// Invalid integer strings are converted to 0. +func StringValueToInt64(v string) *int64 { + i, _ := strconv.Atoi(v) + return aws.Int64(int64(i)) +} + // Takes a string of resource attributes separated by the ResourceIdSeparator constant // returns the number of parts func ResourceIdPartCount(id string) int { From 06c960ca8678827b5e1e779ae2d31dfe6c367886 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 17:37:59 -0500 Subject: [PATCH 30/31] r/aws_lb_target_group_attachment: Tidy up acceptance tests. --- .../elbv2/target_group_attachment_test.go | 225 ++++++------------ 1 file changed, 73 insertions(+), 152 deletions(-) diff --git a/internal/service/elbv2/target_group_attachment_test.go b/internal/service/elbv2/target_group_attachment_test.go index 7412ac9f716e..521a68497817 100644 --- a/internal/service/elbv2/target_group_attachment_test.go +++ b/internal/service/elbv2/target_group_attachment_test.go @@ -5,24 +5,25 @@ package elbv2_test import ( "context" - "errors" "fmt" - "strconv" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfelbv2 "github.com/hashicorp/terraform-provider-aws/internal/service/elbv2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBV2TargetGroupAttachment_basic(t *testing.T) { ctx := acctest.Context(t) - targetGroupName := fmt.Sprintf("test-target-group-%s", sdkacctest.RandString(10)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_target_group_attachment.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -31,9 +32,9 @@ func TestAccELBV2TargetGroupAttachment_basic(t *testing.T) { CheckDestroy: testAccCheckTargetGroupAttachmentDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTargetGroupAttachmentConfig_idInstance(targetGroupName), + Config: testAccTargetGroupAttachmentConfig_idInstance(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupAttachmentExists(ctx, "aws_lb_target_group_attachment.test"), + testAccCheckTargetGroupAttachmentExists(ctx, resourceName), ), }, }, @@ -42,7 +43,9 @@ func TestAccELBV2TargetGroupAttachment_basic(t *testing.T) { func TestAccELBV2TargetGroupAttachment_disappears(t *testing.T) { ctx := acctest.Context(t) - targetGroupName := fmt.Sprintf("test-target-group-%s", sdkacctest.RandString(10)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_target_group_attachment.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -50,10 +53,10 @@ func TestAccELBV2TargetGroupAttachment_disappears(t *testing.T) { CheckDestroy: testAccCheckTargetGroupAttachmentDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTargetGroupAttachmentConfig_idInstance(targetGroupName), + Config: testAccTargetGroupAttachmentConfig_idInstance(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTargetGroupAttachmentExists(ctx, "aws_lb_target_group_attachment.test"), - testAccCheckTargetGroupAttachmentDisappears(ctx, "aws_lb_target_group_attachment.test"), + testAccCheckTargetGroupAttachmentExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelbv2.ResourceTargetGroup(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -63,7 +66,8 @@ func TestAccELBV2TargetGroupAttachment_disappears(t *testing.T) { func TestAccELBV2TargetGroupAttachment_backwardsCompatibility(t *testing.T) { ctx := acctest.Context(t) - targetGroupName := fmt.Sprintf("test-target-group-%s", sdkacctest.RandString(10)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_alb_target_group_attachment.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -72,9 +76,9 @@ func TestAccELBV2TargetGroupAttachment_backwardsCompatibility(t *testing.T) { CheckDestroy: testAccCheckTargetGroupAttachmentDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTargetGroupAttachmentConfig_backwardsCompatibility(targetGroupName), + Config: testAccTargetGroupAttachmentConfig_backwardsCompatibility(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupAttachmentExists(ctx, "aws_alb_target_group_attachment.test"), + testAccCheckTargetGroupAttachmentExists(ctx, resourceName), ), }, }, @@ -83,7 +87,8 @@ func TestAccELBV2TargetGroupAttachment_backwardsCompatibility(t *testing.T) { func TestAccELBV2TargetGroupAttachment_port(t *testing.T) { ctx := acctest.Context(t) - targetGroupName := fmt.Sprintf("test-target-group-%s", sdkacctest.RandString(10)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_target_group_attachment.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -92,9 +97,9 @@ func TestAccELBV2TargetGroupAttachment_port(t *testing.T) { CheckDestroy: testAccCheckTargetGroupAttachmentDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTargetGroupAttachmentConfig_port(targetGroupName), + Config: testAccTargetGroupAttachmentConfig_port(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupAttachmentExists(ctx, "aws_lb_target_group_attachment.test"), + testAccCheckTargetGroupAttachmentExists(ctx, resourceName), ), }, }, @@ -103,7 +108,8 @@ func TestAccELBV2TargetGroupAttachment_port(t *testing.T) { func TestAccELBV2TargetGroupAttachment_ipAddress(t *testing.T) { ctx := acctest.Context(t) - targetGroupName := fmt.Sprintf("test-target-group-%s", sdkacctest.RandString(10)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_target_group_attachment.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -112,9 +118,9 @@ func TestAccELBV2TargetGroupAttachment_ipAddress(t *testing.T) { CheckDestroy: testAccCheckTargetGroupAttachmentDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTargetGroupAttachmentConfig_idIPAddress(targetGroupName), + Config: testAccTargetGroupAttachmentConfig_idIPAddress(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupAttachmentExists(ctx, "aws_lb_target_group_attachment.test"), + testAccCheckTargetGroupAttachmentExists(ctx, resourceName), ), }, }, @@ -123,7 +129,8 @@ func TestAccELBV2TargetGroupAttachment_ipAddress(t *testing.T) { func TestAccELBV2TargetGroupAttachment_lambda(t *testing.T) { ctx := acctest.Context(t) - targetGroupName := fmt.Sprintf("test-target-group-%s", sdkacctest.RandString(10)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_target_group_attachment.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -132,49 +139,15 @@ func TestAccELBV2TargetGroupAttachment_lambda(t *testing.T) { CheckDestroy: testAccCheckTargetGroupAttachmentDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTargetGroupAttachmentConfig_idLambda(targetGroupName), + Config: testAccTargetGroupAttachmentConfig_idLambda(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupAttachmentExists(ctx, "aws_lb_target_group_attachment.test"), + testAccCheckTargetGroupAttachmentExists(ctx, resourceName), ), }, }, }) } -func testAccCheckTargetGroupAttachmentDisappears(ctx context.Context, n string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Attachment not found: %s", n) - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) - targetGroupArn := rs.Primary.Attributes["target_group_arn"] - - target := &elbv2.TargetDescription{ - Id: aws.String(rs.Primary.Attributes["target_id"]), - } - - _, hasPort := rs.Primary.Attributes["port"] - if hasPort { - port, _ := strconv.Atoi(rs.Primary.Attributes["port"]) - target.Port = aws.Int64(int64(port)) - } - - params := &elbv2.DeregisterTargetsInput{ - TargetGroupArn: aws.String(targetGroupArn), - Targets: []*elbv2.TargetDescription{target}, - } - - _, err := conn.DeregisterTargetsWithContext(ctx, params) - if err != nil && !tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTargetGroupNotFoundException) { - return fmt.Errorf("Error deregistering Targets: %s", err) - } - - return err - } -} - func testAccCheckTargetGroupAttachmentExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -182,37 +155,26 @@ func testAccCheckTargetGroupAttachmentExists(ctx context.Context, n string) reso return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return errors.New("No Target Group Attachment ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) - _, hasPort := rs.Primary.Attributes["port"] - targetGroupArn := rs.Primary.Attributes["target_group_arn"] - - target := &elbv2.TargetDescription{ - Id: aws.String(rs.Primary.Attributes["target_id"]), - } - if hasPort { - port, _ := strconv.Atoi(rs.Primary.Attributes["port"]) - target.Port = aws.Int64(int64(port)) + input := &elbv2.DescribeTargetHealthInput{ + TargetGroupArn: aws.String(rs.Primary.Attributes["target_group_arn"]), + Targets: []*elbv2.TargetDescription{{ + Id: aws.String(rs.Primary.Attributes["target_id"]), + }}, } - describe, err := conn.DescribeTargetHealthWithContext(ctx, &elbv2.DescribeTargetHealthInput{ - TargetGroupArn: aws.String(targetGroupArn), - Targets: []*elbv2.TargetDescription{target}, - }) - - if err != nil { - return err + if v := rs.Primary.Attributes["availability_zone"]; v != "" { + input.Targets[0].AvailabilityZone = aws.String(v) } - if len(describe.TargetHealthDescriptions) != 1 { - return errors.New("Target Group Attachment not found") + if v := rs.Primary.Attributes["port"]; v != "" { + input.Targets[0].Port = flex.StringValueToInt64(v) } - return nil + _, err := tfelbv2.FindTargetHealthDescription(ctx, conn, input) + + return err } } @@ -225,95 +187,54 @@ func testAccCheckTargetGroupAttachmentDestroy(ctx context.Context) resource.Test continue } - _, hasPort := rs.Primary.Attributes["port"] - targetGroupArn := rs.Primary.Attributes["target_group_arn"] + input := &elbv2.DescribeTargetHealthInput{ + TargetGroupArn: aws.String(rs.Primary.Attributes["target_group_arn"]), + Targets: []*elbv2.TargetDescription{{ + Id: aws.String(rs.Primary.Attributes["target_id"]), + }}, + } - target := &elbv2.TargetDescription{ - Id: aws.String(rs.Primary.Attributes["target_id"]), + if v := rs.Primary.Attributes["availability_zone"]; v != "" { + input.Targets[0].AvailabilityZone = aws.String(v) } - if hasPort { - port, _ := strconv.Atoi(rs.Primary.Attributes["port"]) - target.Port = aws.Int64(int64(port)) + + if v := rs.Primary.Attributes["port"]; v != "" { + input.Targets[0].Port = flex.StringValueToInt64(v) } - describe, err := conn.DescribeTargetHealthWithContext(ctx, &elbv2.DescribeTargetHealthInput{ - TargetGroupArn: aws.String(targetGroupArn), - Targets: []*elbv2.TargetDescription{target}, - }) - if err == nil { - if len(describe.TargetHealthDescriptions) != 0 { - return fmt.Errorf("Target Group Attachment %q still exists", rs.Primary.ID) - } + _, err := tfelbv2.FindTargetHealthDescription(ctx, conn, input) + + if tfresource.NotFound(err) { + continue } - // Verify the error - if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTargetGroupNotFoundException) || tfawserr.ErrCodeEquals(err, elbv2.ErrCodeInvalidTargetException) { - return nil - } else { - return fmt.Errorf("Unexpected error checking LB destroyed: %s", err) + if err != nil { + return err } + + return fmt.Errorf("ELBv2 Target Group Attachment %s still exists", rs.Primary.ID) } return nil } } -func testAccTargetGroupAttachmentInstanceBaseConfig() string { - return ` -data "aws_availability_zones" "available" { - # t2.micro instance type is not available in these Availability Zones - exclude_zone_ids = ["usw2-az4"] - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -data "aws_ami" "amzn-ami-minimal-hvm-ebs" { - most_recent = true - owners = ["amazon"] - - filter { - name = "name" - values = ["amzn-ami-minimal-hvm-*"] - } - - filter { - name = "root-device-type" - values = ["ebs"] - } -} - +func testAccTargetGroupAttachmentCongig_baseEC2Instance(rName string) string { + return acctest.ConfigCompose(acctest.ConfigLatestAmazonLinuxHVMEBSAMI(), acctest.ConfigVPCWithSubnets(rName, 1), fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" - subnet_id = aws_subnet.test.id -} - -resource "aws_subnet" "test" { - availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.0.1.0/24" - vpc_id = aws_vpc.test.id - - tags = { - Name = "tf-acc-test-lb-target-group-attachment" - } -} - -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" + subnet_id = aws_subnet.test[0].id tags = { - Name = "tf-acc-test-lb-target-group-attachment" + Name = %[1]q } } -` +`, rName)) } func testAccTargetGroupAttachmentConfig_idInstance(rName string) string { - return testAccTargetGroupAttachmentInstanceBaseConfig() + fmt.Sprintf(` + return acctest.ConfigCompose(testAccTargetGroupAttachmentCongig_baseEC2Instance(rName), fmt.Sprintf(` resource "aws_lb_target_group" "test" { name = %[1]q port = 443 @@ -325,11 +246,11 @@ resource "aws_lb_target_group_attachment" "test" { target_group_arn = aws_lb_target_group.test.arn target_id = aws_instance.test.id } -`, rName) +`, rName)) } func testAccTargetGroupAttachmentConfig_port(rName string) string { - return testAccTargetGroupAttachmentInstanceBaseConfig() + fmt.Sprintf(` + return acctest.ConfigCompose(testAccTargetGroupAttachmentCongig_baseEC2Instance(rName), fmt.Sprintf(` resource "aws_lb_target_group" "test" { name = %[1]q port = 443 @@ -342,11 +263,11 @@ resource "aws_lb_target_group_attachment" "test" { target_id = aws_instance.test.id port = 80 } -`, rName) +`, rName)) } func testAccTargetGroupAttachmentConfig_backwardsCompatibility(rName string) string { - return testAccTargetGroupAttachmentInstanceBaseConfig() + fmt.Sprintf(` + return acctest.ConfigCompose(testAccTargetGroupAttachmentCongig_baseEC2Instance(rName), fmt.Sprintf(` resource "aws_lb_target_group" "test" { name = %[1]q port = 443 @@ -359,11 +280,11 @@ resource "aws_alb_target_group_attachment" "test" { target_id = aws_instance.test.id port = 80 } -`, rName) +`, rName)) } func testAccTargetGroupAttachmentConfig_idIPAddress(rName string) string { - return testAccTargetGroupAttachmentInstanceBaseConfig() + fmt.Sprintf(` + return acctest.ConfigCompose(testAccTargetGroupAttachmentCongig_baseEC2Instance(rName), fmt.Sprintf(` resource "aws_lb_target_group" "test" { name = %[1]q port = 443 @@ -377,7 +298,7 @@ resource "aws_lb_target_group_attachment" "test" { target_group_arn = aws_lb_target_group.test.arn target_id = aws_instance.test.private_ip } -`, rName) +`, rName)) } func testAccTargetGroupAttachmentConfig_idLambda(rName string) string { From f05bbfd6eaa19fd662347211d617abb2897d0aed Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 15 Dec 2023 18:13:46 -0500 Subject: [PATCH 31/31] Fix 'TestAccELBV2TargetGroupAttachment_disappears'. --- internal/service/elbv2/target_group_attachment_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/elbv2/target_group_attachment_test.go b/internal/service/elbv2/target_group_attachment_test.go index 521a68497817..85ad386a05b0 100644 --- a/internal/service/elbv2/target_group_attachment_test.go +++ b/internal/service/elbv2/target_group_attachment_test.go @@ -56,7 +56,7 @@ func TestAccELBV2TargetGroupAttachment_disappears(t *testing.T) { Config: testAccTargetGroupAttachmentConfig_idInstance(rName), Check: resource.ComposeTestCheckFunc( testAccCheckTargetGroupAttachmentExists(ctx, resourceName), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelbv2.ResourceTargetGroup(), resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelbv2.ResourceTargetGroupAttachment(), resourceName), ), ExpectNonEmptyPlan: true, },