Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions stackit/internal/services/dns/dns_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func configVarsMaxUpdated() config.Variables {
}

func TestAccDnsMinResource(t *testing.T) {
dotDnsName := "tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + ".example.home."

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckDnsDestroy,
Expand Down Expand Up @@ -247,6 +249,20 @@ func TestAccDnsMinResource(t *testing.T) {
resource.TestCheckResourceAttrSet("stackit_dns_record_set.record_set", "fqdn"),
resource.TestCheckResourceAttrSet("stackit_dns_record_set.record_set", "state")),
},
// Test trailing dot preservation
{
Config: resourceMinConfig,
ConfigVariables: func() config.Variables {
vars := maps.Clone(testConfigVarsMin)
vars["dns_name"] = config.StringVariable(dotDnsName)
return vars
}(),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("stackit_dns_zone.zone", "project_id", testutil.ProjectId),
// Verify that the state has the trailing dot, matching the config
resource.TestCheckResourceAttr("stackit_dns_zone.zone", "dns_name", dotDnsName),
),
},
// Deletion is done by the framework implicitly
},
})
Expand Down
35 changes: 33 additions & 2 deletions stackit/internal/services/dns/zone/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ func mapFields(ctx context.Context, zoneResp *dns.ZoneResponse, model *Model) er
model.Active = types.BoolPointerValue(z.Active)
model.ContactEmail = types.StringPointerValue(z.ContactEmail)
model.DefaultTTL = types.Int64PointerValue(z.DefaultTTL)
model.DnsName = types.StringPointerValue(z.DnsName)
model.DnsName = reconcileDnsName(model.DnsName, z.DnsName)
model.ExpireTime = types.Int64PointerValue(z.ExpireTime)
model.IsReverseZone = types.BoolPointerValue(z.IsReverseZone)
model.Name = types.StringPointerValue(z.Name)
Expand Down Expand Up @@ -566,9 +566,16 @@ func toCreatePayload(model *Model) (*dns.CreateZonePayload, error) {
}
modelPrimaries = append(modelPrimaries, primaryString.ValueString())
}

dnsName := conversion.StringValueToPointer(model.DnsName)
if dnsName != nil && strings.HasSuffix(*dnsName, ".") {
trimmed := strings.TrimSuffix(*dnsName, ".")
dnsName = &trimmed
}

return &dns.CreateZonePayload{
Name: conversion.StringValueToPointer(model.Name),
DnsName: conversion.StringValueToPointer(model.DnsName),
DnsName: dnsName,
ContactEmail: conversion.StringValueToPointer(model.ContactEmail),
Description: conversion.StringValueToPointer(model.Description),
Acl: conversion.StringValueToPointer(model.Acl),
Expand Down Expand Up @@ -601,3 +608,27 @@ func toUpdatePayload(model *Model) (*dns.PartialUpdateZonePayload, error) {
Primaries: nil, // API returns error if this field is set, even if nothing changes
}, nil
}

func reconcileDnsName(stateDnsName types.String, apiDnsName *string) types.String {
if apiDnsName == nil {
return types.StringNull()
}

apiValue := *apiDnsName

if stateDnsName.IsNull() || stateDnsName.IsUnknown() {
return types.StringValue(apiValue)
}

stateValue := stateDnsName.ValueString()

// If state has trailing dot, but API doesn't
if strings.HasSuffix(stateValue, ".") && !strings.HasSuffix(apiValue, ".") {
trimmedState := strings.TrimSuffix(stateValue, ".")
if trimmedState == apiValue {
return stateDnsName // Keep the state value with the dot
}
}

return types.StringValue(apiValue)
}
47 changes: 47 additions & 0 deletions stackit/internal/services/dns/zone/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,40 @@ func TestMapFields(t *testing.T) {
},
true,
},
{
"preserve_trailing_dot",
Model{
ProjectId: types.StringValue("pid"),
ZoneId: types.StringValue("zid"),
DnsName: types.StringValue("example.com."),
},
&dns.ZoneResponse{
Zone: &dns.Zone{
Id: utils.Ptr("zid"),
DnsName: utils.Ptr("example.com"),
},
},
Model{
Id: types.StringValue("pid,zid"),
ProjectId: types.StringValue("pid"),
ZoneId: types.StringValue("zid"),
Name: types.StringNull(),
DnsName: types.StringValue("example.com."),
Acl: types.StringNull(),
DefaultTTL: types.Int64Null(),
ExpireTime: types.Int64Null(),
RefreshTime: types.Int64Null(),
RetryTime: types.Int64Null(),
SerialNumber: types.Int64Null(),
NegativeCache: types.Int64Null(),
Type: types.StringValue(""),
State: types.StringValue(""),
PrimaryNameServer: types.StringNull(),
Primaries: types.ListNull(types.StringType),
Visibility: types.StringValue(""),
},
true,
},
{
"nullable_fields_and_int_conversions_ok",
Model{
Expand Down Expand Up @@ -345,6 +379,19 @@ func TestToCreatePayload(t *testing.T) {
nil,
false,
},
{
"with_trailing_dot",
&Model{
Name: types.StringValue("Name"),
DnsName: types.StringValue("example.com."),
},
&dns.CreateZonePayload{
Name: utils.Ptr("Name"),
DnsName: utils.Ptr("example.com"),
Primaries: &[]string{},
},
true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
Expand Down
Loading