Skip to content

Commit 94c11ba

Browse files
authored
Merge pull request #64 from stackitcloud/fix/split-long-txt-records
fix: split long TXT records into 255-character chunks - some users experienced issues when external dns tried to create ownership records with very long owner_id and/or domain. this fixes it in dns-native way.
2 parents 848271b + 980846a commit 94c11ba

3 files changed

Lines changed: 149 additions & 3 deletions

File tree

internal/stackitprovider/helper.go

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,14 @@ func modifyChange(change *endpoint.Endpoint) {
7171
func getStackitRecordSetPayload(change *endpoint.Endpoint) stackitdnsclient.CreateRecordSetPayload {
7272
records := make([]stackitdnsclient.RecordPayload, len(change.Targets))
7373
for i := range change.Targets {
74+
content := change.Targets[i]
75+
76+
if change.RecordType == txtRecord {
77+
content = formatTXTContent(content)
78+
}
79+
7480
records[i] = stackitdnsclient.RecordPayload{
75-
Content: change.Targets[i],
81+
Content: content,
7682
}
7783
}
7884

@@ -88,8 +94,14 @@ func getStackitRecordSetPayload(change *endpoint.Endpoint) stackitdnsclient.Crea
8894
func getStackitPartialUpdateRecordSetPayload(change *endpoint.Endpoint) stackitdnsclient.PartialUpdateRecordSetPayload {
8995
records := make([]stackitdnsclient.RecordPayload, len(change.Targets))
9096
for i := range change.Targets {
97+
content := change.Targets[i]
98+
99+
if change.RecordType == txtRecord {
100+
content = formatTXTContent(content)
101+
}
102+
91103
records[i] = stackitdnsclient.RecordPayload{
92-
Content: change.Targets[i],
104+
Content: content,
93105
}
94106
}
95107

@@ -126,3 +138,32 @@ func safeTTLToInt32(ttl endpoint.TTL) *int32 {
126138

127139
return &v
128140
}
141+
142+
// formatTXTContent splits long TXT records into 255-character chunks separated by spaces.
143+
func formatTXTContent(content string) string {
144+
cleanContent := strings.Trim(content, `"`)
145+
146+
if len(cleanContent) <= 255 {
147+
return content
148+
}
149+
150+
var chunks []string
151+
for i := 0; i < len(cleanContent); i += 255 {
152+
end := i + 255
153+
if end > len(cleanContent) {
154+
end = len(cleanContent)
155+
}
156+
chunks = append(chunks, `"`+cleanContent[i:end]+`"`)
157+
}
158+
159+
return strings.Join(chunks, " ")
160+
}
161+
162+
// unformatTXTContent reverses the DNS chunking and quoting process.
163+
func unformatTXTContent(content string) string {
164+
if strings.Contains(content, `" "`) {
165+
return strings.ReplaceAll(content, `" "`, ``)
166+
}
167+
168+
return content
169+
}

internal/stackitprovider/helper_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package stackitprovider
22

33
import (
44
"reflect"
5+
"strings"
56
"testing"
67

78
stackitdnsclient "github.com/stackitcloud/stackit-sdk-go/services/dns/v1api"
@@ -214,3 +215,100 @@ func TestGetStackitRRSetRecordPatch(t *testing.T) {
214215
t.Errorf("getStackitRRSetRecordPatch() = %v, want %v", got, expected)
215216
}
216217
}
218+
219+
func TestFormatTXTContent(t *testing.T) {
220+
t.Parallel()
221+
222+
// Generate strings of exact lengths for testing
223+
string255 := strings.Repeat("a", 255)
224+
string256 := strings.Repeat("a", 256)
225+
string511 := strings.Repeat("a", 511)
226+
227+
tests := []struct {
228+
name string
229+
content string
230+
want string
231+
}{
232+
{
233+
name: "Short string without quotes",
234+
content: "hello world",
235+
want: "hello world",
236+
},
237+
{
238+
name: "Short string with existing quotes",
239+
content: `"hello world"`,
240+
want: `"hello world"`,
241+
},
242+
{
243+
name: "Exactly 255 characters unquoted",
244+
content: string255,
245+
want: string255,
246+
},
247+
{
248+
name: "Exactly 255 characters quoted",
249+
content: `"` + string255 + `"`,
250+
want: `"` + string255 + `"`,
251+
},
252+
{
253+
name: "256 characters (requires 2 chunks)",
254+
content: string256,
255+
want: `"` + string255 + `" "a"`,
256+
},
257+
{
258+
name: "511 characters (requires 3 chunks)",
259+
content: string511,
260+
want: `"` + string255 + `" "` + string255 + `" "a"`,
261+
},
262+
}
263+
264+
for _, tt := range tests {
265+
tt := tt
266+
t.Run(tt.name, func(t *testing.T) {
267+
t.Parallel()
268+
if got := formatTXTContent(tt.content); got != tt.want {
269+
t.Errorf("formatTXTContent() = %v, want %v", got, tt.want)
270+
}
271+
})
272+
}
273+
}
274+
275+
func TestUnformatTXTContent(t *testing.T) {
276+
t.Parallel()
277+
278+
tests := []struct {
279+
name string
280+
content string
281+
want string
282+
}{
283+
{
284+
name: "Unquoted short string",
285+
content: "hello world",
286+
want: "hello world",
287+
},
288+
{
289+
name: "Single chunk quoted string",
290+
content: `"hello world"`,
291+
want: `"hello world"`,
292+
},
293+
{
294+
name: "Two chunk string",
295+
content: `"hello" "world"`,
296+
want: `"helloworld"`,
297+
},
298+
{
299+
name: "Three chunk string",
300+
content: `"chunk1" "chunk2" "chunk3"`,
301+
want: `"chunk1chunk2chunk3"`,
302+
},
303+
}
304+
305+
for _, tt := range tests {
306+
tt := tt
307+
t.Run(tt.name, func(t *testing.T) {
308+
t.Parallel()
309+
if got := unformatTXTContent(tt.content); got != tt.want {
310+
t.Errorf("unformatTXTContent() = %v, want %v", got, tt.want)
311+
}
312+
})
313+
}
314+
}

internal/stackitprovider/records.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"sigs.k8s.io/external-dns/provider"
99
)
1010

11+
const txtRecord = "TXT"
12+
1113
// Records returns resource records.
1214
func (d *StackitDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
1315
zones, err := d.zoneFetcherClient.zones(ctx)
@@ -114,7 +116,12 @@ func endpointsFromRecords(name, recordType string, ttl endpoint.TTL, records []s
114116
for i := range records {
115117
rec := &records[i]
116118

117-
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(name, recordType, ttl, rec.Content))
119+
content := rec.Content
120+
if recordType == txtRecord {
121+
content = unformatTXTContent(content)
122+
}
123+
124+
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(name, recordType, ttl, content))
118125
}
119126

120127
return endpoints

0 commit comments

Comments
 (0)