diff --git a/.changelog/30843.txt b/.changelog/30843.txt new file mode 100644 index 000000000000..9b587984587e --- /dev/null +++ b/.changelog/30843.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +aws_vpclattice_listener +``` \ No newline at end of file diff --git a/internal/service/vpclattice/listener_data_source.go b/internal/service/vpclattice/listener_data_source.go new file mode 100644 index 000000000000..6a0a3ab784bd --- /dev/null +++ b/internal/service/vpclattice/listener_data_source.go @@ -0,0 +1,252 @@ +package vpclattice + +import ( + "context" + "errors" + + "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" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// Function annotations are used for datasource registration to the Provider. DO NOT EDIT. +// @SDKDataSource("aws_vpclattice_listener", name="Listener") +func DataSourceListener() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceListenerRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "default_action": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fixed_response": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "status_code": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "forward": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target_groups": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target_group_identifier": { + Type: schema.TypeString, + Computed: true, + }, + "weight": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "last_updated_at": { + Type: schema.TypeString, + Computed: true, + }, + "listener_id": { + Type: schema.TypeString, + Computed: true, + }, + "listener_identifier": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "port": { + Type: schema.TypeInt, + Computed: true, + }, + "protocol": { + Type: schema.TypeString, + Computed: true, + }, + "service_arn": { + Type: schema.TypeString, + Computed: true, + }, + "service_id": { + Type: schema.TypeString, + Computed: true, + }, + "service_identifier": { + Type: schema.TypeString, + Required: true, + }, + "tags": tftags.TagsSchemaComputed(), + }, + } +} + +const ( + DSNameListener = "Listener Data Source" +) + +func dataSourceListenerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() + + serviceId := d.Get("service_identifier").(string) + listenerId := d.Get("listener_identifier").(string) + + out, err := findListenerByListenerIdAndServiceId(ctx, conn, listenerId, serviceId) + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionReading, DSNameListener, d.Id(), err) + } + + // Set simple arguments + d.SetId(aws.ToString(out.Id)) + d.Set("arn", out.Arn) + d.Set("created_at", aws.ToTime(out.CreatedAt).String()) + d.Set("last_updated_at", aws.ToTime(out.LastUpdatedAt).String()) + d.Set("listener_id", out.Id) + d.Set("name", out.Name) + d.Set("port", out.Port) + d.Set("protocol", out.Protocol) + d.Set("service_arn", out.ServiceArn) + d.Set("service_id", out.ServiceId) + + // Flatten complex default_action attribute - uses flatteners from listener.go + if err := d.Set("default_action", flattenListenerRuleActionsDataSource(out.DefaultAction)); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionSetting, DSNameListener, d.Id(), err) + } + + // Set tags + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tags, err := ListTags(ctx, conn, aws.ToString(out.Arn)) + + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionReading, DSNameListener, d.Id(), err) + } + + //lintignore:AWSR002 + if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionSetting, DSNameListener, d.Id(), err) + } + + return nil +} + +func findListenerByListenerIdAndServiceId(ctx context.Context, conn *vpclattice.Client, listener_id string, service_id string) (*vpclattice.GetListenerOutput, error) { + in := &vpclattice.GetListenerInput{ + ListenerIdentifier: aws.String(listener_id), + ServiceIdentifier: aws.String(service_id), + } + + 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, + } + } + + return nil, err + } + + if out == nil || out.Id == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} + +func flattenListenerRuleActionsDataSource(config types.RuleAction) []interface{} { + m := map[string]interface{}{} + + if config == nil { + return []interface{}{} + } + + switch v := config.(type) { + case *types.RuleActionMemberFixedResponse: + m["fixed_response"] = flattenRuleActionMemberFixedResponseDataSource(&v.Value) + case *types.RuleActionMemberForward: + m["forward"] = flattenComplexDefaultActionForwardDataSource(&v.Value) + } + + return []interface{}{m} +} + +// Flatten function for fixed_response action +func flattenRuleActionMemberFixedResponseDataSource(response *types.FixedResponseAction) []interface{} { + tfMap := map[string]interface{}{} + + if v := response.StatusCode; v != nil { + tfMap["status_code"] = aws.ToInt32(v) + } + + return []interface{}{tfMap} +} + +// Flatten function for forward action +func flattenComplexDefaultActionForwardDataSource(forwardAction *types.ForwardAction) []interface{} { + if forwardAction == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "target_groups": flattenDefaultActionForwardTargetGroupsDataSource(forwardAction.TargetGroups), + } + + return []interface{}{m} +} + +// Flatten function for target_groups +func flattenDefaultActionForwardTargetGroupsDataSource(groups []types.WeightedTargetGroup) []interface{} { + if len(groups) == 0 { + return []interface{}{} + } + + var targetGroups []interface{} + + for _, targetGroup := range groups { + m := map[string]interface{}{ + "target_group_identifier": aws.ToString(targetGroup.TargetGroupIdentifier), + "weight": aws.ToInt32(targetGroup.Weight), + } + targetGroups = append(targetGroups, m) + } + + return targetGroups +} diff --git a/internal/service/vpclattice/listener_data_source_test.go b/internal/service/vpclattice/listener_data_source_test.go new file mode 100644 index 000000000000..7ce8048e18dd --- /dev/null +++ b/internal/service/vpclattice/listener_data_source_test.go @@ -0,0 +1,215 @@ +package vpclattice_test + +import ( + "fmt" + "regexp" + "testing" + + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccVPCLatticeListenerDataSource_basic(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_vpclattice_listener.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.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccListenerDataSourceConfig_fixedResponseHTTP(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "name", rName), + resource.TestCheckResourceAttr(dataSourceName, "protocol", "HTTP"), + resource.TestCheckResourceAttr(dataSourceName, "default_action.0.fixed_response.0.status_code", "404"), + acctest.MatchResourceAttrRegionalARN(dataSourceName, "arn", "vpc-lattice", regexp.MustCompile(`service/svc-.*/listener/listener-.+`)), + ), + }, + }, + }) +} + +func TestAccVPCLatticeListenerDataSource_tags(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_vpclattice_listener.test_tags" + tag_name := "tag0" + tag_value := "value0" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.VPCLatticeEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccListenerDataSourceConfig_one_tag(rName, tag_name, tag_value), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "tags.tag0", "value0"), + acctest.MatchResourceAttrRegionalARN(dataSourceName, "arn", "vpc-lattice", regexp.MustCompile(`service/svc-.*/listener/listener-.+`)), + ), + }, + }, + }) +} + +func TestAccVPCLatticeListenerDataSource_forwardMultiTargetGroupHTTP(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + targetGroupName1 := fmt.Sprintf("testtargetgroup-%s", sdkacctest.RandString(10)) + + targetGroupResourceName := "aws_vpclattice_target_group.test" + targetGroup1ResourceName := "aws_vpclattice_target_group.test1" + dataSourceName := "data.aws_vpclattice_listener.test_multi_target" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.VPCLatticeEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccListenerDataSourceConfig_forwardMultiTargetGroupHTTP(rName, targetGroupName1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "default_action.0.forward.0.target_groups.0.target_group_identifier", targetGroupResourceName, "id"), + resource.TestCheckResourceAttr(dataSourceName, "default_action.0.forward.0.target_groups.0.weight", "80"), + resource.TestCheckResourceAttrPair(dataSourceName, "default_action.0.forward.0.target_groups.1.target_group_identifier", targetGroup1ResourceName, "id"), + resource.TestCheckResourceAttr(dataSourceName, "default_action.0.forward.0.target_groups.1.weight", "20"), + acctest.MatchResourceAttrRegionalARN(dataSourceName, "arn", "vpc-lattice", regexp.MustCompile(`service/svc-.*/listener/listener-.+`)), + ), + }, + }, + }) +} + +func testAccListenerDataSourceConfig_one_tag(rName, tag_key, tag_value string) string { + return acctest.ConfigCompose(testAccListenerDataSourceConfig_basic(rName), fmt.Sprintf(` +resource "aws_vpclattice_listener" "test_tags" { + name = %[1]q + protocol = "HTTP" + service_identifier = aws_vpclattice_service.test.id + + default_action { + forward { + target_groups { + target_group_identifier = aws_vpclattice_target_group.test.id + weight = 100 + } + } + } + + tags = { + %[2]q = %[3]q + } +} + +data "aws_vpclattice_listener" "test_tags" { + service_identifier = aws_vpclattice_service.test.id + listener_identifier = aws_vpclattice_listener.test_tags.arn +} +`, rName, tag_key, tag_value)) +} + +func testAccListenerDataSourceConfig_basic(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), fmt.Sprintf(` +resource "aws_vpclattice_service" "test" { + name = %[1]q +} + +resource "aws_vpclattice_target_group" "test" { + name = %[1]q + type = "INSTANCE" + + config { + port = 80 + protocol = "HTTP" + vpc_identifier = aws_vpc.test.id + } +} +`, rName)) +} + +func testAccListenerDataSourceConfig_fixedResponseHTTP(rName string) string { + return acctest.ConfigCompose(testAccListenerDataSourceConfig_basic(rName), fmt.Sprintf(` +resource "aws_vpclattice_listener" "test" { + name = %[1]q + protocol = "HTTP" + service_identifier = aws_vpclattice_service.test.id + default_action { + fixed_response { + status_code = 404 + } + } +} + +data "aws_vpclattice_listener" "test" { + service_identifier = aws_vpclattice_service.test.arn + listener_identifier = aws_vpclattice_listener.test.arn +} +`, rName)) +} + +func testAccListenerDataSourceConfig_forwardMultiTargetGroupHTTP(rName string, targetGroupName1 string) string { + return acctest.ConfigCompose(testAccListenerConfig_basic(rName), fmt.Sprintf(` +resource "aws_vpclattice_target_group" "test1" { + name = %[2]q + type = "INSTANCE" + + config { + port = 8080 + protocol = "HTTP" + vpc_identifier = aws_vpc.test.id + } +} + +resource "aws_vpclattice_listener" "test" { + name = %[1]q + protocol = "HTTP" + service_identifier = aws_vpclattice_service.test.id + default_action { + forward { + target_groups { + target_group_identifier = aws_vpclattice_target_group.test.id + weight = 80 + } + target_groups { + target_group_identifier = aws_vpclattice_target_group.test1.id + weight = 20 + } + } + } +} + +data "aws_vpclattice_listener" "test_multi_target" { + service_identifier = aws_vpclattice_service.test.id + listener_identifier = aws_vpclattice_listener.test.arn +} +`, rName, targetGroupName1)) +} diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index d3e7f7d8a32e..c4d9dc57fe8c 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -21,6 +21,11 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { return []*types.ServicePackageSDKDataSource{ + { + Factory: DataSourceListener, + TypeName: "aws_vpclattice_listener", + Name: "Listener", + }, { Factory: DataSourceService, TypeName: "aws_vpclattice_service", diff --git a/website/docs/d/vpclattice_listener.html.markdown b/website/docs/d/vpclattice_listener.html.markdown new file mode 100644 index 000000000000..d3b2791009d5 --- /dev/null +++ b/website/docs/d/vpclattice_listener.html.markdown @@ -0,0 +1,43 @@ +--- +subcategory: "VPC Lattice" +layout: "aws" +page_title: "AWS: aws_vpclattice_listener" +description: |- + Terraform data source for managing an AWS VPC Lattice Listener. +--- + +# Data Source: aws_vpclattice_listener + +Terraform data source for managing an AWS VPC Lattice Listener. + +## Example Usage + +### Basic Usage + +```terraform +data "aws_vpclattice_listener" "example" { +} +``` + +## Argument Reference + +The following arguments are required: + +* `service_identifier` - (Required) ID or Amazon Resource Name (ARN) of the service network +* `listener_identifier` - (Required) ID or Amazon Resource Name (ARN) of the listener + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the listener. +* `created_at` - The date and time that the listener was created. +* `default_action` - The actions for the default listener rule. +* `last_updated_at` - The date and time the listener was last updated. +* `listener_id` - The ID of the listener. +* `name` - The name of the listener. +* `port` - The listener port. +* `protocol` - The listener protocol. Either `HTTPS` or `HTTP`. +* `service_arn` - The ARN of the service. +* `service_id` - The ID of the service. +* `tags` - List of tags associated with the listener.