diff --git a/.changelog/34488.txt b/.changelog/34488.txt new file mode 100644 index 00000000000..dca4acef3e4 --- /dev/null +++ b/.changelog/34488.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_lb_target_group: Adds plan- and apply-time validation for invalid parameter combinations +``` diff --git a/internal/service/elbv2/export_test.go b/internal/service/elbv2/export_test.go new file mode 100644 index 00000000000..e571212204a --- /dev/null +++ b/internal/service/elbv2/export_test.go @@ -0,0 +1,10 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package elbv2 + +// Exports for use in tests only. +var ( + HealthCheckProtocolEnumValues = healthCheckProtocolEnumValues + ProtocolVersionEnumValues = protocolVersionEnumValues +) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index 3c510e710bb..b873c0bcf4d 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -15,6 +15,7 @@ import ( "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" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" @@ -35,6 +36,22 @@ 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") @@ -49,9 +66,10 @@ func ResourceTargetGroup() *schema.Resource { StateContext: schema.ImportStatePassthroughContext, }, - // NLBs have restrictions on them at this time CustomizeDiff: customdiff.Sequence( resourceTargetGroupCustomizeDiff, + lambdaTargetHealthCheckProtocolCustomizeDiff, + nonLambdaValidationCustomizeDiff, verify.SetTagsDiff, ), @@ -94,9 +112,10 @@ func ResourceTargetGroup() *schema.Resource { ValidateFunc: validation.IntBetween(2, 10), }, "interval": { - Type: schema.TypeInt, - Optional: true, - Default: 30, + Type: schema.TypeInt, + Optional: true, + Default: 30, + ValidateFunc: validation.IntBetween(5, 300), }, "matcher": { Type: schema.TypeString, @@ -104,10 +123,13 @@ func ResourceTargetGroup() *schema.Resource { Optional: true, }, "path": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validTargetGroupHealthCheckPath, + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 1024), + validTargetGroupHealthCheckPath, + ), }, "port": { Type: schema.TypeString, @@ -123,11 +145,7 @@ func ResourceTargetGroup() *schema.Resource { StateFunc: func(v interface{}) string { return strings.ToUpper(v.(string)) }, - ValidateFunc: validation.StringInSlice([]string{ - elbv2.ProtocolEnumHttp, - elbv2.ProtocolEnumHttps, - elbv2.ProtocolEnumTcp, - }, true), + ValidateFunc: validation.StringInSlice(healthCheckProtocolEnumValues(), true), DiffSuppressFunc: suppressIfTargetType(elbv2.TargetTypeEnumLambda), }, "timeout": { @@ -193,10 +211,11 @@ func ResourceTargetGroup() *schema.Resource { ValidateFunc: validTargetGroupNamePrefix, }, "port": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - ValidateFunc: validation.IntBetween(1, 65535), + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 65535), + DiffSuppressFunc: suppressIfTargetType(elbv2.TargetTypeEnumLambda), }, "preserve_client_ip": { Type: nullable.TypeNullableBool, @@ -206,10 +225,11 @@ func ResourceTargetGroup() *schema.Resource { ValidateFunc: nullable.ValidateTypeStringNullableBool, }, "protocol": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice(elbv2.ProtocolEnum_Values(), true), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(elbv2.ProtocolEnum_Values(), true), + DiffSuppressFunc: suppressIfTargetType(elbv2.TargetTypeEnumLambda), }, "protocol_version": { Type: schema.TypeString, @@ -219,12 +239,12 @@ func ResourceTargetGroup() *schema.Resource { StateFunc: func(v interface{}) string { return strings.ToUpper(v.(string)) }, - ValidateFunc: validation.StringInSlice([]string{ - "GRPC", - "HTTP1", - "HTTP2", - }, true), + ValidateFunc: validation.StringInSlice(protocolVersionEnumValues(), true), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // Don't suppress on creation, so that warnings are actually called + if d.Id() == "" { + return false + } if d.Get("target_type").(string) == elbv2.TargetTypeEnumLambda { return true } @@ -337,9 +357,10 @@ func ResourceTargetGroup() *schema.Resource { ValidateFunc: validation.StringInSlice(elbv2.TargetTypeEnum_Values(), false), }, "vpc_id": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: suppressIfTargetType(elbv2.TargetTypeEnumLambda), }, }, } @@ -347,6 +368,10 @@ func ResourceTargetGroup() *schema.Resource { func suppressIfTargetType(t string) schema.SchemaDiffSuppressFunc { return func(k string, old string, new string, d *schema.ResourceData) bool { + // Don't suppress on creation, so that warnings are actually called + if d.Id() == "" { + return false + } return d.Get("target_type").(string) == t } } @@ -370,6 +395,8 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "ELBv2 Target Group (%s) already exists", name) } + runtimeValidations(d, &diags) + input := &elbv2.CreateTargetGroupInput{ Name: aws.String(name), Tags: getTagsIn(ctx), @@ -377,17 +404,6 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta } if d.Get("target_type").(string) != elbv2.TargetTypeEnumLambda { - if _, ok := d.GetOk("port"); !ok { - return sdkdiag.AppendErrorf(diags, "port should be set when target type is %s", d.Get("target_type").(string)) - } - - if _, ok := d.GetOk("protocol"); !ok { - return sdkdiag.AppendErrorf(diags, "protocol should be set when target type is %s", d.Get("target_type").(string)) - } - - if _, ok := d.GetOk("vpc_id"); !ok { - return sdkdiag.AppendErrorf(diags, "vpc_id should be set when target type is %s", d.Get("target_type").(string)) - } input.Port = aws.Int64(int64(d.Get("port").(int))) input.Protocol = aws.String(d.Get("protocol").(string)) switch d.Get("protocol").(string) { @@ -661,6 +677,10 @@ func resourceTargetGroupRead(ctx context.Context, d *schema.ResourceData, meta i return sdkdiag.AppendErrorf(diags, "reading ELBv2 Target Group (%s): %s", d.Id(), err) } + if !d.IsNewResource() { + runtimeValidations(d, &diags) + } + if err := flattenTargetGroupResource(ctx, d, meta, outputRaw.(*elbv2.TargetGroup)); err != nil { return sdkdiag.AppendFromErr(diags, err) } @@ -1001,13 +1021,9 @@ func FindTargetGroup(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.Descri func validTargetGroupHealthCheckPath(v interface{}, k string) (ws []string, errors []error) { value := v.(string) - if len(value) > 1024 { + if !strings.HasPrefix(value, "/") { errors = append(errors, fmt.Errorf( - "%q cannot be longer than 1024 characters: %q", k, value)) - } - if len(value) > 0 && !strings.HasPrefix(value, "/") { - errors = append(errors, fmt.Errorf( - "%q must begin with a '/' character: %q", k, value)) + "%q must begin with a '/' character, got %q", k, value)) } return } @@ -1062,27 +1078,31 @@ func TargetGroupSuffixFromARN(arn *string) string { 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", targetGroup.TargetType) + d.Set("target_type", targetType) if err := d.Set("health_check", flattenLbTargetGroupHealthCheck(targetGroup)); err != nil { return fmt.Errorf("setting health_check: %w", err) } - if v, _ := d.Get("target_type").(string); v != elbv2.TargetTypeEnumLambda { - d.Set("vpc_id", targetGroup.VpcId) + 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) } - - switch d.Get("protocol").(string) { - case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: + 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()), @@ -1245,34 +1265,41 @@ func flattenTargetGroupStickiness(attributes []*elbv2.TargetGroupAttribute) ([]i return []interface{}{m}, nil } -func resourceTargetGroupCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { - protocol := diff.Get("protocol").(string) - - // Network Load Balancers have many special quirks to them. - // See http://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html +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{}) - protocol := healthCheck["protocol"].(string) + healthCheck = healthChecks[0].(map[string]interface{}) + } - if protocol == elbv2.ProtocolEnumTcp { - // Cannot set custom matcher on TCP health checks - if m := healthCheck["matcher"].(string); m != "" { - return fmt.Errorf("%s: health_check.matcher is not supported for target_groups with TCP protocol", diff.Id()) - } - // Cannot set custom path on TCP health checks - if m := healthCheck["path"].(string); m != "" { - return fmt.Errorf("%s: health_check.path is not supported for target_groups with TCP protocol", diff.Id()) - } + 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, + ) } } - if strings.Contains(protocol, elbv2.ProtocolEnumHttp) { - if healthChecks := diff.Get("health_check").([]interface{}); len(healthChecks) == 1 { - healthCheck := healthChecks[0].(map[string]interface{}) - // HTTP(S) Target Groups cannot use TCP health checks - if p := healthCheck["protocol"].(string); strings.ToLower(p) == "tcp" { - return fmt.Errorf("HTTP Target Groups cannot use TCP health checks") - } + 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, + ) } } @@ -1283,6 +1310,63 @@ func resourceTargetGroupCustomizeDiff(_ context.Context, diff *schema.ResourceDi 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{}{} @@ -1310,3 +1394,115 @@ func flattenLbTargetGroupHealthCheck(targetGroup *elbv2.TargetGroup) []interface return []interface{}{m} } + +func pathString(path cty.Path) string { + var buf strings.Builder + for i, step := range path { + switch x := step.(type) { + case cty.GetAttrStep: + if i != 0 { + buf.WriteString(".") + } + buf.WriteString(x.Name) + case cty.IndexStep: + val := x.Key + typ := val.Type() + var s string + switch { + case typ == cty.String: + s = val.AsString() + case typ == cty.Number: + num := val.AsBigFloat() + s = num.String() + default: + s = fmt.Sprintf("", typ.FriendlyName()) + } + buf.WriteString(fmt.Sprintf("[%s]", s)) + default: + if i != 0 { + buf.WriteString(".") + } + buf.WriteString(fmt.Sprintf("", x)) + } + } + return buf.String() +} + +func runtimeValidations(d *schema.ResourceData, diags *diag.Diagnostics) { + targetType := d.Get("target_type").(string) + if targetType == elbv2.TargetTypeEnumLambda { + if _, ok := d.GetOk("protocol"); ok { + path := cty.GetAttrPath("protocol") + *diags = append(*diags, errs.NewAttributeWarningDiagnostic(path, + "Invalid Attribute Combination", + fmt.Sprintf("Attribute %q cannot be specified when %q is %q.\n\nThis will be an error in a future version.", + pathString(path), + "target_type", + elbv2.TargetTypeEnumLambda), + )) + } + + if _, ok := d.GetOk("protocol_version"); ok { + path := cty.GetAttrPath("protocol_version") + *diags = append(*diags, errs.NewAttributeWarningDiagnostic(path, + "Invalid Attribute Combination", + fmt.Sprintf("Attribute %q cannot be specified when %q is %q.\n\nThis will be an error in a future version.", + pathString(path), + "target_type", + elbv2.TargetTypeEnumLambda), + )) + } + + if _, ok := d.GetOk("port"); ok { + path := cty.GetAttrPath("port") + *diags = append(*diags, errs.NewAttributeWarningDiagnostic(path, + "Invalid Attribute Combination", + fmt.Sprintf("Attribute %q cannot be specified when %q is %q.\n\nThis will be an error in a future version.", + pathString(path), + "target_type", + elbv2.TargetTypeEnumLambda), + )) + } + + if _, ok := d.GetOk("vpc_id"); ok { + path := cty.GetAttrPath("vpc_id") + *diags = append(*diags, errs.NewAttributeWarningDiagnostic(path, + "Invalid Attribute Combination", + fmt.Sprintf("Attribute %q cannot be specified when %q is %q.\n\nThis will be an error in a future version.", + pathString(path), + "target_type", + elbv2.TargetTypeEnumLambda), + )) + } + + if healthChecks := d.Get("health_check").([]interface{}); len(healthChecks) == 1 { + healthCheck := healthChecks[0].(map[string]interface{}) + path := cty.GetAttrPath("health_check") + + if healthCheckProtocol := healthCheck["protocol"].(string); healthCheckProtocol != "" { + path := path.GetAttr("protocol") + *diags = append(*diags, errs.NewAttributeWarningDiagnostic(path, + "Invalid Attribute Combination", + fmt.Sprintf("Attribute %q cannot be specified when %q is %q.\n\nThis will be an error in a future version.", pathString(path), "target_type", elbv2.TargetTypeEnumLambda), + )) + } + } + } else { + if _, ok := d.GetOk("protocol_version"); ok { + path := cty.GetAttrPath("protocol_version") + protocol := d.Get("protocol").(string) + switch protocol { + case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps: + // Noop + default: + *diags = append(*diags, errs.NewAttributeWarningDiagnostic(path, + "Invalid Attribute Combination", + fmt.Sprintf("Attribute %q cannot be specified when %q is %q.\n\nThis will be an error in a future version.", + pathString(path), + "protocol", + protocol), + )) + } + } + } +} diff --git a/internal/service/elbv2/target_group_test.go b/internal/service/elbv2/target_group_test.go index f35aeb0ecd4..e1e6fba7ec3 100644 --- a/internal/service/elbv2/target_group_test.go +++ b/internal/service/elbv2/target_group_test.go @@ -813,18 +813,6 @@ func TestAccELBV2TargetGroup_Defaults_network(t *testing.T) { var conf elbv2.TargetGroup rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb_target_group.test" - healthCheckInvalid1 := ` -path = "/health" -interval = 10 -port = 8081 -protocol = "TCP" - ` - healthCheckInvalid2 := ` -interval = 10 -port = 8081 -protocol = "TCP" -matcher = "200" - ` healthCheckValid := ` interval = 10 port = 8081 @@ -838,14 +826,6 @@ timeout = 4 ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckTargetGroupDestroy(ctx), Steps: []resource.TestStep{ - { - Config: testAccTargetGroupConfig_nlbDefaults(rName, healthCheckInvalid1), - ExpectError: regexache.MustCompile("health_check.path is not supported for target_groups with TCP protocol"), - }, - { - Config: testAccTargetGroupConfig_nlbDefaults(rName, healthCheckInvalid2), - ExpectError: regexache.MustCompile("health_check.matcher is not supported for target_groups with TCP protocol"), - }, { Config: testAccTargetGroupConfig_nlbDefaults(rName, healthCheckValid), Check: resource.ComposeAggregateTestCheckFunc( @@ -1993,30 +1973,48 @@ func TestAccELBV2TargetGroup_ALBAlias_lambdaMultiValueHeadersEnabled(t *testing. }) } -func TestAccELBV2TargetGroup_ALBAlias_missingPortProtocolVPC(t *testing.T) { - ctx := acctest.Context(t) - rName := fmt.Sprintf("test-target-group-%s", sdkacctest.RandString(10)) +func TestAccELBV2TargetGroup_ALBAlias_missing(t *testing.T) { + t.Parallel() - 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_albMissingPort(rName), - ExpectError: regexache.MustCompile(`port should be set when target type is`), - }, - { - Config: testAccTargetGroupConfig_albMissingProtocol(rName), - ExpectError: regexache.MustCompile(`protocol should be set when target type is`), - }, - { - Config: testAccTargetGroupConfig_albMissingVPC(rName), - ExpectError: regexache.MustCompile(`vpc_id should be set when target type is`), - }, + testcases := map[string]struct { + config func(string) string + errMessage string + }{ + "Port": { + config: testAccTargetGroupConfig_albMissingPort, + errMessage: `Attribute "port" must be specified when "target_type" is "instance".`, }, - }) + "Protocol": { + config: testAccTargetGroupConfig_albMissingProtocol, + errMessage: `Attribute "protocol" must be specified when "target_type" is "instance".`, + }, + "VPC": { + config: testAccTargetGroupConfig_albMissingVPC, + errMessage: `Attribute "vpc_id" must be specified when "target_type" is "instance".`, + }, + } + + for name, tc := range testcases { //nolint:paralleltest // false positive + tc := tc + + t.Run(name, func(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + 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: tc.config(rName), + ExpectError: regexache.MustCompile(tc.errMessage), + }, + }, + }) + }) + } } func TestAccELBV2TargetGroup_ALBAlias_namePrefix(t *testing.T) { @@ -2344,9 +2342,1605 @@ func TestAccELBV2TargetGroup_ALBAlias_updateStickinessEnabled(t *testing.T) { func TestAccELBV2TargetGroup_targetHealthStateUnhealthyConnectionTermination(t *testing.T) { ctx := acctest.Context(t) - var targetGroup elbv2.TargetGroup - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_lb_target_group.test" + var targetGroup elbv2.TargetGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_target_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TCP", false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "protocol", "TCP"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "false"), + ), + }, + { + Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TCP", true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "protocol", "TCP"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "true"), + ), + }, + { + Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TLS", false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "protocol", "TLS"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "false"), + ), + }, + { + Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TLS", true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "protocol", "TLS"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), + resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "true"), + ), + }, + }, + }) +} + +func TestAccELBV2TargetGroup_Instance_HealthCheck_defaults(t *testing.T) { + t.Parallel() + + const resourceName = "aws_lb_target_group.test" + + testcases := map[string]map[string]struct { + invalidHealthCheckProtocol bool + expectedMatcher string + expectedPath string + expectedTimeout string + }{ + elbv2.ProtocolEnumHttp: { + elbv2.ProtocolEnumHttp: { + expectedMatcher: "200", + expectedPath: "/", + expectedTimeout: "5", + }, + elbv2.ProtocolEnumHttps: { + expectedMatcher: "200", + expectedPath: "/", + expectedTimeout: "5", + }, + elbv2.ProtocolEnumTcp: { + invalidHealthCheckProtocol: true, + }, + }, + elbv2.ProtocolEnumHttps: { + elbv2.ProtocolEnumHttp: { + expectedMatcher: "200", + expectedPath: "/", + expectedTimeout: "5", + }, + elbv2.ProtocolEnumHttps: { + expectedMatcher: "200", + expectedPath: "/", + expectedTimeout: "5", + }, + elbv2.ProtocolEnumTcp: { + invalidHealthCheckProtocol: true, + }, + }, + elbv2.ProtocolEnumTcp: { + elbv2.ProtocolEnumHttp: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "6", + }, + elbv2.ProtocolEnumHttps: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "10", + }, + elbv2.ProtocolEnumTcp: { + expectedMatcher: "", + expectedPath: "", + expectedTimeout: "10", + }, + }, + elbv2.ProtocolEnumTls: { + elbv2.ProtocolEnumHttp: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "6", + }, + elbv2.ProtocolEnumHttps: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "10", + }, + elbv2.ProtocolEnumTcp: { + expectedMatcher: "", + expectedPath: "", + expectedTimeout: "10", + }, + }, + elbv2.ProtocolEnumUdp: { + elbv2.ProtocolEnumHttp: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "6", + }, + elbv2.ProtocolEnumHttps: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "10", + }, + elbv2.ProtocolEnumTcp: { + expectedMatcher: "", + expectedPath: "", + expectedTimeout: "10", + }, + }, + elbv2.ProtocolEnumTcpUdp: { + elbv2.ProtocolEnumHttp: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "6", + }, + elbv2.ProtocolEnumHttps: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "10", + }, + elbv2.ProtocolEnumTcp: { + expectedMatcher: "", + expectedPath: "", + expectedTimeout: "10", + }, + }, + } + + for _, protocol := range elbv2.ProtocolEnum_Values() { + if protocol == elbv2.ProtocolEnumGeneve { + continue + } + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + t.Parallel() + + protocolCase := testcases[protocol] + if protocolCase == nil { + t.Fatalf("missing case for target protocol %q", protocol) + } + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := protocolCase[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + step := resource.TestStep{ + Config: testAccTargetGroupConfig_Instance_HealthCheck_basic(protocol, healthCheckProtocol), + } + if tc.invalidHealthCheckProtocol { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.protocol" cannot have value "%s" when "protocol" is "%s".`, healthCheckProtocol, protocol)) + } else { + step.Check = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumInstance), + resource.TestCheckResourceAttr(resourceName, "protocol", protocol), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.healthy_threshold", "3"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.interval", "30"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", tc.expectedMatcher), + resource.TestCheckResourceAttr(resourceName, "health_check.0.path", tc.expectedPath), + resource.TestCheckResourceAttr(resourceName, "health_check.0.port", "traffic-port"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", healthCheckProtocol), + resource.TestCheckResourceAttr(resourceName, "health_check.0.timeout", tc.expectedTimeout), + resource.TestCheckResourceAttr(resourceName, "health_check.0.unhealthy_threshold", "3"), + ) + } + 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{ + step, + }, + }) + }) + } + }) + } +} + +func TestAccELBV2TargetGroup_Instance_HealthCheck_matcher(t *testing.T) { + t.Parallel() + + const resourceName = "aws_lb_target_group.test" + + testcases := map[string]map[string]struct { + invalidHealthCheckProtocol bool + invalidConfig bool + matcher string + }{ + elbv2.ProtocolEnumHttp: { + elbv2.ProtocolEnumHttp: { + matcher: "200", + }, + elbv2.ProtocolEnumHttps: { + matcher: "200", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "200", + }, + }, + elbv2.ProtocolEnumHttps: { + elbv2.ProtocolEnumHttp: { + matcher: "200", + }, + elbv2.ProtocolEnumHttps: { + matcher: "200", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "200", + }, + }, + elbv2.ProtocolEnumTcp: { + elbv2.ProtocolEnumHttp: { + matcher: "200", + }, + elbv2.ProtocolEnumHttps: { + matcher: "200", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "200", + }, + }, + elbv2.ProtocolEnumTls: { + elbv2.ProtocolEnumHttp: { + matcher: "200", + }, + elbv2.ProtocolEnumHttps: { + matcher: "200", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "200", + }, + }, + elbv2.ProtocolEnumUdp: { + elbv2.ProtocolEnumHttp: { + matcher: "200", + }, + elbv2.ProtocolEnumHttps: { + matcher: "200", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "200", + }, + }, + elbv2.ProtocolEnumTcpUdp: { + elbv2.ProtocolEnumHttp: { + matcher: "200", + }, + elbv2.ProtocolEnumHttps: { + matcher: "200", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "200", + }, + }, + } + + for _, protocol := range elbv2.ProtocolEnum_Values() { + if protocol == elbv2.ProtocolEnumGeneve { + continue + } + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + t.Parallel() + + protocolCase := testcases[protocol] + if protocolCase == nil { + t.Fatalf("missing case for target protocol %q", protocol) + } + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := protocolCase[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + step := resource.TestStep{ + Config: testAccTargetGroupConfig_Instance_HealthCheck_matcher(protocol, healthCheckProtocol, tc.matcher), + } + if tc.invalidHealthCheckProtocol { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.protocol" cannot have value "%s" when "protocol" is "%s".`, healthCheckProtocol, protocol)) + } else if tc.invalidConfig { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.matcher" cannot be specified when "health_check.protocol" is "%s".`, healthCheckProtocol)) + } else { + step.Check = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "protocol", protocol), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", tc.matcher), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", healthCheckProtocol), + ) + } + 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{ + step, + }, + }) + }) + } + }) + } +} + +func TestAccELBV2TargetGroup_Instance_HealthCheck_path(t *testing.T) { + t.Parallel() + + const resourceName = "aws_lb_target_group.test" + + testcases := map[string]map[string]struct { + invalidHealthCheckProtocol bool + invalidConfig bool + path string + }{ + elbv2.ProtocolEnumHttp: { + elbv2.ProtocolEnumHttp: { + path: "/path", + }, + elbv2.ProtocolEnumHttps: { + path: "/path", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + path: "/path", + }, + }, + elbv2.ProtocolEnumHttps: { + elbv2.ProtocolEnumHttp: { + path: "/path", + }, + elbv2.ProtocolEnumHttps: { + path: "/path", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + path: "/path", + }, + }, + elbv2.ProtocolEnumTcp: { + elbv2.ProtocolEnumHttp: { + path: "/path", + }, + elbv2.ProtocolEnumHttps: { + path: "/path", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + path: "/path", + }, + }, + elbv2.ProtocolEnumTls: { + elbv2.ProtocolEnumHttp: { + path: "/path", + }, + elbv2.ProtocolEnumHttps: { + path: "/path", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + path: "/path", + }, + }, + elbv2.ProtocolEnumUdp: { + elbv2.ProtocolEnumHttp: { + path: "/path", + }, + elbv2.ProtocolEnumHttps: { + path: "/path", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + path: "/path", + }, + }, + elbv2.ProtocolEnumTcpUdp: { + elbv2.ProtocolEnumHttp: { + path: "/path", + }, + elbv2.ProtocolEnumHttps: { + path: "/path", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + path: "/path", + }, + }, + } + + for _, protocol := range elbv2.ProtocolEnum_Values() { + if protocol == elbv2.ProtocolEnumGeneve { + continue + } + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + t.Parallel() + + protocolCase := testcases[protocol] + if protocolCase == nil { + t.Fatalf("missing case for target protocol %q", protocol) + } + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := protocolCase[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + step := resource.TestStep{ + Config: testAccTargetGroupConfig_Instance_HealthCheck_path(protocol, healthCheckProtocol, tc.path), + } + if tc.invalidHealthCheckProtocol { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.protocol" cannot have value "%s" when "protocol" is "%s".`, healthCheckProtocol, protocol)) + } else if tc.invalidConfig { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.path" cannot be specified when "health_check.protocol" is "%s".`, healthCheckProtocol)) + } else { + step.Check = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "protocol", protocol), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.path", tc.path), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", healthCheckProtocol), + ) + } + 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{ + step, + }, + }) + }) + } + }) + } +} + +func TestAccELBV2TargetGroup_Instance_HealthCheck_matcherOutOfRange(t *testing.T) { + t.Parallel() + + testcases := map[string]map[string]struct { + invalidHealthCheckProtocol bool + invalidConfig bool + matcher string + validRange string + }{ + elbv2.ProtocolEnumHttp: { + elbv2.ProtocolEnumHttp: { + matcher: "500", + validRange: "200-499", + }, + elbv2.ProtocolEnumHttps: { + matcher: "500", + validRange: "200-499", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "500", + }, + }, + elbv2.ProtocolEnumHttps: { + elbv2.ProtocolEnumHttp: { + matcher: "500", + validRange: "200-499", + }, + elbv2.ProtocolEnumHttps: { + matcher: "500", + validRange: "200-499", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "500", + }, + }, + elbv2.ProtocolEnumTcp: { + elbv2.ProtocolEnumHttp: { + matcher: "600", + validRange: "200-599", + }, + elbv2.ProtocolEnumHttps: { + matcher: "600", + validRange: "200-599", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "600", + }, + }, + elbv2.ProtocolEnumTls: { + elbv2.ProtocolEnumHttp: { + matcher: "600", + validRange: "200-599", + }, + elbv2.ProtocolEnumHttps: { + matcher: "600", + validRange: "200-599", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "600", + }, + }, + elbv2.ProtocolEnumUdp: { + elbv2.ProtocolEnumHttp: { + matcher: "600", + validRange: "200-599", + }, + elbv2.ProtocolEnumHttps: { + matcher: "600", + validRange: "200-599", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "600", + }, + }, + elbv2.ProtocolEnumTcpUdp: { + elbv2.ProtocolEnumHttp: { + matcher: "600", + validRange: "200-599", + }, + elbv2.ProtocolEnumHttps: { + matcher: "600", + validRange: "200-599", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + matcher: "600", + }, + }, + } + + for _, protocol := range elbv2.ProtocolEnum_Values() { + if protocol == elbv2.ProtocolEnumGeneve { + continue + } + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + t.Parallel() + + protocolCase := testcases[protocol] + if protocolCase == nil { + t.Fatalf("missing case for target protocol %q", protocol) + } + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := protocolCase[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + + step := resource.TestStep{ + Config: testAccTargetGroupConfig_Instance_HealthCheck_matcher(protocol, healthCheckProtocol, tc.matcher), + } + if tc.invalidHealthCheckProtocol { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.protocol" cannot have value "%s" when "protocol" is "%s".`, healthCheckProtocol, protocol)) + } else if tc.invalidConfig { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.matcher" cannot be specified when "health_check.protocol" is "%s".`, healthCheckProtocol)) + } else { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`ValidationError: Health check matcher HTTP code '%s' must be within '%s' inclusive`, tc.matcher, tc.validRange)) + } + 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{ + step, + }, + }) + }) + } + }) + } +} + +func TestAccELBV2TargetGroup_Instance_HealthCheckGeneve_defaults(t *testing.T) { + t.Parallel() + + const resourceName = "aws_lb_target_group.test" + + testcases := map[string]struct { + expectedMatcher string + expectedPath string + expectedTimeout string + }{ + elbv2.ProtocolEnumHttp: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "5", + }, + elbv2.ProtocolEnumHttps: { + expectedMatcher: "200-399", + expectedPath: "/", + expectedTimeout: "5", + }, + elbv2.ProtocolEnumTcp: { + expectedMatcher: "", + expectedPath: "", + expectedTimeout: "5", + }, + } + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { //nolint:paralleltest // false positive + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := testcases[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + 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_Instance_HealthCheckGeneve_basic(healthCheckProtocol), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "protocol", elbv2.ProtocolEnumGeneve), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.healthy_threshold", "3"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.interval", "30"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", tc.expectedMatcher), + resource.TestCheckResourceAttr(resourceName, "health_check.0.path", tc.expectedPath), + resource.TestCheckResourceAttr(resourceName, "health_check.0.port", "traffic-port"), // Should be 80 + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", healthCheckProtocol), + resource.TestCheckResourceAttr(resourceName, "health_check.0.timeout", tc.expectedTimeout), + resource.TestCheckResourceAttr(resourceName, "health_check.0.unhealthy_threshold", "3"), + ), + }, + }, + }) + }) + } +} + +func TestAccELBV2TargetGroup_Instance_HealthCheckGRPC_defaults(t *testing.T) { + t.Parallel() + + const resourceName = "aws_lb_target_group.test" + + testcases := map[string]struct { + invalidHealthCheckProtocol bool + expectedMatcher string + expectedPath string + expectedTimeout string + }{ + elbv2.ProtocolEnumHttp: { + expectedMatcher: "12", + expectedPath: "/AWS.ALB/healthcheck", + expectedTimeout: "5", + }, + elbv2.ProtocolEnumHttps: { + expectedMatcher: "12", + expectedPath: "/AWS.ALB/healthcheck", + expectedTimeout: "5", + }, + elbv2.ProtocolEnumTcp: { + invalidHealthCheckProtocol: true, + }, + } + + for _, protocol := range []string{elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps} { + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + t.Parallel() + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := testcases[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + step := resource.TestStep{ + Config: testAccTargetGroupConfig_Instance_HealhCheckGRPC_basic(protocol, healthCheckProtocol), + } + if tc.invalidHealthCheckProtocol { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.protocol" cannot have value "%s" when "protocol" is "%s".`, healthCheckProtocol, protocol)) + } else { + step.Check = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "protocol", protocol), + resource.TestCheckResourceAttr(resourceName, "protocol_version", "GRPC"), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.healthy_threshold", "3"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.interval", "30"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", tc.expectedMatcher), + resource.TestCheckResourceAttr(resourceName, "health_check.0.path", tc.expectedPath), + resource.TestCheckResourceAttr(resourceName, "health_check.0.port", "traffic-port"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", healthCheckProtocol), + resource.TestCheckResourceAttr(resourceName, "health_check.0.timeout", tc.expectedTimeout), + resource.TestCheckResourceAttr(resourceName, "health_check.0.unhealthy_threshold", "3"), + ) + } + 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{ + step, + }, + }) + }) + } + }) + } +} + +func TestAccELBV2TargetGroup_Instance_HealthCheckGRPC_path(t *testing.T) { + t.Parallel() + + const resourceName = "aws_lb_target_group.test" + + testcases := map[string]struct { + invalidHealthCheckProtocol bool + invalidConfig bool + path string + }{ + elbv2.ProtocolEnumHttp: { + path: "/path", + }, + elbv2.ProtocolEnumHttps: { + path: "/path", + }, + elbv2.ProtocolEnumTcp: { + invalidConfig: true, + path: "/path", + }, + } + + for _, protocol := range []string{elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps} { + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + t.Parallel() + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := testcases[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + step := resource.TestStep{ + Config: testAccTargetGroupConfig_Instance_HealhCheckGRPC_path(protocol, healthCheckProtocol, tc.path), + } + if tc.invalidHealthCheckProtocol { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.protocol" cannot have value "%s" when "protocol" is "%s".`, healthCheckProtocol, protocol)) + } else if tc.invalidConfig { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.path" cannot be specified when "health_check.protocol" is "%s".`, healthCheckProtocol)) + } else { + step.Check = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "protocol", protocol), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.path", tc.path), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", healthCheckProtocol), + ) + } + 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{ + step, + }, + }) + }) + } + }) + } +} + +func TestAccELBV2TargetGroup_Instance_HealthCheckGRPC_matcherOutOfRange(t *testing.T) { + t.Parallel() + + testcases := map[string]struct { + invalidHealthCheckProtocol bool + matcher string + }{ + elbv2.ProtocolEnumHttp: { + matcher: "101", + }, + elbv2.ProtocolEnumHttps: { + matcher: "101", + }, + elbv2.ProtocolEnumTcp: { + invalidHealthCheckProtocol: true, + }, + } + + for _, protocol := range []string{elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps} { + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + t.Parallel() + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := testcases[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + + step := resource.TestStep{ + Config: testAccTargetGroupConfig_Instance_HealhCheckGRPC_matcher(protocol, healthCheckProtocol, tc.matcher), + } + if tc.invalidHealthCheckProtocol { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.protocol" cannot have value "%s" when "protocol" is "%s".`, healthCheckProtocol, protocol)) + } else { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`ValidationError: Health check matcher GRPC code '%s' must be within '0-99' inclusive`, tc.matcher)) + } + 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{ + step, + }, + }) + }) + } + }) + } +} + +func TestAccELBV2TargetGroup_Instance_protocolVersion(t *testing.T) { + t.Parallel() + + const resourceName = "aws_lb_target_group.test" + + testcases := map[string]struct { + validConfig bool + }{ + elbv2.ProtocolEnumHttp: { + validConfig: true, + }, + elbv2.ProtocolEnumHttps: { + validConfig: true, + }, + elbv2.ProtocolEnumTcp: { + validConfig: false, + }, + elbv2.ProtocolEnumTls: { + validConfig: false, + }, + elbv2.ProtocolEnumUdp: { + validConfig: false, + }, + elbv2.ProtocolEnumTcpUdp: { + validConfig: false, + }, + } + + for _, protocol := range elbv2.ProtocolEnum_Values() { //nolint:paralleltest // false positive + if protocol == elbv2.ProtocolEnumGeneve { + continue + } + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + protocolCase, ok := testcases[protocol] + if !ok { + t.Fatalf("missing case for target protocol %q", protocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + step := resource.TestStep{ + Config: testAccTargetGroupConfig_Instance_protocolVersion(protocol, "HTTP1"), + } + if protocolCase.validConfig { + step.Check = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumInstance), + resource.TestCheckResourceAttr(resourceName, "protocol_version", "HTTP1"), + ) + } else { + step.Check = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumInstance), + resource.TestCheckResourceAttr(resourceName, "protocol_version", ""), // Should be Null + ) + } + + 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{ + step, + }, + }) + }) + } +} + +func TestAccELBV2TargetGroup_Instance_protocolVersion_MigrateV0(t *testing.T) { + t.Parallel() + + const resourceName = "aws_lb_target_group.test" + + testcases := map[string]struct { + validConfig bool + }{ + elbv2.ProtocolEnumHttp: { + validConfig: true, + }, + elbv2.ProtocolEnumHttps: { + validConfig: true, + }, + elbv2.ProtocolEnumTcp: { + validConfig: false, + }, + elbv2.ProtocolEnumTls: { + validConfig: false, + }, + elbv2.ProtocolEnumUdp: { + validConfig: false, + }, + elbv2.ProtocolEnumTcpUdp: { + validConfig: false, + }, + } + + for _, protocol := range elbv2.ProtocolEnum_Values() { //nolint:paralleltest // false positive + if protocol == elbv2.ProtocolEnumGeneve { + continue + } + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + protocolCase, ok := testcases[protocol] + if !ok { + t.Fatalf("missing case for target protocol %q", protocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + var ( + preCheck resource.TestCheckFunc + postCheck resource.TestCheckFunc + ) + if protocolCase.validConfig { + preCheck = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumInstance), + resource.TestCheckResourceAttr(resourceName, "protocol_version", "HTTP1"), + ) + postCheck = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumInstance), + resource.TestCheckResourceAttr(resourceName, "protocol_version", "HTTP1"), + ) + } else { + preCheck = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumInstance), + resource.TestCheckNoResourceAttr(resourceName, "protocol_version"), + ) + postCheck = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumInstance), + resource.TestCheckNoResourceAttr(resourceName, "protocol_version"), + ) + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: testAccMigrateTest{ + PreviousVersion: "5.25.0", + Config: testAccTargetGroupConfig_Instance_protocolVersion(protocol, "HTTP1"), + PreCheck: preCheck, + PostCheck: postCheck, + }.Steps(), + }) + }) + } +} + +func TestAccELBV2TargetGroup_Lambda_defaults(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + 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_Lambda_basic(), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv4"), + resource.TestCheckNoResourceAttr(resourceName, "port"), + resource.TestCheckNoResourceAttr(resourceName, "protocol"), + resource.TestCheckNoResourceAttr(resourceName, "protocol_version"), + resource.TestCheckNoResourceAttr(resourceName, "vpc_id"), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "false"), + ), + }, + }, + }) +} + +func TestAccELBV2TargetGroup_Lambda_defaults_MigrateV0(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: testAccMigrateTest{ + PreviousVersion: "5.25.0", + Config: testAccTargetGroupConfig_Lambda_basic(), + PreCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv4"), + resource.TestCheckNoResourceAttr(resourceName, "port"), + resource.TestCheckNoResourceAttr(resourceName, "protocol"), + resource.TestCheckNoResourceAttr(resourceName, "protocol_version"), + resource.TestCheckNoResourceAttr(resourceName, "vpc_id"), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "false"), + ), + PostCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "ip_address_type", "ipv4"), + resource.TestCheckNoResourceAttr(resourceName, "port"), + resource.TestCheckNoResourceAttr(resourceName, "protocol"), + resource.TestCheckNoResourceAttr(resourceName, "protocol_version"), + resource.TestCheckNoResourceAttr(resourceName, "vpc_id"), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "false"), + ), + }.Steps(), + }) +} + +func TestAccELBV2TargetGroup_Lambda_vpc(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + 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_Lambda_vpc(), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "vpc_id", ""), // Should be Null + ), + }, + }, + }) +} + +func TestAccELBV2TargetGroup_Lambda_vpc_MigrateV0(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: testAccMigrateTest{ + PreviousVersion: "5.25.0", + Config: testAccTargetGroupConfig_Lambda_vpc(), + PreCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttrPair(resourceName, "vpc_id", "aws_vpc.test", "id"), + ), + PostCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "vpc_id", ""), // Should be Null + ), + }.Steps(), + }) +} + +func TestAccELBV2TargetGroup_Lambda_protocol(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + t.Parallel() + + for _, protocol := range elbv2.ProtocolEnum_Values() { //nolint:paralleltest // false positive + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + 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_Lambda_protocol(protocol), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "protocol", ""), // Should be Null + ), + }, + }, + }) + }) + } +} + +func TestAccELBV2TargetGroup_Lambda_protocol_MigrateV0(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + t.Parallel() + + for _, protocol := range elbv2.ProtocolEnum_Values() { //nolint:paralleltest // false positive + protocol := protocol + + t.Run(protocol, func(t *testing.T) { + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: testAccMigrateTest{ + PreviousVersion: "5.25.0", + Config: testAccTargetGroupConfig_Lambda_protocol(protocol), + PreCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "protocol", protocol), + ), + PostCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "protocol", ""), // Should be Null + ), + }.Steps(), + }) + }) + } +} + +func TestAccELBV2TargetGroup_Lambda_protocolVersion(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + 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_Lambda_protocolVersion("HTTP1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "protocol_version", ""), + ), + }, + }, + }) +} + +func TestAccELBV2TargetGroup_Lambda_protocolVersion_MigrateV0(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: testAccMigrateTest{ + PreviousVersion: "5.25.0", + Config: testAccTargetGroupConfig_Lambda_protocolVersion("GRPC"), + PreCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckNoResourceAttr(resourceName, "protocol_version"), + ), + PostCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckNoResourceAttr(resourceName, "protocol_version"), + ), + }.Steps(), + }) +} + +func TestAccELBV2TargetGroup_Lambda_port(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + 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_Lambda_port("443"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "port", "0"), // Should be Null + ), + }, + }, + }) +} + +func TestAccELBV2TargetGroup_Lambda_port_MigrateV0(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: testAccMigrateTest{ + PreviousVersion: "5.25.0", + Config: testAccTargetGroupConfig_Lambda_port("443"), + PreCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "port", "443"), + ), + PostCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "target_type", elbv2.TargetTypeEnumLambda), + resource.TestCheckResourceAttr(resourceName, "port", "0"), // Should be Null + ), + }.Steps(), + }) +} + +func TestAccELBV2TargetGroup_Lambda_HealthCheck_basic(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + 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_Lambda_HealthCheck_basic(), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.healthy_threshold", "3"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.interval", "40"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", "200"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.path", "/"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.port", ""), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", ""), + resource.TestCheckResourceAttr(resourceName, "health_check.0.timeout", "35"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.unhealthy_threshold", "3"), + ), + }, + }, + }) +} + +func TestAccELBV2TargetGroup_Lambda_HealthCheck_basic_MigrateV0(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: testAccMigrateTest{ + PreviousVersion: "5.25.0", + Config: testAccTargetGroupConfig_Lambda_HealthCheck_basic(), + PreCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.healthy_threshold", "3"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.interval", "40"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", "200"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.path", "/"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.port", ""), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", ""), + resource.TestCheckResourceAttr(resourceName, "health_check.0.timeout", "35"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.unhealthy_threshold", "3"), + ), + PostCheck: resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.healthy_threshold", "3"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.interval", "40"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.matcher", "200"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.path", "/"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.port", ""), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", ""), + resource.TestCheckResourceAttr(resourceName, "health_check.0.timeout", "35"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.unhealthy_threshold", "3"), + ), + }.Steps(), + }) +} + +func TestAccELBV2TargetGroup_Lambda_HealthCheck_protocol(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + t.Parallel() + + testcases := map[string]struct { + invalidHealthCheckProtocol bool + warning bool + }{ + elbv2.ProtocolEnumHttp: { + warning: true, + }, + elbv2.ProtocolEnumHttps: { + warning: true, + }, + elbv2.ProtocolEnumTcp: { + invalidHealthCheckProtocol: true, + }, + } + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { //nolint:paralleltest // false positive + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := testcases[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + step := resource.TestStep{ + Config: testAccTargetGroupConfig_Lambda_HealthCheck_protocol(healthCheckProtocol), + } + if tc.invalidHealthCheckProtocol { + step.ExpectError = regexache.MustCompile(fmt.Sprintf(`Attribute "health_check.protocol" cannot have value %q when "target_type" is "lambda"`, healthCheckProtocol)) + } else if tc.warning { + step.Check = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", ""), + ) + } else { + t.Fatal("invalid test case, one of invalidHealthCheckProtocol or warning must be set") + } + 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{ + step, + }, + }) + }) + } +} + +func TestAccELBV2TargetGroup_Lambda_HealthCheck_protocol_MigrateV0(t *testing.T) { + const resourceName = "aws_lb_target_group.test" + + t.Parallel() + + testcases := map[string]struct { + invalidHealthCheckProtocol bool + warning bool + }{ + elbv2.ProtocolEnumHttp: { + warning: true, + }, + elbv2.ProtocolEnumHttps: { + warning: true, + }, + elbv2.ProtocolEnumTcp: { + invalidHealthCheckProtocol: true, + }, + } + + for _, healthCheckProtocol := range tfelbv2.HealthCheckProtocolEnumValues() { //nolint:paralleltest // false positive + healthCheckProtocol := healthCheckProtocol + + t.Run(healthCheckProtocol, func(t *testing.T) { + tc, ok := testcases[healthCheckProtocol] + if !ok { + t.Fatalf("missing case for health check protocol %q", healthCheckProtocol) + } + + ctx := acctest.Context(t) + var targetGroup elbv2.TargetGroup + + config := testAccTargetGroupConfig_Lambda_HealthCheck_protocol(healthCheckProtocol) + + step := resource.TestStep{ + Config: config, + ExternalProviders: map[string]resource.ExternalProvider{ + "aws": { + Source: "hashicorp/aws", + VersionConstraint: "5.25.0", + }, + }, + } + if tc.invalidHealthCheckProtocol { + // Lambda health checks don't take a protocol, but are effectively an HTTP check. + // So, they return a `matcher` on read. When Terraform validates the diff, the (incorrectly stored) protocol + // `TCP` is checked against the `matcher`, and returns an error. + step.ExpectError = regexache.MustCompile(`health_check.matcher is not supported for target_groups with TCP protocol`) + } else if tc.warning { + step.Check = resource.ComposeAggregateTestCheckFunc( + testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), + resource.TestCheckResourceAttr(resourceName, "health_check.#", "1"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "health_check.0.protocol", ""), + ) + } else { + t.Fatal("invalid test case, one of invalidHealthCheckProtocol or warning must be set") + } + + steps := []resource.TestStep{step} + if tc.warning { + steps = append(steps, resource.TestStep{ + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Config: config, + PlanOnly: true, + }) + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + CheckDestroy: testAccCheckTargetGroupDestroy(ctx), + Steps: steps, + }) + }) + } +} + +func TestAccELBV2TargetGroup_Lambda_HealthCheck_matcherOutOfRange(t *testing.T) { + ctx := acctest.Context(t) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -2355,44 +3949,8 @@ func TestAccELBV2TargetGroup_targetHealthStateUnhealthyConnectionTermination(t * CheckDestroy: testAccCheckTargetGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TCP", false), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), - resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "protocol", "TCP"), - resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), - resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "false"), - ), - }, - { - Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TCP", true), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), - resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "protocol", "TCP"), - resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), - resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "true"), - ), - }, - { - Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TLS", false), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), - resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "protocol", "TLS"), - resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), - resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "false"), - ), - }, - { - Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TLS", true), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup), - resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "protocol", "TLS"), - resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"), - resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "true"), - ), + Config: testAccTargetGroupConfig_Lambda_HealthCheck_matcher("999"), + ExpectError: regexache.MustCompile(fmt.Sprintf(`ValidationError: Health check matcher HTTP code '%s' must be within '200-499' inclusive`, "999")), }, }, }) @@ -2469,6 +4027,44 @@ func testAccCheckTargetGroupRecreated(i, j *elbv2.TargetGroup) resource.TestChec } } +type testAccMigrateTest struct { + // PreviousVersion is a version of the provider previous to the changes to be migrated + PreviousVersion string + + // Config is the configuration to be deployed with the previous version and checked with the updated version + Config string + + // PreCheck is a check function to validate the values prior to migration + PreCheck resource.TestCheckFunc + + PostCheck resource.TestCheckFunc +} + +func (t testAccMigrateTest) Steps() []resource.TestStep { + return []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "aws": { + Source: "hashicorp/aws", + VersionConstraint: t.PreviousVersion, + }, + }, + Config: t.Config, + Check: t.PreCheck, + }, + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Config: t.Config, + PlanOnly: true, + }, + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Config: t.Config, + Check: t.PostCheck, + }, + } +} + func testAccTargetGroupConfig_basic(rName string, deregDelay int) string { return fmt.Sprintf(` resource "aws_lb_target_group" "test" { @@ -3133,6 +4729,13 @@ resource "aws_vpc" "test" { func testAccTargetGroupConfig_stickinessDefault(rName, protocol string) string { return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + name_prefix = "tf-" + port = 25 + protocol = %[2]q + vpc_id = aws_vpc.test.id +} + resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" @@ -3140,13 +4743,6 @@ resource "aws_vpc" "test" { Name = %[1]q } } - -resource "aws_lb_target_group" "test" { - name_prefix = "tf-" - port = 25 - protocol = %[2]q - vpc_id = aws_vpc.test.id -} `, rName, protocol) } @@ -4081,3 +5677,260 @@ resource "aws_vpc" "test" { } }`, rName) } + +func testAccTargetGroupConfig_Instance_HealthCheck_basic(protocol, healthCheckProtocol string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + port = 443 + protocol = %[1]q + vpc_id = aws_vpc.test.id + + target_type = "instance" + + health_check { + protocol = %[2]q + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} +`, protocol, healthCheckProtocol) +} + +func testAccTargetGroupConfig_Instance_HealthCheck_matcher(protocol, healthCheckProtocol, matcher string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + port = 443 + protocol = %[1]q + vpc_id = aws_vpc.test.id + + target_type = "instance" + + health_check { + protocol = %[2]q + matcher = %[3]q + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} +`, protocol, healthCheckProtocol, matcher) +} + +func testAccTargetGroupConfig_Instance_HealthCheck_path(protocol, healthCheckProtocol, matcher string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + port = 443 + protocol = %[1]q + vpc_id = aws_vpc.test.id + + target_type = "instance" + + health_check { + protocol = %[2]q + path = %[3]q + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} +`, protocol, healthCheckProtocol, matcher) +} + +func testAccTargetGroupConfig_Instance_HealthCheckGeneve_basic(healthCheckProtocol string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + port = 6081 + protocol = "GENEVE" + vpc_id = aws_vpc.test.id + + target_type = "instance" + + health_check { + protocol = %[1]q + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} +`, healthCheckProtocol) +} + +func testAccTargetGroupConfig_Instance_HealhCheckGRPC_basic(protocol, healthCheckProtocol string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + port = 443 + protocol = %[1]q + protocol_version = "GRPC" + vpc_id = aws_vpc.test.id + + target_type = "instance" + + health_check { + protocol = %[2]q + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} +`, protocol, healthCheckProtocol) +} + +func testAccTargetGroupConfig_Instance_HealhCheckGRPC_path(protocol, healthCheckProtocol, path string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + port = 443 + protocol = %[1]q + protocol_version = "GRPC" + vpc_id = aws_vpc.test.id + + target_type = "instance" + + health_check { + protocol = %[2]q + path = %[3]q + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} +`, protocol, healthCheckProtocol, path) +} + +func testAccTargetGroupConfig_Instance_HealhCheckGRPC_matcher(protocol, healthCheckProtocol, matcher string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + port = 443 + protocol = %[1]q + protocol_version = "GRPC" + vpc_id = aws_vpc.test.id + + target_type = "instance" + + health_check { + protocol = %[2]q + matcher = %[3]q + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} +`, protocol, healthCheckProtocol, matcher) +} + +func testAccTargetGroupConfig_Instance_protocolVersion(protocol, protocolVersion string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + target_type = "instance" + + port = 443 + protocol = %[1]q + protocol_version = %[2]q + vpc_id = aws_vpc.test.id +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} +`, protocol, protocolVersion) +} + +func testAccTargetGroupConfig_Lambda_basic() string { + return ` +resource "aws_lb_target_group" "test" { + target_type = "lambda" +} +` +} + +func testAccTargetGroupConfig_Lambda_vpc() string { + return ` +resource "aws_lb_target_group" "test" { + target_type = "lambda" + + vpc_id = aws_vpc.test.id +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} +` +} + +func testAccTargetGroupConfig_Lambda_protocol(protocol string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + target_type = "lambda" + + protocol = %[1]q +} +`, protocol) +} + +func testAccTargetGroupConfig_Lambda_protocolVersion(protocolVersion string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + target_type = "lambda" + + protocol_version = %[1]q +} +`, protocolVersion) +} + +func testAccTargetGroupConfig_Lambda_port(port string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + target_type = "lambda" + + port = %[1]q +} +`, port) +} + +func testAccTargetGroupConfig_Lambda_HealthCheck_basic() string { + return ` +resource "aws_lb_target_group" "test" { + target_type = "lambda" + + health_check { + timeout = 35 # The Terraform default (30) is too short for Lambda. + interval = 40 # Must be > timeout + } +} +` +} + +func testAccTargetGroupConfig_Lambda_HealthCheck_protocol(healthCheckProtocol string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + target_type = "lambda" + + health_check { + protocol = %[1]q + timeout = 35 # The Terraform default (30) is too short for Lambda. + interval = 40 # Must be > timeout + } +} +`, healthCheckProtocol) +} + +func testAccTargetGroupConfig_Lambda_HealthCheck_matcher(matcher string) string { + return fmt.Sprintf(` +resource "aws_lb_target_group" "test" { + target_type = "lambda" + + health_check { + matcher = %[1]q + timeout = 35 # The Terraform default (30) is too short for Lambda. + interval = 40 # Must be > timeout + } +} +`, matcher) +} diff --git a/website/docs/r/lb_target_group.html.markdown b/website/docs/r/lb_target_group.html.markdown index 556d6518bd3..0a3b26a4fa0 100644 --- a/website/docs/r/lb_target_group.html.markdown +++ b/website/docs/r/lb_target_group.html.markdown @@ -96,14 +96,19 @@ This resource supports the following arguments: * `port` - (May be required, Forces new resource) Port on which targets receive traffic, unless overridden when registering a specific target. Required when `target_type` is `instance`, `ip` or `alb`. Does not apply when `target_type` is `lambda`. * `preserve_client_ip` - (Optional) Whether client IP preservation is enabled. See [doc](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#client-ip-preservation) for more information. * `protocol_version` - (Optional, Forces new resource) Only applicable when `protocol` is `HTTP` or `HTTPS`. The protocol version. Specify `GRPC` to send requests to targets using gRPC. Specify `HTTP2` to send requests to targets using HTTP/2. The default is `HTTP1`, which sends requests to targets using HTTP/1.1 -* `protocol` - (May be required, Forces new resource) Protocol to use for routing traffic to the targets. Should be one of `GENEVE`, `HTTP`, `HTTPS`, `TCP`, `TCP_UDP`, `TLS`, or `UDP`. Required when `target_type` is `instance`, `ip` or `alb`. Does not apply when `target_type` is `lambda`. +* `protocol` - (May be required, Forces new resource) Protocol to use for routing traffic to the targets. + Should be one of `GENEVE`, `HTTP`, `HTTPS`, `TCP`, `TCP_UDP`, `TLS`, or `UDP`. + Required when `target_type` is `instance`, `ip`, or `alb`. + Does not apply when `target_type` is `lambda`. * `proxy_protocol_v2` - (Optional) Whether to enable support for proxy protocol v2 on Network Load Balancers. See [doc](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#proxy-protocol) for more information. Default is `false`. * `slow_start` - (Optional) Amount time for targets to warm up before the load balancer sends them a full share of requests. The range is 30-900 seconds or 0 to disable. The default value is 0 seconds. * `stickiness` - (Optional, Maximum of 1) Stickiness configuration block. Detailed below. * `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `target_failover` - (Optional) Target failover block. Only applicable for Gateway Load Balancer target groups. See [target_failover](#target_failover) for more information. * `target_health_state` - (Optional) Target health state block. Only applicable for Network Load Balancer target groups when `protocol` is `TCP` or `TLS`. See [target_health_state](#target_health_state) for more information. -* `target_type` - (May be required, Forces new resource) Type of target that you must specify when registering targets with this target group. See [doc](https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html) for supported values. The default is `instance`. +* `target_type` - (Optional, Forces new resource) Type of target that you must specify when registering targets with this target group. + See [doc](https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html) for supported values. + The default is `instance`. Note that you can't specify targets for a target group using both instance IDs and IP addresses. @@ -122,10 +127,24 @@ This resource supports the following arguments: * `enabled` - (Optional) Whether health checks are enabled. Defaults to `true`. * `healthy_threshold` - (Optional) Number of consecutive health check successes required before considering a target healthy. The range is 2-10. Defaults to 3. * `interval` - (Optional) Approximate amount of time, in seconds, between health checks of an individual target. The range is 5-300. For `lambda` target groups, it needs to be greater than the timeout of the underlying `lambda`. Defaults to 30. -* `matcher` (May be required) Response codes to use when checking for a healthy responses from a target. You can specify multiple values (for example, "200,202" for HTTP(s) or "0,12" for GRPC) or a range of values (for example, "200-299" or "0-99"). Required for HTTP/HTTPS/GRPC ALB. Only applies to Application Load Balancers (i.e., HTTP/HTTPS/GRPC) not Network Load Balancers (i.e., TCP). +* `matcher` (Optional) The HTTP or gRPC codes to use when checking for a successful response from a target. + The `health_check.protocol` must be one of `HTTP` or `HTTPS` or the `target_type` must be `lambda`. + Values can be comma-separated individual values (e.g., "200,202") or a range of values (e.g., "200-299"). + * For gRPC-based target groups (i.e., the `protocol` is one of `HTTP` or `HTTPS` and the `protocol_version` is `GRPC`), values can be between `0` and `99`. The default is `12`. + * When used with an Application Load Balancer (i.e., the `protocol` is one of `HTTP` or `HTTPS` and the `protocol_version` is not `GRPC`), values can be between `200` and `499`. The default is `200`. + * When used with a Network Load Balancer (i.e., the `protocol` is one of `TCP`, `TCP_UDP`, `UDP`, or `TLS`), values can be between `200` and `599`. The default is `200-399`. + * When the `target_type` is `lambda`, values can be between `200` and `499`. The default is `200`. * `path` - (May be required) Destination for the health check request. Required for HTTP/HTTPS ALB and HTTP NLB. Only applies to HTTP/HTTPS. -* `port` - (Optional) The port the load balancer uses when performing health checks on targets. Default is traffic-port. -* `protocol` - (Optional) Protocol the load balancer uses when performing health checks on targets. Must be either `TCP`, `HTTP`, or `HTTPS`. The TCP protocol is not supported for health checks if the protocol of the target group is HTTP or HTTPS. Defaults to HTTP. + * For HTTP and HTTPS health checks, the default is `/`. + * For gRPC health checks, the default is `/Amazon Web Services.ALB/healthcheck`. +* `port` - (Optional) The port the load balancer uses when performing health checks on targets. + Valid values are either `traffic-port`, to use the same port as the target group, or a valid port number between `1` and `65536`. + Default is `traffic-port`. +* `protocol` - (Optional) Protocol the load balancer uses when performing health checks on targets. + Must be one of `TCP`, `HTTP`, or `HTTPS`. + The `TCP` protocol is not supported for health checks if the protocol of the target group is `HTTP` or `HTTPS`. + Default is `HTTP`. + Cannot be specified when the `target_type` is `lambda`. * `timeout` - (optional) Amount of time, in seconds, during which no response from a target means a failed health check. The range is 2–120 seconds. For target groups with a protocol of HTTP, the default is 6 seconds. For target groups with a protocol of TCP, TLS or HTTPS, the default is 10 seconds. For target groups with a protocol of GENEVE, the default is 5 seconds. If the target type is lambda, the default is 30 seconds. * `unhealthy_threshold` - (Optional) Number of consecutive health check failures required before considering a target unhealthy. The range is 2-10. Defaults to 3.