diff --git a/.changelog/47950.txt b/.changelog/47950.txt new file mode 100644 index 000000000000..8bd308b298de --- /dev/null +++ b/.changelog/47950.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_route53_zone_association: Add resource identity support +``` + +```release-note:new-list-resource +aws_route53_zone_association +``` diff --git a/internal/service/route53/service_package_gen.go b/internal/service/route53/service_package_gen.go index 95a5b2316385..1dd72e0f387b 100644 --- a/internal/service/route53/service_package_gen.go +++ b/internal/service/route53/service_package_gen.go @@ -185,6 +185,15 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*inttypes.ServicePa TypeName: "aws_route53_zone_association", Name: "Zone Association", Region: inttypes.ResourceRegionDisabled(), + Identity: inttypes.GlobalParameterizedIdentity([]inttypes.IdentityAttribute{ + inttypes.StringIdentityAttribute("zone_id", true), + inttypes.StringIdentityAttribute(names.AttrVPCID, true), + inttypes.StringIdentityAttribute("vpc_region", false), + }), + Import: inttypes.SDKv2Import{ + WrappedImport: true, + ImportID: zoneAssociationImportID{}, + }, }, } } @@ -226,6 +235,17 @@ func (p *servicePackage) SDKListResources(ctx context.Context) iter.Seq[*inttype }), Identity: inttypes.GlobalSingleParameterIdentity(inttypes.StringIdentityAttribute("zone_id", true)), }, + { + Factory: newZoneAssociationResourceAsListResource, + TypeName: "aws_route53_zone_association", + Name: "Zone Association", + Region: inttypes.ResourceRegionDisabled(), + Identity: inttypes.GlobalParameterizedIdentity([]inttypes.IdentityAttribute{ + inttypes.StringIdentityAttribute("zone_id", true), + inttypes.StringIdentityAttribute(names.AttrVPCID, true), + inttypes.StringIdentityAttribute("vpc_region", false), + }), + }, }) } diff --git a/internal/service/route53/testdata/ZoneAssociation/basic/main_gen.tf b/internal/service/route53/testdata/ZoneAssociation/basic/main_gen.tf new file mode 100644 index 000000000000..def3e2d9938d --- /dev/null +++ b/internal/service/route53/testdata/ZoneAssociation/basic/main_gen.tf @@ -0,0 +1,45 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_route53_zone_association" "test" { + zone_id = aws_route53_zone.foo.id + vpc_id = aws_vpc.bar.id +} + +resource "aws_vpc" "foo" { + cidr_block = "10.6.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_vpc" "bar" { + cidr_block = "10.7.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_route53_zone" "foo" { + name = "${var.rName}.example.com" + + vpc { + vpc_id = aws_vpc.foo.id + } + + lifecycle { + ignore_changes = [vpc] + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} diff --git a/internal/service/route53/testdata/ZoneAssociation/basic_v6.45.0/main_gen.tf b/internal/service/route53/testdata/ZoneAssociation/basic_v6.45.0/main_gen.tf new file mode 100644 index 000000000000..d2c4dfa81b18 --- /dev/null +++ b/internal/service/route53/testdata/ZoneAssociation/basic_v6.45.0/main_gen.tf @@ -0,0 +1,55 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_route53_zone_association" "test" { + zone_id = aws_route53_zone.foo.id + vpc_id = aws_vpc.bar.id +} + +resource "aws_vpc" "foo" { + cidr_block = "10.6.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_vpc" "bar" { + cidr_block = "10.7.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_route53_zone" "foo" { + name = "${var.rName}.example.com" + + vpc { + vpc_id = aws_vpc.foo.id + } + + lifecycle { + ignore_changes = [vpc] + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "6.45.0" + } + } +} + +provider "aws" {} diff --git a/internal/service/route53/testdata/ZoneAssociation/list_basic/main.tf b/internal/service/route53/testdata/ZoneAssociation/list_basic/main.tf new file mode 100644 index 000000000000..784fcb8a5e7e --- /dev/null +++ b/internal/service/route53/testdata/ZoneAssociation/list_basic/main.tf @@ -0,0 +1,57 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_route53_zone_association" "test" { + count = var.resource_count + + zone_id = aws_route53_zone.foo[count.index].id + vpc_id = aws_vpc.bar.id +} + +resource "aws_vpc" "bar" { + cidr_block = "10.7.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_vpc" "foo" { + count = var.resource_count + + cidr_block = cidrsubnet("10.0.0.0/8", 8, count.index) + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "${var.rName}-${count.index}" + } +} + +resource "aws_route53_zone" "foo" { + count = var.resource_count + + name = "${var.rName}-${count.index}.example.com" + + vpc { + vpc_id = aws_vpc.foo[count.index].id + } + + lifecycle { + ignore_changes = [vpc] + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} diff --git a/internal/service/route53/testdata/ZoneAssociation/list_basic/query.tfquery.hcl b/internal/service/route53/testdata/ZoneAssociation/list_basic/query.tfquery.hcl new file mode 100644 index 000000000000..201fea9a5149 --- /dev/null +++ b/internal/service/route53/testdata/ZoneAssociation/list_basic/query.tfquery.hcl @@ -0,0 +1,10 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_route53_zone_association" "test" { + provider = aws + + config { + vpc_id = aws_vpc.bar.id + } +} diff --git a/internal/service/route53/testdata/ZoneAssociation/list_include_resource/main.tf b/internal/service/route53/testdata/ZoneAssociation/list_include_resource/main.tf new file mode 100644 index 000000000000..784fcb8a5e7e --- /dev/null +++ b/internal/service/route53/testdata/ZoneAssociation/list_include_resource/main.tf @@ -0,0 +1,57 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_route53_zone_association" "test" { + count = var.resource_count + + zone_id = aws_route53_zone.foo[count.index].id + vpc_id = aws_vpc.bar.id +} + +resource "aws_vpc" "bar" { + cidr_block = "10.7.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_vpc" "foo" { + count = var.resource_count + + cidr_block = cidrsubnet("10.0.0.0/8", 8, count.index) + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "${var.rName}-${count.index}" + } +} + +resource "aws_route53_zone" "foo" { + count = var.resource_count + + name = "${var.rName}-${count.index}.example.com" + + vpc { + vpc_id = aws_vpc.foo[count.index].id + } + + lifecycle { + ignore_changes = [vpc] + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} diff --git a/internal/service/route53/testdata/ZoneAssociation/list_include_resource/query.tfquery.hcl b/internal/service/route53/testdata/ZoneAssociation/list_include_resource/query.tfquery.hcl new file mode 100644 index 000000000000..3c0e199b45a6 --- /dev/null +++ b/internal/service/route53/testdata/ZoneAssociation/list_include_resource/query.tfquery.hcl @@ -0,0 +1,12 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_route53_zone_association" "test" { + provider = aws + + include_resource = true + + config { + vpc_id = aws_vpc.bar.id + } +} diff --git a/internal/service/route53/testdata/tmpl/zone_association_basic.gtpl b/internal/service/route53/testdata/tmpl/zone_association_basic.gtpl new file mode 100644 index 000000000000..1de2d4f23bc4 --- /dev/null +++ b/internal/service/route53/testdata/tmpl/zone_association_basic.gtpl @@ -0,0 +1,37 @@ +resource "aws_route53_zone_association" "test" { +{{- template "region" }} + zone_id = aws_route53_zone.foo.id + vpc_id = aws_vpc.bar.id +} + +resource "aws_vpc" "foo" { + cidr_block = "10.6.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_vpc" "bar" { + cidr_block = "10.7.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_route53_zone" "foo" { + name = "${var.rName}.example.com" + + vpc { + vpc_id = aws_vpc.foo.id + } + + lifecycle { + ignore_changes = [vpc] + } +} diff --git a/internal/service/route53/zone_association.go b/internal/service/route53/zone_association.go index 7893ad0d6e57..9090daa54623 100644 --- a/internal/service/route53/zone_association.go +++ b/internal/service/route53/zone_association.go @@ -25,20 +25,22 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/retry" tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + inttypes "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/names" ) // @SDKResource("aws_route53_zone_association", name="Zone Association") +// @IdentityAttribute("zone_id") +// @IdentityAttribute("vpc_id") +// @IdentityAttribute("vpc_region", optional="true", testNotNull="true") +// @ImportIDHandler("zoneAssociationImportID") +// @Testing(preIdentityVersion="v6.45.0") func resourceZoneAssociation() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceZoneAssociationCreate, ReadWithoutTimeout: resourceZoneAssociationRead, DeleteWithoutTimeout: resourceZoneAssociationDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ "owning_account": { Type: schema.TypeString, @@ -139,14 +141,18 @@ func resourceZoneAssociationRead(ctx context.Context, d *schema.ResourceData, me return sdkdiag.AppendErrorf(diags, "reading Route 53 Zone Association (%s): %s", d.Id(), err) } - d.Set("owning_account", hostedZoneSummary.Owner.OwningAccount) - d.Set(names.AttrVPCID, vpcID) - d.Set("vpc_region", vpcRegion) - d.Set("zone_id", hostedZoneSummary.HostedZoneId) + resourceZoneAssociationFlatten(d, hostedZoneSummary, vpcID, vpcRegion) return diags } +func resourceZoneAssociationFlatten(d *schema.ResourceData, summary *awstypes.HostedZoneSummary, vpcID, vpcRegion string) { + d.Set("owning_account", summary.Owner.OwningAccount) + d.Set(names.AttrVPCID, vpcID) + d.Set("vpc_region", vpcRegion) + d.Set("zone_id", summary.HostedZoneId) +} + func resourceZoneAssociationDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).Route53Client(ctx) @@ -267,3 +273,28 @@ func findZoneAssociations(ctx context.Context, conn *route53.Client, input *rout return output, nil } + +var _ inttypes.SDKv2ImportID = zoneAssociationImportID{} + +type zoneAssociationImportID struct{} + +func (zoneAssociationImportID) Create(d *schema.ResourceData) string { + return zoneAssociationCreateResourceID(d.Get("zone_id").(string), d.Get(names.AttrVPCID).(string), d.Get("vpc_region").(string)) +} + +func (zoneAssociationImportID) Parse(id string) (string, map[string]any, error) { + zoneID, vpcID, vpcRegion, err := zoneAssociationParseResourceID(id) + if err != nil { + return "", nil, err + } + + result := map[string]any{ + "zone_id": zoneID, + names.AttrVPCID: vpcID, + } + if vpcRegion != "" { + result["vpc_region"] = vpcRegion + } + + return id, result, nil +} diff --git a/internal/service/route53/zone_association_identity_gen_test.go b/internal/service/route53/zone_association_identity_gen_test.go new file mode 100644 index 000000000000..76b599b34f78 --- /dev/null +++ b/internal/service/route53/zone_association_identity_gen_test.go @@ -0,0 +1,225 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by internal/generate/identitytests/main.go; DO NOT EDIT. + +package route53_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfknownvalue "github.com/hashicorp/terraform-provider-aws/internal/acctest/knownvalue" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccRoute53ZoneAssociation_Identity_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_route53_zone_association.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.Route53ServiceID), + CheckDestroy: testAccCheckZoneAssociationDestroy(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckZoneAssociationExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + "zone_id": knownvalue.NotNull(), + names.AttrVPCID: knownvalue.NotNull(), + "vpc_region": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("zone_id")), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrVPCID)), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("vpc_region")), + }, + }, + + // Step 2: Import command + { + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import block with Import ID + { + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("zone_id"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrVPCID), knownvalue.NotNull()), + }, + }, + }, + + // Step 4: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("zone_id"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrVPCID), knownvalue.NotNull()), + }, + }, + }, + }, + }) +} + +// Resource Identity was added after v6.45.0 +func TestAccRoute53ZoneAssociation_Identity_ExistingResource_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_route53_zone_association.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.Route53ServiceID), + CheckDestroy: testAccCheckZoneAssociationDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/basic_v6.45.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckZoneAssociationExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + "zone_id": knownvalue.NotNull(), + names.AttrVPCID: knownvalue.NotNull(), + "vpc_region": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("zone_id")), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrVPCID)), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("vpc_region")), + }, + }, + }, + }) +} + +// Resource Identity was added after v6.45.0 +func TestAccRoute53ZoneAssociation_Identity_ExistingResource_noRefreshNoChange(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_route53_zone_association.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.Route53ServiceID), + CheckDestroy: testAccCheckZoneAssociationDestroy(ctx, t), + AdditionalCLIOptions: &resource.AdditionalCLIOptions{ + Plan: resource.PlanOptions{ + NoRefresh: true, + }, + }, + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/basic_v6.45.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckZoneAssociationExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + }, + }) +} diff --git a/internal/service/route53/zone_association_list.go b/internal/service/route53/zone_association_list.go new file mode 100644 index 000000000000..c70f7f82963c --- /dev/null +++ b/internal/service/route53/zone_association_list.go @@ -0,0 +1,127 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package route53 + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/route53" + awstypes "github.com/aws/aws-sdk-go-v2/service/route53/types" + "github.com/hashicorp/terraform-plugin-framework/list" + listschema "github.com/hashicorp/terraform-plugin-framework/list/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/logging" + inttypes "github.com/hashicorp/terraform-provider-aws/internal/types" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// Function annotations are used for list resource registration to the Provider. DO NOT EDIT. +// @SDKListResource("aws_route53_zone_association") +func newZoneAssociationResourceAsListResource() inttypes.ListResourceForSDK { + l := zoneAssociationListResource{} + l.SetResourceSchema(resourceZoneAssociation()) + return &l +} + +var _ list.ListResource = &zoneAssociationListResource{} + +type zoneAssociationListResource struct { + framework.ListResourceWithSDKv2Resource +} +type zoneAssociationListResourceModel struct { + VPCID types.String `tfsdk:"vpc_id"` + VPCRegion types.String `tfsdk:"vpc_region"` +} + +func (l *zoneAssociationListResource) ListResourceConfigSchema(_ context.Context, _ list.ListResourceSchemaRequest, response *list.ListResourceSchemaResponse) { + response.Schema = listschema.Schema{ + Attributes: map[string]listschema.Attribute{ + names.AttrVPCID: listschema.StringAttribute{ + Required: true, + Description: "ID of the VPC to list hosted zone associations for.", + }, + "vpc_region": listschema.StringAttribute{ + Optional: true, + Description: "Region of the VPC. Defaults to the provider region.", + }, + }, + } +} + +func (l *zoneAssociationListResource) List(ctx context.Context, request list.ListRequest, stream *list.ListResultsStream) { + var query zoneAssociationListResourceModel + if diags := request.Config.Get(ctx, &query); diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + awsClient := l.Meta() + conn := awsClient.Route53Client(ctx) + + vpcID := query.VPCID.ValueString() + vpcRegion := query.VPCRegion.ValueString() + if vpcRegion == "" { + vpcRegion = awsClient.Region(ctx) + } + + tflog.Info(ctx, "Listing Route 53 Zone Associations", map[string]any{ + logging.ResourceAttributeKey(names.AttrVPCID): vpcID, + logging.ResourceAttributeKey("vpc_region"): vpcRegion, + }) + + stream.Results = func(yield func(list.ListResult) bool) { + input := &route53.ListHostedZonesByVPCInput{ + VPCId: aws.String(vpcID), + VPCRegion: awstypes.VPCRegion(vpcRegion), + } + + err := listHostedZonesByVPCPages(ctx, conn, input, func(page *route53.ListHostedZonesByVPCOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, summary := range page.HostedZoneSummaries { + hostedZoneID := aws.ToString(summary.HostedZoneId) + ctx := tflog.SetField(ctx, logging.ResourceAttributeKey("zone_id"), hostedZoneID) + id := zoneAssociationCreateResourceID(hostedZoneID, vpcID, vpcRegion) + + result := request.NewListResult(ctx) + + rd := l.ResourceData() + rd.SetId(id) + rd.Set("zone_id", hostedZoneID) + rd.Set(names.AttrVPCID, vpcID) + rd.Set("vpc_region", vpcRegion) + + if request.IncludeResource { + resourceZoneAssociationFlatten(rd, &summary, vpcID, vpcRegion) + } + + result.DisplayName = normalizeDomainName(aws.ToString(summary.Name)) + + l.SetResult(ctx, awsClient, request.IncludeResource, rd, &result) + if result.Diagnostics.HasError() { + yield(result) + return false + } + + if !yield(result) { + return false + } + } + + return !lastPage + }) + + if err != nil { + result := fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + } +} diff --git a/internal/service/route53/zone_association_list_test.go b/internal/service/route53/zone_association_list_test.go new file mode 100644 index 000000000000..72c98ec18591 --- /dev/null +++ b/internal/service/route53/zone_association_list_test.go @@ -0,0 +1,136 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package route53_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfquerycheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/querycheck" + tfqueryfilter "github.com/hashicorp/terraform-provider-aws/internal/acctest/queryfilter" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccRoute53ZoneAssociation_List_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName1 := "aws_route53_zone_association.test[0]" + resourceName2 := "aws_route53_zone_association.test[1]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + identity1 := tfstatecheck.Identity() + identity2 := tfstatecheck.Identity() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.Route53ServiceID), + CheckDestroy: testAccCheckZoneAssociationDestroy(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(resourceName1), + identity2.GetIdentity(resourceName2), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_route53_zone_association.test", identity1.Checks()), + querycheck.ExpectResourceDisplayName("aws_route53_zone_association.test", tfqueryfilter.ByResourceIdentityFunc(identity1.Checks()), knownvalue.StringExact(fmt.Sprintf("%s-0.example.com", rName))), + tfquerycheck.ExpectNoResourceObject("aws_route53_zone_association.test", tfqueryfilter.ByResourceIdentityFunc(identity1.Checks())), + + tfquerycheck.ExpectIdentityFunc("aws_route53_zone_association.test", identity2.Checks()), + querycheck.ExpectResourceDisplayName("aws_route53_zone_association.test", tfqueryfilter.ByResourceIdentityFunc(identity2.Checks()), knownvalue.StringExact(fmt.Sprintf("%s-1.example.com", rName))), + tfquerycheck.ExpectNoResourceObject("aws_route53_zone_association.test", tfqueryfilter.ByResourceIdentityFunc(identity2.Checks())), + }, + }, + }, + }) +} + +func TestAccRoute53ZoneAssociation_List_includeResource(t *testing.T) { + ctx := acctest.Context(t) + + resourceName1 := "aws_route53_zone_association.test[0]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + identity1 := tfstatecheck.Identity() + vpcID := tfstatecheck.StateValue() + zoneID := tfstatecheck.StateValue() + resourceID := tfstatecheck.StateValue() + owningAccount := tfstatecheck.StateValue() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.Route53ServiceID), + CheckDestroy: testAccCheckZoneAssociationDestroy(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/list_include_resource/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(1), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(resourceName1), + vpcID.GetStateValue(resourceName1, tfjsonpath.New(names.AttrVPCID)), + zoneID.GetStateValue(resourceName1, tfjsonpath.New("zone_id")), + resourceID.GetStateValue(resourceName1, tfjsonpath.New(names.AttrID)), + owningAccount.GetStateValue(resourceName1, tfjsonpath.New("owning_account")), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/ZoneAssociation/list_include_resource/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(1), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_route53_zone_association.test", identity1.Checks()), + querycheck.ExpectResourceKnownValues("aws_route53_zone_association.test", tfqueryfilter.ByResourceIdentityFunc(identity1.Checks()), []querycheck.KnownValueCheck{ + tfquerycheck.KnownValueCheck(tfjsonpath.New(names.AttrID), resourceID.ValueCheck()), + tfquerycheck.KnownValueCheck(tfjsonpath.New("zone_id"), zoneID.ValueCheck()), + tfquerycheck.KnownValueCheck(tfjsonpath.New(names.AttrVPCID), vpcID.ValueCheck()), + tfquerycheck.KnownValueCheck(tfjsonpath.New("vpc_region"), knownvalue.StringExact(acctest.Region())), + tfquerycheck.KnownValueCheck(tfjsonpath.New("owning_account"), owningAccount.ValueCheck()), + }), + }, + }, + }, + }) +} diff --git a/website/docs/list-resources/route53_zone_association.html.markdown b/website/docs/list-resources/route53_zone_association.html.markdown new file mode 100644 index 000000000000..b8ee420ef386 --- /dev/null +++ b/website/docs/list-resources/route53_zone_association.html.markdown @@ -0,0 +1,30 @@ +--- +subcategory: "Route 53" +layout: "aws" +page_title: "AWS: aws_route53_zone_association" +description: |- + Lists Route 53 Zone Association resources. +--- + +# List Resource: aws_route53_zone_association + +Lists Route 53 Zone Association resources. + +## Example Usage + +```terraform +list "aws_route53_zone_association" "example" { + provider = aws + + config { + vpc_id = aws_vpc.example.id + } +} +``` + +## Argument Reference + +This list resource supports the following arguments: + +* `vpc_id` - (Required) ID of the VPC to list hosted zone associations for. +* `vpc_region` - (Optional) Region of the VPC. Defaults to the provider region. diff --git a/website/docs/r/route53_zone_association.html.markdown b/website/docs/r/route53_zone_association.html.markdown index f0e8572670a7..fc101f851a3a 100644 --- a/website/docs/r/route53_zone_association.html.markdown +++ b/website/docs/r/route53_zone_association.html.markdown @@ -76,6 +76,36 @@ This resource exports the following attributes in addition to the arguments abov ## Import +In Terraform v1.12.0 and later, the [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used with the `identity` attribute. For example: + +```terraform +import { + to = aws_route53_zone_association.example + identity = { + zone_id = "Z123456ABCDEFG" + vpc_id = "vpc-12345678" + vpc_region = "us-east-1" + } +} + +resource "aws_route53_zone_association" "example" { + ### Configuration omitted for brevity ### +} +``` + +### Identity Schema + +#### Required + +* `zone_id` (String) The ID of the private hosted zone that you want to associate a VPC with. +* `vpc_id` (String) The VPC to associate with the private hosted zone. + +#### Optional + +* `vpc_region` (String) The VPC's region. Defaults to the region of the AWS provider. +* `account_id` (String) AWS Account where this resource is managed. +* `region` (String) Region where this resource is managed. + In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Route 53 Hosted Zone Associations using the Hosted Zone ID and VPC ID, separated by a colon (`:`). For example: The VPC is in the same region where you have configured the Terraform AWS Provider: