diff --git a/.changelog/37964.txt b/.changelog/37964.txt new file mode 100644 index 00000000000..2b83af1eae3 --- /dev/null +++ b/.changelog/37964.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_vpclattice_listener: Support `TLS_PASSTHROUGH` as a valid value for `protocol` +``` \ No newline at end of file diff --git a/internal/service/vpclattice/exports_test.go b/internal/service/vpclattice/exports_test.go index 09446138ae1..333acbf64ab 100644 --- a/internal/service/vpclattice/exports_test.go +++ b/internal/service/vpclattice/exports_test.go @@ -6,6 +6,7 @@ package vpclattice // Exports for use in tests only. var ( FindAccessLogSubscriptionByID = findAccessLogSubscriptionByID + FindListenerByTwoPartKey = findListenerByTwoPartKey FindServiceByID = findServiceByID FindServiceNetworkByID = findServiceNetworkByID FindServiceNetworkServiceAssociationByID = findServiceNetworkServiceAssociationByID @@ -17,6 +18,7 @@ var ( SuppressEquivalentIDOrARN = suppressEquivalentIDOrARN ResourceAccessLogSubscription = resourceAccessLogSubscription + ResourceListener = resourceListener ResourceService = resourceService ResourceServiceNetwork = resourceServiceNetwork ResourceServiceNetworkServiceAssociation = resourceServiceNetworkServiceAssociation diff --git a/internal/service/vpclattice/listener.go b/internal/service/vpclattice/listener.go index a58f42f5270..a8373bdea6e 100644 --- a/internal/service/vpclattice/listener.go +++ b/internal/service/vpclattice/listener.go @@ -5,7 +5,6 @@ package vpclattice import ( "context" - "errors" "fmt" "log" "strings" @@ -20,6 +19,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -27,10 +28,9 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// Function annotations are used for resource registration to the Provider. DO NOT EDIT. // @SDKResource("aws_vpclattice_listener", name="Listener") // @Tags(identifierAttribute="arn") -func ResourceListener() *schema.Resource { +func resourceListener() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceListenerCreate, ReadWithoutTimeout: resourceListenerRead, @@ -137,10 +137,10 @@ func ResourceListener() *schema.Resource { ValidateFunc: validation.IsPortNumber, }, names.AttrProtocol: { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"HTTP", "HTTPS"}, true), + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: enum.Validate[types.ListenerProtocol](), }, "service_arn": { Type: schema.TypeString, @@ -194,14 +194,11 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in } out, err := conn.CreateListener(ctx, in) + if err != nil { return create.AppendDiagError(diags, names.VPCLattice, create.ErrActionCreating, ResNameListener, d.Get(names.AttrName).(string), err) } - if out == nil || out.Arn == nil { - return create.AppendDiagError(diags, names.VPCLattice, create.ErrActionCreating, ResNameListener, d.Get(names.AttrName).(string), errors.New("empty output")) - } - // Id returned by GetListener does not contain required service name // Create a composite ID using service ID and listener ID d.Set("listener_id", out.Id) @@ -225,7 +222,7 @@ func resourceListenerRead(ctx context.Context, d *schema.ResourceData, meta inte serviceId := d.Get("service_identifier").(string) listenerId := d.Get("listener_id").(string) - out, err := findListenerByIdAndServiceId(ctx, conn, listenerId, serviceId) + out, err := findListenerByTwoPartKey(ctx, conn, listenerId, serviceId) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] VPCLattice Listener (%s) not found, removing from state", d.Id()) @@ -286,43 +283,41 @@ func resourceListenerDelete(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).VPCLatticeClient(ctx) - log.Printf("[INFO] Deleting VPC Lattice Listener %s", d.Id()) - serviceId := d.Get("service_identifier").(string) listenerId := d.Get("listener_id").(string) + log.Printf("[INFO] Deleting VPCLattice Listener %s", d.Id()) _, err := conn.DeleteListener(ctx, &vpclattice.DeleteListenerInput{ ListenerIdentifier: aws.String(listenerId), ServiceIdentifier: aws.String(serviceId), }) - if err != nil { - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return diags - } + if errs.IsA[*types.ResourceNotFoundException](err) { + return diags + } + if err != nil { return create.AppendDiagError(diags, names.VPCLattice, create.ErrActionDeleting, ResNameListener, d.Id(), err) } return diags } -func findListenerByIdAndServiceId(ctx context.Context, conn *vpclattice.Client, id string, serviceId string) (*vpclattice.GetListenerOutput, error) { +func findListenerByTwoPartKey(ctx context.Context, conn *vpclattice.Client, listenerID, serviceID string) (*vpclattice.GetListenerOutput, error) { in := &vpclattice.GetListenerInput{ - ListenerIdentifier: aws.String(id), - ServiceIdentifier: aws.String(serviceId), + ListenerIdentifier: aws.String(listenerID), + ServiceIdentifier: aws.String(serviceID), } out, err := conn.GetListener(ctx, in) - if err != nil { - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } + + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, } + } + if err != nil { return nil, err } diff --git a/internal/service/vpclattice/listener_test.go b/internal/service/vpclattice/listener_test.go index 894c7553486..af5611b39f9 100644 --- a/internal/service/vpclattice/listener_test.go +++ b/internal/service/vpclattice/listener_test.go @@ -10,9 +10,7 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/vpclattice" - "github.com/aws/aws-sdk-go-v2/service/vpclattice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -20,6 +18,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" tfvpclattice "github.com/hashicorp/terraform-provider-aws/internal/service/vpclattice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -154,6 +153,47 @@ func TestAccVPCLatticeListener_fixedResponseHTTPS(t *testing.T) { }) } +func TestAccVPCLatticeListener_forwardTLSPassthrough(t *testing.T) { + ctx := acctest.Context(t) + + var listener vpclattice.GetListenerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_listener.test" + serviceName := "aws_vpclattice_service.test" + targetGroupResourceName := "aws_vpclattice_target_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.VPCLatticeEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckListenerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccListenerConfig_forwardTLSPassthrough(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckListenerExists(ctx, resourceName, &listener), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, names.AttrPort, "8443"), + resource.TestCheckResourceAttr(resourceName, names.AttrProtocol, "TLS_PASSTHROUGH"), + resource.TestCheckResourceAttrPair(resourceName, "service_identifier", serviceName, names.AttrID), + resource.TestCheckResourceAttrPair(resourceName, "default_action.0.forward.0.target_groups.0.target_group_identifier", targetGroupResourceName, names.AttrID), + resource.TestCheckResourceAttr(resourceName, "default_action.0.forward.0.target_groups.0.weight", "80"), + acctest.MatchResourceAttrRegionalARN(resourceName, names.AttrARN, "vpc-lattice", regexache.MustCompile(`service\/svc-.*\/listener\/listener-.+`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccVPCLatticeListener_forwardHTTPTargetGroup(t *testing.T) { ctx := acctest.Context(t) @@ -458,19 +498,17 @@ func testAccCheckListenerDestroy(ctx context.Context) resource.TestCheckFunc { continue } - _, err := conn.GetListener(ctx, &vpclattice.GetListenerInput{ - ListenerIdentifier: aws.String(rs.Primary.Attributes["listener_id"]), - ServiceIdentifier: aws.String(rs.Primary.Attributes["service_identifier"]), - }) + _, err := tfvpclattice.FindListenerByTwoPartKey(ctx, conn, rs.Primary.Attributes["listener_id"], rs.Primary.Attributes["service_identifier"]) + + if tfresource.NotFound(err) { + continue + } + if err != nil { - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return nil - } return err } - return create.Error(names.VPCLattice, create.ErrActionCheckingDestroyed, tfvpclattice.ResNameListener, rs.Primary.ID, errors.New("not destroyed")) + return fmt.Errorf("VPC Lattice Listener %s still exists", rs.Primary.ID) } return nil @@ -489,13 +527,10 @@ func testAccCheckListenerExists(ctx context.Context, name string, listener *vpcl } conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient(ctx) - resp, err := conn.GetListener(ctx, &vpclattice.GetListenerInput{ - ListenerIdentifier: aws.String(rs.Primary.Attributes["listener_id"]), - ServiceIdentifier: aws.String(rs.Primary.Attributes["service_identifier"]), - }) + resp, err := tfvpclattice.FindListenerByTwoPartKey(ctx, conn, rs.Primary.Attributes["listener_id"], rs.Primary.Attributes["service_identifier"]) if err != nil { - return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameListener, rs.Primary.ID, err) + return err } *listener = *resp @@ -553,6 +588,42 @@ resource "aws_vpclattice_listener" "test" { `, rName)) } +func testAccListenerConfig_forwardTLSPassthrough(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), fmt.Sprintf(` +resource "aws_vpclattice_service" "test" { + name = %[1]q + auth_type = "AWS_IAM" + custom_domain_name = "example.com" +} + +resource "aws_vpclattice_target_group" "test" { + name = %[1]q + type = "INSTANCE" + + config { + port = 80 + protocol = "TCP" + vpc_identifier = aws_vpc.test.id + } +} + +resource "aws_vpclattice_listener" "test" { + name = %[1]q + protocol = "TLS_PASSTHROUGH" + port = 8443 + service_identifier = aws_vpclattice_service.test.id + default_action { + forward { + target_groups { + target_group_identifier = aws_vpclattice_target_group.test.id + weight = 80 + } + } + } +} +`, rName)) +} + func testAccListenerConfig_forwardMultiTargetGroupHTTP(rName string, targetGroupName1 string) string { return acctest.ConfigCompose(testAccListenerConfig_basic(rName), fmt.Sprintf(` resource "aws_vpclattice_target_group" "test1" { diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index db00c3d9673..1d57c12e072 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -67,7 +67,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka TypeName: "aws_vpclattice_auth_policy", }, { - Factory: ResourceListener, + Factory: resourceListener, TypeName: "aws_vpclattice_listener", Name: "Listener", Tags: &types.ServicePackageResourceTags{ diff --git a/website/docs/r/vpclattice_listener.html.markdown b/website/docs/r/vpclattice_listener.html.markdown index c17668892f0..32736cf9ec6 100644 --- a/website/docs/r/vpclattice_listener.html.markdown +++ b/website/docs/r/vpclattice_listener.html.markdown @@ -118,7 +118,7 @@ This resource supports the following arguments: * `default_action` - (Required) Default action block for the default listener rule. Default action blocks are defined below. * `name` - (Required, Forces new resource) Name of the listener. A listener name must be unique within a service. Valid characters are a-z, 0-9, and hyphens (-). You can't use a hyphen as the first or last character, or immediately after another hyphen. * `port` - (Optional, Forces new resource) Listener port. You can specify a value from 1 to 65535. If `port` is not specified and `protocol` is HTTP, the value will default to 80. If `port` is not specified and `protocol` is HTTPS, the value will default to 443. -* `protocol` - (Required, Forces new resource) Protocol for the listener. Supported values are `HTTP` or `HTTPS` +* `protocol` - (Required, Forces new resource) Protocol for the listener. Supported values are `HTTP`, `HTTPS` or `TLS_PASSTHROUGH` * `service_arn` - (Optional) Amazon Resource Name (ARN) of the VPC Lattice service. You must include either the `service_arn` or `service_identifier` arguments. * `service_identifier` - (Optional) ID of the VPC Lattice service. You must include either the `service_arn` or `service_identifier` arguments. -> **NOTE:** You must specify one of the following arguments: `service_arn` or `service_identifier`.