From 74c25a3a63f0c3c49f7feba501ac290bb0389640 Mon Sep 17 00:00:00 2001 From: ejorgensen22 Date: Mon, 24 Nov 2025 16:13:35 -0600 Subject: [PATCH 1/3] fix: use old zone id for records removal --- internal/service/route53/zone.go | 10 +++++- internal/service/route53/zone_test.go | 47 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/internal/service/route53/zone.go b/internal/service/route53/zone.go index a8126d1ffa39..b5f1e2db2878 100644 --- a/internal/service/route53/zone.go +++ b/internal/service/route53/zone.go @@ -321,7 +321,15 @@ func findHostedZoneByID(ctx context.Context, conn *route53.Client, id string) (* func deleteHostedZone(ctx context.Context, conn *route53.Client, hostedZoneID, hostedZoneName string, force bool, timeout time.Duration) error { if force { - if err := deleteAllResourceRecordsFromHostedZone(ctx, conn, hostedZoneID, hostedZoneName, timeout); err != nil { + // Get the actual zone name from AWS instead of relying on the Terraform state + // which might contain the new zone name during a ForceNew operation + zoneOutput, err := findHostedZoneByID(ctx, conn, hostedZoneID) + if err != nil { + return fmt.Errorf("reading Route 53 Hosted Zone (%s) for deletion: %w", hostedZoneID, err) + } + + actualZoneName := aws.ToString(zoneOutput.HostedZone.Name) + if err := deleteAllResourceRecordsFromHostedZone(ctx, conn, hostedZoneID, actualZoneName, timeout); err != nil { return err } diff --git a/internal/service/route53/zone_test.go b/internal/service/route53/zone_test.go index c0425bc5c76c..b9b78be0c6fb 100644 --- a/internal/service/route53/zone_test.go +++ b/internal/service/route53/zone_test.go @@ -542,6 +542,39 @@ func TestAccRoute53Zone_escapedSpace(t *testing.T) { }) } +func TestAccRoute53Zone_nameUpdate(t *testing.T) { + ctx := acctest.Context(t) + var zone1, zone2 route53.GetHostedZoneOutput + resourceName := "aws_route53_zone.test" + zoneName1 := acctest.RandomDomainName() + zoneName2 := acctest.RandomDomainName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.Route53ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckZoneDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccZoneConfig_basic(zoneName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckZoneExists(ctx, resourceName, &zone1), + resource.TestCheckResourceAttr(resourceName, names.AttrName, zoneName1), + ), + }, + { + Config: testAccZoneConfig_basic(zoneName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckZoneExists(ctx, resourceName, &zone2), + resource.TestCheckResourceAttr(resourceName, names.AttrName, zoneName2), + // Verify that a new zone was created (different zone ID) + testAccCheckZoneRecreated(&zone1, &zone2), + ), + }, + }, + }) +} + func testAccCheckZoneDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).Route53Client(ctx) @@ -666,6 +699,20 @@ func testAccCheckDomainName(zone *route53.GetHostedZoneOutput, domain string) re } } +func testAccCheckZoneRecreated(before, after *route53.GetHostedZoneOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before.HostedZone.Id == nil || after.HostedZone.Id == nil { + return fmt.Errorf("Missing hosted zone ID") + } + + if aws.ToString(before.HostedZone.Id) == aws.ToString(after.HostedZone.Id) { + return fmt.Errorf("Expected zone to be recreated, but zone ID remained the same: %s", aws.ToString(before.HostedZone.Id)) + } + + return nil + } +} + func testAccZoneConfig_basic(zoneName string) string { return fmt.Sprintf(` resource "aws_route53_zone" "test" { From ba4018e5d03747705a96afd18520237749ef98ed Mon Sep 17 00:00:00 2001 From: ejorgensen22 Date: Tue, 25 Nov 2025 10:03:46 -0600 Subject: [PATCH 2/3] chore: add changelog --- .changelog/45242.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/45242.txt diff --git a/.changelog/45242.txt b/.changelog/45242.txt new file mode 100644 index 000000000000..87fc3747ac94 --- /dev/null +++ b/.changelog/45242.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_route53_zone: Fix InvalidChangeBatch error during ForceNew operations when zone name changes +``` \ No newline at end of file From b8a0f58d35a33545cebad62fd7ae663e98300519 Mon Sep 17 00:00:00 2001 From: ejorgensen22 Date: Tue, 25 Nov 2025 11:15:52 -0600 Subject: [PATCH 3/3] chore: remove unused parameter --- internal/service/route53/zone.go | 8 ++++---- internal/service/route53domains/domain.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/service/route53/zone.go b/internal/service/route53/zone.go index b5f1e2db2878..825f80977a5a 100644 --- a/internal/service/route53/zone.go +++ b/internal/service/route53/zone.go @@ -287,7 +287,7 @@ func resourceZoneDelete(ctx context.Context, d *schema.ResourceData, meta any) d var diags diag.Diagnostics conn := meta.(*conns.AWSClient).Route53Client(ctx) - if err := deleteHostedZone(ctx, conn, d.Id(), d.Get(names.AttrName).(string), d.Get(names.AttrForceDestroy).(bool), d.Timeout(schema.TimeoutDelete)); err != nil { + if err := deleteHostedZone(ctx, conn, d.Id(), d.Get(names.AttrForceDestroy).(bool), d.Timeout(schema.TimeoutDelete)); err != nil { return sdkdiag.AppendFromErr(diags, err) } @@ -319,7 +319,7 @@ func findHostedZoneByID(ctx context.Context, conn *route53.Client, id string) (* return output, nil } -func deleteHostedZone(ctx context.Context, conn *route53.Client, hostedZoneID, hostedZoneName string, force bool, timeout time.Duration) error { +func deleteHostedZone(ctx context.Context, conn *route53.Client, hostedZoneID string, force bool, timeout time.Duration) error { if force { // Get the actual zone name from AWS instead of relying on the Terraform state // which might contain the new zone name during a ForceNew operation @@ -328,8 +328,8 @@ func deleteHostedZone(ctx context.Context, conn *route53.Client, hostedZoneID, h return fmt.Errorf("reading Route 53 Hosted Zone (%s) for deletion: %w", hostedZoneID, err) } - actualZoneName := aws.ToString(zoneOutput.HostedZone.Name) - if err := deleteAllResourceRecordsFromHostedZone(ctx, conn, hostedZoneID, actualZoneName, timeout); err != nil { + hostedZoneName := aws.ToString(zoneOutput.HostedZone.Name) + if err := deleteAllResourceRecordsFromHostedZone(ctx, conn, hostedZoneID, hostedZoneName, timeout); err != nil { return err } diff --git a/internal/service/route53domains/domain.go b/internal/service/route53domains/domain.go index edd8cfd03a41..f8ccf90a9cdb 100644 --- a/internal/service/route53domains/domain.go +++ b/internal/service/route53domains/domain.go @@ -616,7 +616,7 @@ func (r *domainResource) Delete(ctx context.Context, request resource.DeleteRequ // Delete the associated Route 53 hosted zone. if hostedZoneID := fwflex.StringValueFromFramework(ctx, data.HostedZoneID); hostedZoneID != "" { - if err := tfroute53.DeleteHostedZone(ctx, r.Meta().Route53Client(ctx), hostedZoneID, domainName, true, timeout); err != nil { + if err := tfroute53.DeleteHostedZone(ctx, r.Meta().Route53Client(ctx), hostedZoneID, true, timeout); err != nil { response.Diagnostics.AddError(fmt.Sprintf("deleting Route 53 Hosted Zone (%s)", hostedZoneID), err.Error()) return