Skip to content
Closed
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
10 changes: 9 additions & 1 deletion internal/controller/dnszone_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ func normalizeStringSlice(in []string) []string {
if len(in) == 0 {
return nil
}
out := append([]string(nil), in...)
seen := make(map[string]struct{}, len(in))
out := make([]string, 0, len(in))
for _, s := range in {
if _, ok := seen[s]; ok {
continue
}
seen[s] = struct{}{}
out = append(out, s)
}
sort.Strings(out)
return out
}
Expand Down
8 changes: 8 additions & 0 deletions internal/controller/dnszone_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ func TestNormalizeStringSlice(t *testing.T) {
if reflect.DeepEqual(in, want) {
t.Fatalf("expected input slice to remain unsorted")
}

// Duplicates are removed: a class listing each nameserver twice must not
// produce a doubled NS RRset (PowerDNS rejects duplicate records, 422).
dupIn := []string{"ns2", "ns1", "ns2", "ns1", "ns1"}
dupWant := []string{"ns1", "ns2"}
if dupGot := normalizeStringSlice(dupIn); !reflect.DeepEqual(dupGot, dupWant) {
t.Fatalf("expected dedup: got=%v want=%v", dupGot, dupWant)
}
}

func TestNormalizeDomainNameservers(t *testing.T) {
Expand Down
27 changes: 23 additions & 4 deletions internal/pdns/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,18 @@ type createZoneRequest struct {
func (c *Client) CreateZone(ctx context.Context, zone string, nameservers []string) error {
// PDNS expects absolute nameserver hostnames (trailing dot)
nsAbs := make([]string, 0, len(nameservers))
seen := make(map[string]struct{}, len(nameservers))
for _, ns := range nameservers {
if ns == "" {
continue
}
if ns[len(ns)-1] != '.' {
ns += "."
}
if _, ok := seen[ns]; ok {
continue
}
seen[ns] = struct{}{}
nsAbs = append(nsAbs, ns)
}
payload := createZoneRequest{
Expand Down Expand Up @@ -516,10 +521,15 @@ func buildRRSets(zone string, rs dnsv1alpha1.DNSRecordSet) []rrset {
}
v := strings.TrimSpace(rec.NS.Content)
if v != "" {
r.Records = append(r.Records, rrsetRecord{
Content: qualifyIfNeeded(v),
Disabled: false,
})
// Dedupe by qualified content: PowerDNS rejects an RRset with
// duplicate records (422), and duplicates reach here when the
// nameserver source lists an NS twice, including trailing-dot
// variants (ns1.example.net vs ns1.example.net.) that collapse
// to identical content only after qualifyIfNeeded.
content := qualifyIfNeeded(v)
if !recordContentExists(r.Records, content) {
r.Records = append(r.Records, rrsetRecord{Content: content, Disabled: false})
}
}

case dnsv1alpha1.RRTypeSOA:
Expand Down Expand Up @@ -730,6 +740,15 @@ func makeSimpleRRSet(name, typ string, ttl int, values []string) rrset {
}
}

func recordContentExists(recs []rrsetRecord, content string) bool {
for _, r := range recs {
if r.Content == content {
return true
}
}
return false
}

func qualifyOwner(owner, zone string) string {
if owner == "@" || owner == "" {
return zone + "."
Expand Down
29 changes: 29 additions & 0 deletions internal/pdns/pdns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,35 @@ func TestApplyRecordSetAuthoritative_PathAndHeaders(t *testing.T) {
}
}

// Duplicate NS content (including trailing-dot variants) must collapse to a
// single record; PowerDNS rejects an RRset with duplicate records (422).
func TestBuildRRSets_NSDedup(t *testing.T) {
t.Parallel()
rs := dnsv1alpha1.DNSRecordSet{
Spec: dnsv1alpha1.DNSRecordSetSpec{
RecordType: dnsv1alpha1.RRTypeNS,
Records: []dnsv1alpha1.RecordEntry{
{Name: "@", NS: &dnsv1alpha1.NSRecordSpec{Content: "ns1.example.net."}},
{Name: "@", NS: &dnsv1alpha1.NSRecordSpec{Content: "ns1.example.net"}}, // trailing-dot variant
{Name: "@", NS: &dnsv1alpha1.NSRecordSpec{Content: "ns1.example.net."}}, // exact dup
{Name: "@", NS: &dnsv1alpha1.NSRecordSpec{Content: "ns2.example.net."}},
},
},
}
rr := buildRRSets("example.com", rs)
if len(rr) != 1 {
t.Fatalf("expected 1 rrset, got %#v", rr)
}
got := []string{}
for _, rec := range rr[0].Records {
got = append(got, rec.Content)
}
want := []string{"ns1.example.net.", "ns2.example.net."}
if !reflect.DeepEqual(got, want) {
t.Fatalf("NS dedup: got %#v want %#v", got, want)
}
}

// sanity: makeSimpleRRSet keeps values verbatim (used after we normalize)
func TestMakeSimpleRRSet(t *testing.T) {
t.Parallel()
Expand Down
Loading