Skip to content

Commit 9f16d83

Browse files
feat(txt-registry): deprecate legacy txt-format (#5172)
* feat(txt-registry): only support single format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): only support single format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): only support single format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): only support single format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): only support single format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Co-authored-by: Michel Loiseleur <[email protected]> * feat(txt-registry): deprecate legacy txt-format Co-authored-by: Michel Loiseleur <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): address review comments Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format * feat(txt-registry): deprecate legacy txt-format Co-authored-by: Michel Loiseleur <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> * feat(txt-registry): deprecate legacy txt-format Signed-off-by: ivan katliarchuk <[email protected]> --------- Signed-off-by: ivan katliarchuk <[email protected]> Co-authored-by: Michel Loiseleur <[email protected]>
1 parent 3c8f774 commit 9f16d83

File tree

13 files changed

+361
-173
lines changed

13 files changed

+361
-173
lines changed

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ insert_final_newline = true
2020
[{Makefile,go.mod,go.sum,*.go}]
2121
indent_style = tab
2222
indent_size = 4
23+
24+
[*.py]
25+
indent_style = space
26+
indent_size = 4

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ repos:
2323
rev: v0.44.0
2424
hooks:
2525
- id: markdownlint
26+
args: ["--fix"]
2627

2728
minimum_pre_commit_version: !!str 3.2

controller/execute.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ func selectRegistry(cfg *externaldns.Config, p provider.Provider) (registry.Regi
395395
case "noop":
396396
r, err = registry.NewNoopRegistry(p)
397397
case "txt":
398-
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey), cfg.TXTNewFormatOnly)
398+
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey))
399399
case "aws-sd":
400400
r, err = registry.NewAWSSDRegistry(p, cfg.TXTOwnerID)
401401
default:

controller/execute_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ func TestSelectRegistry(t *testing.T) {
8383
TXTWildcardReplacement: "wildcard",
8484
ManagedDNSRecordTypes: []string{"A", "CNAME"},
8585
ExcludeDNSRecordTypes: []string{"TXT"},
86-
TXTNewFormatOnly: true,
8786
},
8887
provider: &MockProvider{},
8988
wantErr: false,

docs/flags.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@
165165
| `--txt-wildcard-replacement=""` | When using the TXT registry, a custom string that's used instead of an asterisk for TXT records corresponding to wildcard DNS records (optional) |
166166
| `--[no-]txt-encrypt-enabled` | When using the TXT registry, set if TXT records should be encrypted before stored (default: disabled) |
167167
| `--txt-encrypt-aes-key=""` | When using the TXT registry, set TXT record decryption and encryption 32 byte aes key (required when --txt-encrypt=true) |
168-
| `--[no-]txt-new-format-only` | When using the TXT registry, only use new format records which include record type information (e.g., prefix: 'a-'). Reduces number of TXT records (default: disabled) |
169168
| `--dynamodb-region=""` | When using the DynamoDB registry, the AWS region of the DynamoDB table (optional) |
170169
| `--dynamodb-table="external-dns"` | When using the DynamoDB registry, the name of the DynamoDB table (default: "external-dns") |
171170
| `--txt-cache-interval=0s` | The interval between cache synchronizations in duration format (default: disabled) |

docs/registry/txt.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,51 @@
33
The TXT registry is the default registry.
44
It stores DNS record metadata in TXT records, using the same provider.
55

6+
If you plan to manage apex domains with external-dns whilst using a txt registry, you should ensure when using --txt-prefix that you specify the record type substitution and that it ends in a period (**.**). The record should be created under the same domain as the apex record being managed, i.e. --txt-prefix=someprefix-%{record_type}.
7+
8+
> Note: `--txt-prefix` and `--txt-suffix` contribute to the 63-byte maximum record length. To avoid errors, use them only if absolutely required and keep them as short as possible.
9+
610
## Record Format Options
711

12+
### For version `v0.18+`
13+
14+
The TXT registry supports single format for storing DNS record metadata:
15+
16+
- Creates a TXT record with record type information (e.g., 'a-' prefix for A records)
17+
18+
The TXT registry would try to guarantee a consistency in between providers and sources, if provider supports the behaviour.
19+
20+
If you are dealing with APEX domains, like `example.com` and TXT records are failing to be created for managed record types specified by `--managed-record-types`, consider following options:
21+
22+
1. TXT record with prefix based on requirements. Example `--txt-prefix="%{record_type}-abc-"` or `--txt-prefix="%{record_type}.abc-"`
23+
2. TXT record with suffix based on requirements. Example `--txt-suffix="-abc-%{record_type}"` or `--txt-suffix="-abc.%{record_type}."`
24+
25+
If configured `--txt-prefix="%{record_type}-abc-"` for apex domain `ex.com` the expected result is
26+
27+
| Name | TYPE |
28+
|:------------------------------:|:-------:|
29+
| `cname-a-abc-nginx-v2.ex.com.` | `TXT` |
30+
| `nginx-v2.ex.com.` | `CNAME` |
31+
32+
If configured `--txt-suffix="-abc.%{record_type}"` for apex domain `ex.com` the expected result is
33+
34+
| Name | TYPE |
35+
|:------------------------------:|:-------:|
36+
| `cname-nginx-v2-abc.a.ex.com.` | `TXT` |
37+
| `nginx-v3.ex.com.` | `CNAME` |
38+
39+
### Manually Cleanup Legacy TXT Records
40+
41+
> While deleting registry TXT records won't cause downtime, a well-thought-out migration and cleanup plan is crucial.
42+
43+
Occasionally, it may be necessary to remove outdated TXT records from your registry.
44+
45+
An example script for AWS can be found in [scripts/aws-cleanup-legacy-txt-records.py](../../scripts/aws-cleanup-legacy-txt-records.py) with instructions on how to run it.
46+
The script performs targeted deletion of TXT records that include `ResourceRecords` matching the `heritage=external-dns,external-dns/owner=default` or similar pattern.
47+
In the event of unintended deletion of all TXT records managed by `external-dns`, `external-dns` will initiate a full DNS record regeneration, along with`TXT` and `non-TXT` records. Just be aware, this operation's duration is directly proportional to the DNS estate size."
48+
49+
### For version `v0.16.0 & v0.16.1`
50+
851
The TXT registry supports two formats for storing DNS record metadata:
952

1053
- Legacy format: Creates a TXT record without record type information
@@ -31,14 +74,14 @@ The `--txt-new-format-only` flag should be used in addition to your existing ext
3174

3275
### Migration to New Format Only
3376

77+
> Note: `external-dns` will not automatically remove legacy format records when switching to new-format-only mode. You'll need to clean up the old records manually if desired.
78+
3479
When transitioning from dual-format to new-format-only records:
3580

3681
- Ensure all your `external-dns` instances support the new format
3782
- Enable the `--txt-new-format-only` flag on your external-dns instances
3883
Manually clean up any existing legacy format TXT records from your DNS provider
3984

40-
Note: `external-dns` will not automatically remove legacy format records when switching to new-format-only mode. You'll need to clean up the old records manually if desired.
41-
4285
## Prefixes and Suffixes
4386

4487
In order to avoid having the registry TXT records collide with

pkg/apis/externaldns/types.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ type Config struct {
146146
TXTSuffix string
147147
TXTEncryptEnabled bool
148148
TXTEncryptAESKey string `secure:"yes"`
149-
TXTNewFormatOnly bool
150149
Interval time.Duration
151150
MinEventSyncInterval time.Duration
152151
Once bool
@@ -367,7 +366,6 @@ var defaultConfig = &Config{
367366
TXTCacheInterval: 0,
368367
TXTEncryptAESKey: "",
369368
TXTEncryptEnabled: false,
370-
TXTNewFormatOnly: false,
371369
TXTOwnerID: "default",
372370
TXTPrefix: "",
373371
TXTSuffix: "",
@@ -625,7 +623,6 @@ func App(cfg *Config) *kingpin.Application {
625623
app.Flag("txt-wildcard-replacement", "When using the TXT registry, a custom string that's used instead of an asterisk for TXT records corresponding to wildcard DNS records (optional)").Default(defaultConfig.TXTWildcardReplacement).StringVar(&cfg.TXTWildcardReplacement)
626624
app.Flag("txt-encrypt-enabled", "When using the TXT registry, set if TXT records should be encrypted before stored (default: disabled)").BoolVar(&cfg.TXTEncryptEnabled)
627625
app.Flag("txt-encrypt-aes-key", "When using the TXT registry, set TXT record decryption and encryption 32 byte aes key (required when --txt-encrypt=true)").Default(defaultConfig.TXTEncryptAESKey).StringVar(&cfg.TXTEncryptAESKey)
628-
app.Flag("txt-new-format-only", "When using the TXT registry, only use new format records which include record type information (e.g., prefix: 'a-'). Reduces number of TXT records (default: disabled)").BoolVar(&cfg.TXTNewFormatOnly)
629626
app.Flag("dynamodb-region", "When using the DynamoDB registry, the AWS region of the DynamoDB table (optional)").Default(cfg.AWSDynamoDBRegion).StringVar(&cfg.AWSDynamoDBRegion)
630627
app.Flag("dynamodb-table", "When using the DynamoDB registry, the name of the DynamoDB table (default: \"external-dns\")").Default(defaultConfig.AWSDynamoDBTable).StringVar(&cfg.AWSDynamoDBTable)
631628

pkg/apis/externaldns/types_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ var (
101101
TXTOwnerID: "default",
102102
TXTPrefix: "",
103103
TXTCacheInterval: 0,
104-
TXTNewFormatOnly: false,
105104
Interval: time.Minute,
106105
MinEventSyncInterval: 5 * time.Second,
107106
Once: false,
@@ -214,7 +213,6 @@ var (
214213
TXTOwnerID: "owner-1",
215214
TXTPrefix: "associated-txt-record",
216215
TXTCacheInterval: 12 * time.Hour,
217-
TXTNewFormatOnly: true,
218216
Interval: 10 * time.Minute,
219217
MinEventSyncInterval: 50 * time.Second,
220218
Once: true,
@@ -359,7 +357,6 @@ func TestParseFlags(t *testing.T) {
359357
"--txt-owner-id=owner-1",
360358
"--txt-prefix=associated-txt-record",
361359
"--txt-cache-interval=12h",
362-
"--txt-new-format-only",
363360
"--dynamodb-table=custom-table",
364361
"--interval=10m",
365362
"--min-event-sync-interval=50s",

registry/txt.go

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ type TXTRegistry struct {
5858
// encrypt text records
5959
txtEncryptEnabled bool
6060
txtEncryptAESKey []byte
61-
62-
newFormatOnly bool
6361
}
6462

6563
// NewTXTRegistry returns a new TXTRegistry object. When newFormatOnly is true, it will only
@@ -68,8 +66,7 @@ type TXTRegistry struct {
6866
func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string,
6967
cacheInterval time.Duration, txtWildcardReplacement string,
7068
managedRecordTypes, excludeRecordTypes []string,
71-
txtEncryptEnabled bool, txtEncryptAESKey []byte,
72-
newFormatOnly bool) (*TXTRegistry, error) {
69+
txtEncryptEnabled bool, txtEncryptAESKey []byte) (*TXTRegistry, error) {
7370
if ownerID == "" {
7471
return nil, errors.New("owner id cannot be empty")
7572
}
@@ -103,7 +100,6 @@ func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID st
103100
excludeRecordTypes: excludeRecordTypes,
104101
txtEncryptEnabled: txtEncryptEnabled,
105102
txtEncryptAESKey: txtEncryptAESKey,
106-
newFormatOnly: newFormatOnly,
107103
}, nil
108104
}
109105

@@ -236,25 +232,13 @@ func (im *TXTRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, error
236232
func (im *TXTRegistry) generateTXTRecord(r *endpoint.Endpoint) []*endpoint.Endpoint {
237233
endpoints := make([]*endpoint.Endpoint, 0)
238234

239-
// Create legacy format record by default unless newFormatOnly is true
240-
if !im.newFormatOnly && !im.txtEncryptEnabled && !im.mapper.recordTypeInAffix() && r.RecordType != endpoint.RecordTypeAAAA {
241-
// old TXT record format
242-
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), endpoint.RecordTypeTXT, r.Labels.Serialize(true, im.txtEncryptEnabled, im.txtEncryptAESKey))
243-
if txt != nil {
244-
txt.WithSetIdentifier(r.SetIdentifier)
245-
txt.Labels[endpoint.OwnedRecordLabelKey] = r.DNSName
246-
txt.ProviderSpecific = r.ProviderSpecific
247-
endpoints = append(endpoints, txt)
248-
}
249-
}
250-
251235
// Always create new format record
252236
recordType := r.RecordType
253237
// AWS Alias records are encoded as type "cname"
254238
if isAlias, found := r.GetProviderSpecificProperty("alias"); found && isAlias == "true" && recordType == endpoint.RecordTypeA {
255239
recordType = endpoint.RecordTypeCNAME
256240
}
257-
txtNew := endpoint.NewEndpoint(im.mapper.toNewTXTName(r.DNSName, recordType), endpoint.RecordTypeTXT, r.Labels.Serialize(true, im.txtEncryptEnabled, im.txtEncryptAESKey))
241+
txtNew := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName, recordType), endpoint.RecordTypeTXT, r.Labels.Serialize(true, im.txtEncryptEnabled, im.txtEncryptAESKey))
258242
if txtNew != nil {
259243
txtNew.WithSetIdentifier(r.SetIdentifier)
260244
txtNew.Labels[endpoint.OwnedRecordLabelKey] = r.DNSName
@@ -336,8 +320,7 @@ func (im *TXTRegistry) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpo
336320

337321
type nameMapper interface {
338322
toEndpointName(string) (endpointName string, recordType string)
339-
toTXTName(string) string
340-
toNewTXTName(string, string) string
323+
toTXTName(string, string) string
341324
recordTypeInAffix() bool
342325
}
343326

@@ -437,22 +420,6 @@ func (pr affixNameMapper) toEndpointName(txtDNSName string) (endpointName string
437420
return "", ""
438421
}
439422

440-
func (pr affixNameMapper) toTXTName(endpointDNSName string) string {
441-
DNSName := strings.SplitN(endpointDNSName, ".", 2)
442-
443-
prefix := pr.dropAffixTemplate(pr.prefix)
444-
suffix := pr.dropAffixTemplate(pr.suffix)
445-
// If specified, replace a leading asterisk in the generated txt record name with some other string
446-
if pr.wildcardReplacement != "" && DNSName[0] == "*" {
447-
DNSName[0] = pr.wildcardReplacement
448-
}
449-
450-
if len(DNSName) < 2 {
451-
return prefix + DNSName[0] + suffix
452-
}
453-
return prefix + DNSName[0] + suffix + "." + DNSName[1]
454-
}
455-
456423
func (pr affixNameMapper) recordTypeInAffix() bool {
457424
if strings.Contains(pr.prefix, recordTemplate) {
458425
return true
@@ -470,7 +437,7 @@ func (pr affixNameMapper) normalizeAffixTemplate(afix, recordType string) string
470437
return afix
471438
}
472439

473-
func (pr affixNameMapper) toNewTXTName(endpointDNSName, recordType string) string {
440+
func (pr affixNameMapper) toTXTName(endpointDNSName, recordType string) string {
474441
DNSName := strings.SplitN(endpointDNSName, ".", 2)
475442
recordType = strings.ToLower(recordType)
476443
recordT := recordType + "-"

registry/txt_encryption_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func TestNewTXTRegistryEncryptionConfig(t *testing.T) {
6161
},
6262
}
6363
for _, test := range tests {
64-
actual, err := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, test.encEnabled, test.aesKeyRaw, false)
64+
actual, err := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, test.encEnabled, test.aesKeyRaw)
6565
if test.errorExpected {
6666
require.Error(t, err)
6767
} else {
@@ -107,7 +107,7 @@ func TestGenerateTXTGenerateTextRecordEncryptionWihDecryption(t *testing.T) {
107107
for _, k := range withEncryptionKeys {
108108
t.Run(fmt.Sprintf("key '%s' with decrypted result '%s'", k, test.decrypted), func(t *testing.T) {
109109
key := []byte(k)
110-
r, err := NewTXTRegistry(p, "", "", "owner", time.Minute, "", []string{}, []string{}, true, key, false)
110+
r, err := NewTXTRegistry(p, "", "", "owner", time.Minute, "", []string{}, []string{}, true, key)
111111
assert.NoError(t, err, "Error creating TXT registry")
112112
txtRecords := r.generateTXTRecord(test.record)
113113
assert.Len(t, txtRecords, len(test.record.Targets))
@@ -144,7 +144,7 @@ func TestApplyRecordsWithEncryption(t *testing.T) {
144144

145145
key := []byte("ZPitL0NGVQBZbTD6DwXJzD8RiStSazzYXQsdUowLURY=")
146146

147-
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, key, false)
147+
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, key)
148148

149149
_ = r.ApplyChanges(ctx, &plan.Changes{
150150
Create: []*endpoint.Endpoint{
@@ -202,7 +202,7 @@ func TestApplyRecordsWithEncryptionKeyChanged(t *testing.T) {
202202
}
203203

204204
for _, key := range withEncryptionKeys {
205-
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key), false)
205+
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key))
206206
_ = r.ApplyChanges(ctx, &plan.Changes{
207207
Create: []*endpoint.Endpoint{
208208
newEndpointWithOwner("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner"),
@@ -232,7 +232,7 @@ func TestApplyRecordsOnEncryptionKeyChangeWithKeyIdLabel(t *testing.T) {
232232
}
233233

234234
for i, key := range withEncryptionKeys {
235-
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key), false)
235+
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key))
236236
keyId := fmt.Sprintf("key-id-%d", i)
237237
changes := []*endpoint.Endpoint{
238238
newEndpointWithOwnerAndOwnedRecordWithKeyIDLabel("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner", "", keyId),

0 commit comments

Comments
 (0)