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
45 changes: 43 additions & 2 deletions internal/stackitprovider/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,14 @@ func modifyChange(change *endpoint.Endpoint) {
func getStackitRecordSetPayload(change *endpoint.Endpoint) stackitdnsclient.CreateRecordSetPayload {
records := make([]stackitdnsclient.RecordPayload, len(change.Targets))
for i := range change.Targets {
content := change.Targets[i]

if change.RecordType == txtRecord {
content = formatTXTContent(content)
}

records[i] = stackitdnsclient.RecordPayload{
Content: change.Targets[i],
Content: content,
}
}

Expand All @@ -88,8 +94,14 @@ func getStackitRecordSetPayload(change *endpoint.Endpoint) stackitdnsclient.Crea
func getStackitPartialUpdateRecordSetPayload(change *endpoint.Endpoint) stackitdnsclient.PartialUpdateRecordSetPayload {
records := make([]stackitdnsclient.RecordPayload, len(change.Targets))
for i := range change.Targets {
content := change.Targets[i]

if change.RecordType == txtRecord {
content = formatTXTContent(content)
}

records[i] = stackitdnsclient.RecordPayload{
Content: change.Targets[i],
Content: content,
}
}

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

return &v
}

// formatTXTContent splits long TXT records into 255-character chunks separated by spaces.
func formatTXTContent(content string) string {
cleanContent := strings.Trim(content, `"`)

if len(cleanContent) <= 255 {
return content
}

var chunks []string
for i := 0; i < len(cleanContent); i += 255 {
end := i + 255
if end > len(cleanContent) {
end = len(cleanContent)
}
chunks = append(chunks, `"`+cleanContent[i:end]+`"`)
}

return strings.Join(chunks, " ")
}

// unformatTXTContent reverses the DNS chunking and quoting process.
func unformatTXTContent(content string) string {
if strings.Contains(content, `" "`) {
return strings.ReplaceAll(content, `" "`, ``)
}

return content
}
98 changes: 98 additions & 0 deletions internal/stackitprovider/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package stackitprovider

import (
"reflect"
"strings"
"testing"

stackitdnsclient "github.com/stackitcloud/stackit-sdk-go/services/dns/v1api"
Expand Down Expand Up @@ -214,3 +215,100 @@ func TestGetStackitRRSetRecordPatch(t *testing.T) {
t.Errorf("getStackitRRSetRecordPatch() = %v, want %v", got, expected)
}
}

func TestFormatTXTContent(t *testing.T) {
t.Parallel()

// Generate strings of exact lengths for testing
string255 := strings.Repeat("a", 255)
string256 := strings.Repeat("a", 256)
string511 := strings.Repeat("a", 511)

tests := []struct {
name string
content string
want string
}{
{
name: "Short string without quotes",
content: "hello world",
want: "hello world",
},
{
name: "Short string with existing quotes",
content: `"hello world"`,
want: `"hello world"`,
},
{
name: "Exactly 255 characters unquoted",
content: string255,
want: string255,
},
{
name: "Exactly 255 characters quoted",
content: `"` + string255 + `"`,
want: `"` + string255 + `"`,
},
{
name: "256 characters (requires 2 chunks)",
content: string256,
want: `"` + string255 + `" "a"`,
},
{
name: "511 characters (requires 3 chunks)",
content: string511,
want: `"` + string255 + `" "` + string255 + `" "a"`,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := formatTXTContent(tt.content); got != tt.want {
t.Errorf("formatTXTContent() = %v, want %v", got, tt.want)
}
})
}
}

func TestUnformatTXTContent(t *testing.T) {
t.Parallel()

tests := []struct {
name string
content string
want string
}{
{
name: "Unquoted short string",
content: "hello world",
want: "hello world",
},
{
name: "Single chunk quoted string",
content: `"hello world"`,
want: `"hello world"`,
},
{
name: "Two chunk string",
content: `"hello" "world"`,
want: `"helloworld"`,
},
{
name: "Three chunk string",
content: `"chunk1" "chunk2" "chunk3"`,
want: `"chunk1chunk2chunk3"`,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := unformatTXTContent(tt.content); got != tt.want {
t.Errorf("unformatTXTContent() = %v, want %v", got, tt.want)
}
})
}
}
9 changes: 8 additions & 1 deletion internal/stackitprovider/records.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"sigs.k8s.io/external-dns/provider"
)

const txtRecord = "TXT"

// Records returns resource records.
func (d *StackitDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
zones, err := d.zoneFetcherClient.zones(ctx)
Expand Down Expand Up @@ -114,7 +116,12 @@ func endpointsFromRecords(name, recordType string, ttl endpoint.TTL, records []s
for i := range records {
rec := &records[i]

endpoints = append(endpoints, endpoint.NewEndpointWithTTL(name, recordType, ttl, rec.Content))
content := rec.Content
if recordType == txtRecord {
content = unformatTXTContent(content)
}

endpoints = append(endpoints, endpoint.NewEndpointWithTTL(name, recordType, ttl, content))
}

return endpoints
Expand Down
Loading