Skip to content

Commit d7093af

Browse files
authored
r/aws_route53_zone: prevent re-creation with uppercase name (#36563)
This change handles normalizing the `name` argument such that the value returned from the Route53 API matches what is stored in state. Previously, if the `name` argument included an uppercase letter a persistent difference would be present, causing the resource to be re-created. ```console % make testacc PKG=route53 TESTS=TestAccRoute53Zone_ ==> Checking that code complies with gofmt requirements... TF_ACC=1 go1.21.8 test ./internal/service/route53/... -v -count 1 -parallel 20 -run='TestAccRoute53Zone_' -timeout 360m --- PASS: TestAccRoute53Zone_disappears (57.95s) --- PASS: TestAccRoute53Zone_delegationSetID (65.40s) --- PASS: TestAccRoute53Zone_basic (66.34s) --- PASS: TestAccRoute53Zone_VPC_single (70.98s) --- PASS: TestAccRoute53Zone_comment (71.97s) --- PASS: TestAccRoute53Zone_multiple (74.73s) --- PASS: TestAccRoute53Zone_tags (76.63s) --- PASS: TestAccRoute53Zone_VPC_multiple (143.04s) --- PASS: TestAccRoute53Zone_VPC_single_forceDestroy (190.01s) --- PASS: TestAccRoute53Zone_VPC_updates (212.37s) --- PASS: TestAccRoute53Zone_forceDestroy (258.91s) --- PASS: TestAccRoute53Zone_ForceDestroy_trailingPeriod (274.02s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/route53 279.439 ``` ```console % make testacc PKG=route53 TESTS=TestAccRoute53ZoneDataSource_ ==> Checking that code complies with gofmt requirements... TF_ACC=1 go1.21.8 test ./internal/service/route53/... -v -count 1 -parallel 20 -run='TestAccRoute53ZoneDataSource_' -timeout 360m --- PASS: TestAccRoute53ZoneDataSource_id (45.26s) --- PASS: TestAccRoute53ZoneDataSource_name (48.35s) --- PASS: TestAccRoute53ZoneDataSource_tags (65.21s) --- PASS: TestAccRoute53ZoneDataSource_vpc (83.68s) --- PASS: TestAccRoute53ZoneDataSource_serviceDiscovery (102.34s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/route53 107.890s ```
1 parent ecb879e commit d7093af

File tree

4 files changed

+22
-11
lines changed

4 files changed

+22
-11
lines changed

.changelog/36563.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
resource/aws_route53_zone: Prevent re-creation when `name` casing changes
3+
```

internal/service/route53/zone.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func ResourceZone() *schema.Resource {
8282
Type: schema.TypeString,
8383
Required: true,
8484
ForceNew: true,
85-
StateFunc: TrimTrailingPeriod,
85+
StateFunc: NormalizeZoneName,
8686
ValidateFunc: validation.StringLenBetween(1, 1024),
8787
},
8888
"name_servers": {
@@ -209,7 +209,7 @@ func resourceZoneRead(ctx context.Context, d *schema.ResourceData, meta interfac
209209
d.Set("delegation_set_id", "")
210210
// To be consistent with other AWS services (e.g. ACM) that do not accept a trailing period,
211211
// we remove the suffix from the Hosted Zone Name returned from the API
212-
d.Set("name", TrimTrailingPeriod(aws.StringValue(output.HostedZone.Name)))
212+
d.Set("name", NormalizeZoneName(aws.StringValue(output.HostedZone.Name)))
213213
d.Set("zone_id", CleanZoneID(aws.StringValue(output.HostedZone.Id)))
214214

215215
var nameServers []string
@@ -549,11 +549,14 @@ func CleanZoneID(ID string) string {
549549
return strings.TrimPrefix(ID, "/hostedzone/")
550550
}
551551

552-
// TrimTrailingPeriod is used to remove the trailing period
553-
// of "name" or "domain name" attributes often returned from
554-
// the Route53 API or provided as user input.
552+
// NormalizeZoneName is used to remove the trailing period
553+
// and apply consistent casing to "name" or "domain name"
554+
// attributes returned from the Route53 API or provided as
555+
// user input.
556+
//
555557
// The single dot (".") domain name is returned as-is.
556-
func TrimTrailingPeriod(v interface{}) string {
558+
// Uppercase letters are converted to lowercase.
559+
func NormalizeZoneName(v interface{}) string {
557560
var str string
558561
switch value := v.(type) {
559562
case *string:
@@ -568,7 +571,7 @@ func TrimTrailingPeriod(v interface{}) string {
568571
return str
569572
}
570573

571-
return strings.TrimSuffix(str, ".")
574+
return strings.ToLower(strings.TrimSuffix(str, "."))
572575
}
573576

574577
func findNameServers(ctx context.Context, conn *route53.Route53, zoneId string, zoneName string) ([]string, error) {

internal/service/route53/zone_data_source.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func dataSourceZoneRead(ctx context.Context, d *schema.ResourceData, meta interf
123123
hostedZoneFound = hostedZone
124124
break
125125
// we check if the name is the same as requested and if private zone field is the same as requested or if there is a vpc_id
126-
} else if (TrimTrailingPeriod(aws.StringValue(hostedZone.Name)) == TrimTrailingPeriod(name)) && (aws.BoolValue(hostedZone.Config.PrivateZone) == d.Get("private_zone").(bool) || (aws.BoolValue(hostedZone.Config.PrivateZone) && vpcIdExists)) {
126+
} else if (NormalizeZoneName(aws.StringValue(hostedZone.Name)) == NormalizeZoneName(name)) && (aws.BoolValue(hostedZone.Config.PrivateZone) == d.Get("private_zone").(bool) || (aws.BoolValue(hostedZone.Config.PrivateZone) && vpcIdExists)) {
127127
matchingVPC := false
128128
if vpcIdExists {
129129
reqHostedZone := &route53.GetHostedZoneInput{}
@@ -178,7 +178,7 @@ func dataSourceZoneRead(ctx context.Context, d *schema.ResourceData, meta interf
178178
d.Set("zone_id", idHostedZone)
179179
// To be consistent with other AWS services (e.g. ACM) that do not accept a trailing period,
180180
// we remove the suffix from the Hosted Zone Name returned from the API
181-
d.Set("name", TrimTrailingPeriod(aws.StringValue(hostedZoneFound.Name)))
181+
d.Set("name", NormalizeZoneName(aws.StringValue(hostedZoneFound.Name)))
182182
d.Set("comment", hostedZoneFound.Config.Comment)
183183
d.Set("private_zone", hostedZoneFound.Config.PrivateZone)
184184
d.Set("caller_reference", hostedZoneFound.CallerReference)

internal/service/route53/zone_test.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func TestCleanChangeID(t *testing.T) {
6060
}
6161
}
6262

63-
func TestTrimTrailingPeriod(t *testing.T) {
63+
func TestNormalizeZoneName(t *testing.T) {
6464
t.Parallel()
6565

6666
cases := []struct {
@@ -70,17 +70,22 @@ func TestTrimTrailingPeriod(t *testing.T) {
7070
{"example.com", "example.com"},
7171
{"example.com.", "example.com"},
7272
{"www.example.com.", "www.example.com"},
73+
{"www.ExAmPlE.COM.", "www.example.com"},
7374
{"", ""},
7475
{".", "."},
7576
{aws.String("example.com"), "example.com"},
7677
{aws.String("example.com."), "example.com"},
78+
{aws.String("www.example.com."), "www.example.com"},
79+
{aws.String("www.ExAmPlE.COM."), "www.example.com"},
80+
{aws.String(""), ""},
81+
{aws.String("."), "."},
7782
{(*string)(nil), ""},
7883
{42, ""},
7984
{nil, ""},
8085
}
8186

8287
for _, tc := range cases {
83-
actual := tfroute53.TrimTrailingPeriod(tc.Input)
88+
actual := tfroute53.NormalizeZoneName(tc.Input)
8489
if actual != tc.Output {
8590
t.Fatalf("input: %s\noutput: %s", tc.Input, actual)
8691
}

0 commit comments

Comments
 (0)