mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2026-05-04 22:26:11 +02:00
feat: add new flags to allow migration of OwnerID (#4823)
* Reintroduce base config for txt owner migration # Conflicts: # controller/execute.go # registry/txt.go # Conflicts: # pkg/apis/externaldns/types.go * Added label update logic and fixed existing tests * Fixed existing declaration in tests, re introduced tests for new flag, regened flags.md from make * Fixed tests logic and target expression evaluation, fixed update of label in the TXT registry process * Set Old owner id var down the plan to calculate changes correctly * Lint fixes * (wip) Code cleaning and test coverage * Simplified label overwriting on migration and implem tests for coverage * Fix tests * Update txt registry doc * Fix rebase issues in txt test * Update docs/registry/txt.md Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> * Update docs/registry/txt.md Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> * Update docs/registry/txt.md Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> * Fix label overriding in TXT record generation when migration is enabled * Make linter happy * Regen flags, fix types tests after types updates * Removed boolean flag that enabled migration, evaluate only against old owner flag instead --------- Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>
This commit is contained in:
parent
7fd53667de
commit
41b1154cdd
@ -182,6 +182,8 @@ type Controller struct {
|
||||
ExcludeRecordTypes []string
|
||||
// MinEventSyncInterval is used as a window for batching events
|
||||
MinEventSyncInterval time.Duration
|
||||
// Old txt-owner value we need to migrate from
|
||||
TXTOwnerOld string
|
||||
}
|
||||
|
||||
// RunOnce runs a single iteration of a reconciliation loop.
|
||||
@ -236,6 +238,7 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
||||
ManagedRecords: c.ManagedRecordTypes,
|
||||
ExcludeRecords: c.ExcludeRecordTypes,
|
||||
OwnerID: c.Registry.OwnerID(),
|
||||
OldOwnerId: c.TXTOwnerOld,
|
||||
}
|
||||
|
||||
plan = plan.Calculate()
|
||||
|
||||
@ -382,6 +382,7 @@ func buildController(
|
||||
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
|
||||
ExcludeRecordTypes: cfg.ExcludeDNSRecordTypes,
|
||||
MinEventSyncInterval: cfg.MinEventSyncInterval,
|
||||
TXTOwnerOld: cfg.TXTOwnerOld,
|
||||
EventEmitter: eventEmitter,
|
||||
}, nil
|
||||
}
|
||||
@ -418,7 +419,7 @@ func selectRegistry(cfg *externaldns.Config, p provider.Provider) (registry.Regi
|
||||
case "noop":
|
||||
r, err = registry.NewNoopRegistry(p)
|
||||
case "txt":
|
||||
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey))
|
||||
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.TXTOwnerOld)
|
||||
case "aws-sd":
|
||||
r, err = registry.NewAWSSDRegistry(p, cfg.TXTOwnerID)
|
||||
default:
|
||||
|
||||
@ -164,6 +164,7 @@
|
||||
| `--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) |
|
||||
| `--[no-]txt-encrypt-enabled` | When using the TXT registry, set if TXT records should be encrypted before stored (default: disabled) |
|
||||
| `--txt-encrypt-aes-key=""` | When using the TXT registry, set TXT record decryption and encryption 32 byte aes key (required when --txt-encrypt=true) |
|
||||
| `--migrate-from-txt-owner=""` | Old txt-owner-id that needs to be overwritten (default: default) |
|
||||
| `--dynamodb-region=""` | When using the DynamoDB registry, the AWS region of the DynamoDB table (optional) |
|
||||
| `--dynamodb-table="external-dns"` | When using the DynamoDB registry, the name of the DynamoDB table (default: "external-dns") |
|
||||
| `--txt-cache-interval=0s` | The interval between cache synchronizations in duration format (default: disabled) |
|
||||
|
||||
@ -205,3 +205,91 @@ The TXT registry can optionally cache DNS records read from the provider. This c
|
||||
rate limits imposed by the provider.
|
||||
|
||||
Caching is enabled by specifying a cache duration with the `--txt-cache-interval` flag.
|
||||
|
||||
## OwnerID migration
|
||||
|
||||
The owner ID of the TXT records managed by external-dns instance can be updated.
|
||||
|
||||
When `--migrate-from-txt-owner` is set, it will enable the migration checks
|
||||
in the run loop using `--txt-owner-id=new-owner-id` and the value you defined for this flag.
|
||||
|
||||
If you want to test the outputs of a migration beforehand, you can use the `--dry-run` flag
|
||||
along with `--migrate-from-txt-owner`.
|
||||
|
||||
Example, if you had a standard deployment like so:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.k8s.io/external-dns/external-dns:v0.19.0
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "--txt-prefix=%{record_type}-"
|
||||
- "--txt-cache-interval=2m"
|
||||
- "--log-level=debug"
|
||||
- "--log-format=text"
|
||||
- "--txt-owner-id=old-owner"
|
||||
- "--policy=sync"
|
||||
- "--provider=some-provider"
|
||||
- "--registry=txt"
|
||||
- "--interval=1m"
|
||||
- "--source=ingress"
|
||||
```
|
||||
|
||||
You can update your deployment to migrate like so :
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
imagePullPolicy: Always
|
||||
image: registry.k8s.io/external-dns/external-dns:v0.19.0
|
||||
args:
|
||||
- "--txt-prefix=%{record_type}-"
|
||||
- "--txt-cache-interval=2m"
|
||||
- "--log-level=debug"
|
||||
- "--log-format=text"
|
||||
- "--txt-owner-id=new-owner"
|
||||
- "--migrate-from-txt-owner=old-owner"
|
||||
- "--policy=sync"
|
||||
- "--provider=some-provider"
|
||||
- "--registry=txt"
|
||||
- "--interval=1m"
|
||||
- "--source=ingress"
|
||||
```
|
||||
|
||||
If you didn't set the owner ID, the value set by external-dns is `default`. You can set the
|
||||
`--migrate-from-txt-owner` flag to `default` to migrate the associated records.
|
||||
|
||||
@ -142,6 +142,7 @@ type Config struct {
|
||||
Policy string
|
||||
Registry string
|
||||
TXTOwnerID string
|
||||
TXTOwnerOld string
|
||||
TXTPrefix string
|
||||
TXTSuffix string
|
||||
TXTEncryptEnabled bool
|
||||
@ -371,6 +372,7 @@ var defaultConfig = &Config{
|
||||
TXTEncryptAESKey: "",
|
||||
TXTEncryptEnabled: false,
|
||||
TXTOwnerID: "default",
|
||||
TXTOwnerOld: "",
|
||||
TXTPrefix: "",
|
||||
TXTSuffix: "",
|
||||
TXTWildcardReplacement: "",
|
||||
@ -782,6 +784,7 @@ func bindFlags(b FlagBinder, cfg *Config) {
|
||||
b.StringVar("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)", defaultConfig.TXTWildcardReplacement, &cfg.TXTWildcardReplacement)
|
||||
b.BoolVar("txt-encrypt-enabled", "When using the TXT registry, set if TXT records should be encrypted before stored (default: disabled)", defaultConfig.TXTEncryptEnabled, &cfg.TXTEncryptEnabled)
|
||||
b.StringVar("txt-encrypt-aes-key", "When using the TXT registry, set TXT record decryption and encryption 32 byte aes key (required when --txt-encrypt=true)", defaultConfig.TXTEncryptAESKey, &cfg.TXTEncryptAESKey)
|
||||
b.StringVar("migrate-from-txt-owner", "Old txt-owner-id that needs to be overwritten (default: default)", defaultConfig.TXTOwnerOld, &cfg.TXTOwnerOld)
|
||||
b.StringVar("dynamodb-region", "When using the DynamoDB registry, the AWS region of the DynamoDB table (optional)", cfg.AWSDynamoDBRegion, &cfg.AWSDynamoDBRegion)
|
||||
b.StringVar("dynamodb-table", "When using the DynamoDB registry, the name of the DynamoDB table (default: \"external-dns\")", defaultConfig.AWSDynamoDBTable, &cfg.AWSDynamoDBTable)
|
||||
|
||||
|
||||
@ -104,6 +104,7 @@ var (
|
||||
Policy: "sync",
|
||||
Registry: "txt",
|
||||
TXTOwnerID: "default",
|
||||
TXTOwnerOld: "",
|
||||
TXTPrefix: "",
|
||||
TXTCacheInterval: 0,
|
||||
Interval: time.Minute,
|
||||
@ -217,6 +218,7 @@ var (
|
||||
Registry: "noop",
|
||||
TXTOwnerID: "owner-1",
|
||||
TXTPrefix: "associated-txt-record",
|
||||
TXTOwnerOld: "old-owner",
|
||||
TXTCacheInterval: 12 * time.Hour,
|
||||
Interval: 10 * time.Minute,
|
||||
MinEventSyncInterval: 50 * time.Second,
|
||||
@ -360,6 +362,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"--policy=upsert-only",
|
||||
"--registry=noop",
|
||||
"--txt-owner-id=owner-1",
|
||||
"--migrate-from-txt-owner=old-owner",
|
||||
"--txt-prefix=associated-txt-record",
|
||||
"--txt-cache-interval=12h",
|
||||
"--dynamodb-table=custom-table",
|
||||
@ -482,6 +485,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_REGISTRY": "noop",
|
||||
"EXTERNAL_DNS_TXT_OWNER_ID": "owner-1",
|
||||
"EXTERNAL_DNS_TXT_PREFIX": "associated-txt-record",
|
||||
"EXTERNAL_DNS_MIGRATE_FROM_TXT_OWNER": "old-owner",
|
||||
"EXTERNAL_DNS_TXT_CACHE_INTERVAL": "12h",
|
||||
"EXTERNAL_DNS_TXT_NEW_FORMAT_ONLY": "1",
|
||||
"EXTERNAL_DNS_INTERVAL": "10m",
|
||||
|
||||
@ -52,6 +52,8 @@ type Plan struct {
|
||||
ExcludeRecords []string
|
||||
// OwnerID of records to manage
|
||||
OwnerID string
|
||||
// Old owner ID we migrate from
|
||||
OldOwnerId string
|
||||
}
|
||||
|
||||
// Changes holds lists of actions to be executed by dns providers
|
||||
@ -224,7 +226,8 @@ func (p *Plan) Calculate() *Plan {
|
||||
if records.current != nil && len(records.candidates) > 0 {
|
||||
update := t.resolver.ResolveUpdate(records.current, records.candidates)
|
||||
|
||||
if shouldUpdateTTL(update, records.current) || targetChanged(update, records.current) || p.shouldUpdateProviderSpecific(update, records.current) {
|
||||
if shouldUpdateTTL(update, records.current) || targetChanged(update, records.current) || p.shouldUpdateProviderSpecific(update, records.current) ||
|
||||
p.isOldOwnerIdSetAndDifferent(records.current) {
|
||||
inheritOwner(records.current, update)
|
||||
changes.UpdateNew = append(changes.UpdateNew, update)
|
||||
changes.UpdateOld = append(changes.UpdateOld, records.current)
|
||||
@ -276,6 +279,10 @@ func (p *Plan) Calculate() *Plan {
|
||||
return plan
|
||||
}
|
||||
|
||||
func (p *Plan) isOldOwnerIdSetAndDifferent(current *endpoint.Endpoint) bool {
|
||||
return len(p.OldOwnerId) != 0 && current.Labels[endpoint.OwnerLabelKey] != p.OldOwnerId
|
||||
}
|
||||
|
||||
func inheritOwner(from, to *endpoint.Endpoint) {
|
||||
if to.Labels == nil {
|
||||
to.Labels = map[string]string{}
|
||||
|
||||
@ -1017,6 +1017,32 @@ func (suite *PlanTestSuite) TestDualStackToSingleStack() {
|
||||
validateEntries(suite.T(), changes.UpdateNew, expectNoChanges)
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestRecordOwnerIdMigration() {
|
||||
suite.fooA5.Labels[endpoint.OwnerLabelKey] = "bar"
|
||||
current := []*endpoint.Endpoint{suite.fooA5}
|
||||
desired := []*endpoint.Endpoint{suite.fooA5}
|
||||
expectedCreate := []*endpoint.Endpoint{}
|
||||
expectedUpdateOld := []*endpoint.Endpoint{suite.fooA5}
|
||||
expectedUpdateNew := []*endpoint.Endpoint{suite.fooA5}
|
||||
expectedDelete := []*endpoint.Endpoint{}
|
||||
|
||||
p := &Plan{
|
||||
Policies: []Policy{&SyncPolicy{}},
|
||||
Current: current,
|
||||
Desired: desired,
|
||||
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
|
||||
OwnerID: suite.fooA5.Labels[endpoint.OwnerLabelKey],
|
||||
OldOwnerId: "foo",
|
||||
}
|
||||
|
||||
changes := p.Calculate().Changes
|
||||
|
||||
validateEntries(suite.T(), changes.Create, expectedCreate)
|
||||
validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
|
||||
validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
|
||||
validateEntries(suite.T(), changes.Delete, expectedDelete)
|
||||
}
|
||||
|
||||
func TestPlan(t *testing.T) {
|
||||
suite.Run(t, new(PlanTestSuite))
|
||||
}
|
||||
|
||||
@ -60,6 +60,9 @@ type TXTRegistry struct {
|
||||
txtEncryptEnabled bool
|
||||
txtEncryptAESKey []byte
|
||||
|
||||
//Handle Owner ID migration
|
||||
oldOwnerID string
|
||||
|
||||
// existingTXTs is the TXT records that already exist in the zone so that
|
||||
// ApplyChanges() can skip re-creating them. See the struct below for details.
|
||||
existingTXTs *existingTXTs
|
||||
@ -114,7 +117,8 @@ func (im *existingTXTs) reset() {
|
||||
func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string,
|
||||
cacheInterval time.Duration, txtWildcardReplacement string,
|
||||
managedRecordTypes, excludeRecordTypes []string,
|
||||
txtEncryptEnabled bool, txtEncryptAESKey []byte) (*TXTRegistry, error) {
|
||||
txtEncryptEnabled bool, txtEncryptAESKey []byte,
|
||||
oldOwnerID string) (*TXTRegistry, error) {
|
||||
if ownerID == "" {
|
||||
return nil, errors.New("owner id cannot be empty")
|
||||
}
|
||||
@ -148,6 +152,7 @@ func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID st
|
||||
excludeRecordTypes: excludeRecordTypes,
|
||||
txtEncryptEnabled: txtEncryptEnabled,
|
||||
txtEncryptAESKey: txtEncryptAESKey,
|
||||
oldOwnerID: oldOwnerID,
|
||||
existingTXTs: newExistingTXTs(),
|
||||
}, nil
|
||||
}
|
||||
@ -252,6 +257,10 @@ func (im *TXTRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, error
|
||||
}
|
||||
}
|
||||
|
||||
if im.oldOwnerID != "" && ep.Labels[endpoint.OwnerLabelKey] == im.oldOwnerID {
|
||||
ep.Labels[endpoint.OwnerLabelKey] = im.ownerID
|
||||
}
|
||||
|
||||
// Handle the migration of TXT records created before the new format (introduced in v0.12.0).
|
||||
// The migration is done for the TXT records owned by this instance only.
|
||||
if len(txtRecordsMap) > 0 && ep.Labels[endpoint.OwnerLabelKey] == im.ownerID {
|
||||
@ -292,6 +301,11 @@ func (im *TXTRegistry) generateTXTRecordWithFilter(r *endpoint.Endpoint, filter
|
||||
if isAlias, found := r.GetProviderSpecificProperty("alias"); found && isAlias == "true" && recordType == endpoint.RecordTypeA {
|
||||
recordType = endpoint.RecordTypeCNAME
|
||||
}
|
||||
|
||||
if im.oldOwnerID != "" && r.Labels[endpoint.OwnerLabelKey] == im.oldOwnerID {
|
||||
r.Labels[endpoint.OwnerLabelKey] = im.ownerID
|
||||
}
|
||||
|
||||
txtNew := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName, recordType), endpoint.RecordTypeTXT, r.Labels.Serialize(true, im.txtEncryptEnabled, im.txtEncryptAESKey))
|
||||
if txtNew != nil {
|
||||
txtNew.WithSetIdentifier(r.SetIdentifier)
|
||||
@ -315,6 +329,7 @@ func (im *TXTRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
||||
UpdateOld: endpoint.FilterEndpointsByOwnerID(im.ownerID, changes.UpdateOld),
|
||||
Delete: endpoint.FilterEndpointsByOwnerID(im.ownerID, changes.Delete),
|
||||
}
|
||||
|
||||
for _, r := range filteredChanges.Create {
|
||||
if r.Labels == nil {
|
||||
r.Labels = make(map[string]string)
|
||||
|
||||
@ -61,7 +61,7 @@ func TestNewTXTRegistryEncryptionConfig(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
actual, err := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, test.encEnabled, test.aesKeyRaw)
|
||||
actual, err := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, test.encEnabled, test.aesKeyRaw, "")
|
||||
if test.errorExpected {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
@ -107,7 +107,7 @@ func TestGenerateTXTGenerateTextRecordEncryptionWihDecryption(t *testing.T) {
|
||||
for _, k := range withEncryptionKeys {
|
||||
t.Run(fmt.Sprintf("key '%s' with decrypted result '%s'", k, test.decrypted), func(t *testing.T) {
|
||||
key := []byte(k)
|
||||
r, err := NewTXTRegistry(p, "", "", "owner", time.Minute, "", []string{}, []string{}, true, key)
|
||||
r, err := NewTXTRegistry(p, "", "", "owner", time.Minute, "", []string{}, []string{}, true, key, "")
|
||||
assert.NoError(t, err, "Error creating TXT registry")
|
||||
txtRecords := r.generateTXTRecord(test.record)
|
||||
assert.Len(t, txtRecords, len(test.record.Targets))
|
||||
@ -144,7 +144,7 @@ func TestApplyRecordsWithEncryption(t *testing.T) {
|
||||
|
||||
key := []byte("ZPitL0NGVQBZbTD6DwXJzD8RiStSazzYXQsdUowLURY=")
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, key)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, key, "")
|
||||
|
||||
_ = r.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -202,7 +202,7 @@ func TestApplyRecordsWithEncryptionKeyChanged(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, key := range withEncryptionKeys {
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key))
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key), "")
|
||||
_ = r.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
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) {
|
||||
}
|
||||
|
||||
for i, key := range withEncryptionKeys {
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key))
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key), "")
|
||||
keyId := fmt.Sprintf("key-id-%d", i)
|
||||
changes := []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerAndOwnedRecordWithKeyIDLabel("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner", "", keyId),
|
||||
|
||||
@ -49,20 +49,20 @@ func TestTXTRegistry(t *testing.T) {
|
||||
|
||||
func testTXTRegistryNew(t *testing.T) {
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
_, err := NewTXTRegistry(p, "txt", "", "", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
_, err := NewTXTRegistry(p, "txt", "", "", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "", "txt", "", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
_, err = NewTXTRegistry(p, "", "txt", "", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
require.Error(t, err)
|
||||
|
||||
r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, p, r.provider)
|
||||
|
||||
r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
_, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
require.Error(t, err)
|
||||
|
||||
_, ok := r.mapper.(affixNameMapper)
|
||||
@ -71,16 +71,16 @@ func testTXTRegistryNew(t *testing.T) {
|
||||
assert.Equal(t, p, r.provider)
|
||||
|
||||
aesKey := []byte(";k&l)nUC/33:{?d{3)54+,AD?]SX%yh^")
|
||||
_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, aesKey)
|
||||
_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, aesKey, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, nil)
|
||||
_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, nil, "")
|
||||
require.Error(t, err)
|
||||
|
||||
r, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, aesKey)
|
||||
r, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, aesKey, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok = r.mapper.(affixNameMapper)
|
||||
@ -228,13 +228,13 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
// Ensure prefix is case-insensitive
|
||||
r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
|
||||
r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "")
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
@ -363,13 +363,13 @@ func testTXTRegistryRecordsSuffixed(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
// Ensure prefix is case-insensitive
|
||||
r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
|
||||
@ -490,7 +490,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
@ -527,12 +527,12 @@ func testTXTRegistryRecordsPrefixedTemplated(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt-%{record_type}.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "txt-%{record_type}.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
r, _ = NewTXTRegistry(p, "TxT-%{record_type}.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
|
||||
r, _ = NewTXTRegistry(p, "TxT-%{record_type}.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "")
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
@ -569,12 +569,12 @@ func testTXTRegistryRecordsSuffixedTemplated(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "txt%{record_type}", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "txt%{record_type}", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
r, _ = NewTXTRegistry(p, "", "TxT%{record_type}", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
|
||||
r, _ = NewTXTRegistry(p, "", "TxT%{record_type}", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "")
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
@ -617,7 +617,7 @@ func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
|
||||
newEndpointWithOwner("txt.cname-multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -698,7 +698,7 @@ func testTXTRegistryApplyChangesWithTemplatedPrefix(t *testing.T) {
|
||||
p.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "prefix%{record_type}.", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "prefix%{record_type}.", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
|
||||
@ -741,7 +741,7 @@ func testTXTRegistryApplyChangesWithTemplatedSuffix(t *testing.T) {
|
||||
p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
|
||||
assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey))
|
||||
}
|
||||
r, _ := NewTXTRegistry(p, "", "-%{record_type}suffix", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "-%{record_type}suffix", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
|
||||
@ -806,7 +806,7 @@ func testTXTRegistryApplyChangesWithSuffix(t *testing.T) {
|
||||
newEndpointWithOwner("cname-multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "wildcard", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "wildcard", []string{}, []string{}, false, nil, "")
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -900,7 +900,7 @@ func testTXTRegistryApplyChangesNoPrefix(t *testing.T) {
|
||||
newEndpointWithOwner("cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -1058,7 +1058,7 @@ func testTXTRegistryMissingRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS}, []string{}, false, nil, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
@ -1168,7 +1168,7 @@ func testTXTRegistryMissingRecordsWithPrefix(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS, endpoint.RecordTypeTXT}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS, endpoint.RecordTypeTXT}, []string{}, false, nil, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
@ -1463,7 +1463,7 @@ func TestNewTXTScheme(t *testing.T) {
|
||||
newEndpointWithOwner("cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -1528,11 +1528,46 @@ func TestGenerateTXT(t *testing.T) {
|
||||
}
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
gotTXT := r.generateTXTRecord(record)
|
||||
assert.Equal(t, expectedTXT, gotTXT)
|
||||
}
|
||||
|
||||
func TestGenerateTXTWithMigration(t *testing.T) {
|
||||
record := newEndpointWithOwner("foo.test-zone.example.org", "1.2.3.4", endpoint.RecordTypeA, "owner")
|
||||
expectedTXTBeforeMigration := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a-foo.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=owner\""},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnedRecordLabelKey: "foo.test-zone.example.org",
|
||||
},
|
||||
},
|
||||
}
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
gotTXTBeforeMigration := r.generateTXTRecord(record)
|
||||
assert.Equal(t, expectedTXTBeforeMigration, gotTXTBeforeMigration)
|
||||
|
||||
expectedTXTAfterMigration := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a-foo.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=foobar\""},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnedRecordLabelKey: "foo.test-zone.example.org",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rMigrated, _ := NewTXTRegistry(p, "", "", "foobar", time.Hour, "", []string{}, []string{}, false, nil, "owner")
|
||||
gotTXTAfterMigration := rMigrated.generateTXTRecord(record)
|
||||
assert.Equal(t, expectedTXTAfterMigration, gotTXTAfterMigration)
|
||||
|
||||
}
|
||||
|
||||
func TestGenerateTXTForAAAA(t *testing.T) {
|
||||
record := newEndpointWithOwner("foo.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, "owner")
|
||||
expectedTXT := []*endpoint.Endpoint{
|
||||
@ -1547,7 +1582,7 @@ func TestGenerateTXTForAAAA(t *testing.T) {
|
||||
}
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
gotTXT := r.generateTXTRecord(record)
|
||||
assert.Equal(t, expectedTXT, gotTXT)
|
||||
}
|
||||
@ -1564,7 +1599,7 @@ func TestFailGenerateTXT(t *testing.T) {
|
||||
expectedTXT := []*endpoint.Endpoint{}
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
gotTXT := r.generateTXTRecord(cnameRecord)
|
||||
assert.Equal(t, expectedTXT, gotTXT)
|
||||
}
|
||||
@ -1582,7 +1617,7 @@ func TestTXTRegistryApplyChangesEncrypt(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte("12345678901234567890123456789012"))
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte("12345678901234567890123456789012"), "")
|
||||
records, _ := r.Records(ctx)
|
||||
changes := &plan.Changes{
|
||||
Delete: records,
|
||||
@ -1628,7 +1663,7 @@ func TestMultiClusterDifferentRecordTypeOwnership(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
r, _ := NewTXTRegistry(p, "_owner.", "", "bar", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "_owner.", "", "bar", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
// new cluster has same ingress host as other cluster and uses CNAME ingress address
|
||||
@ -1713,7 +1748,7 @@ func TestGenerateTXTRecordWithNewFormatOnly(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
records := r.generateTXTRecord(tc.endpoint)
|
||||
|
||||
assert.Len(t, records, tc.expectedRecords, tc.description)
|
||||
@ -1742,7 +1777,7 @@ func TestApplyChangesWithNewFormatOnly(t *testing.T) {
|
||||
p.CreateZone(testZone)
|
||||
ctx := context.Background()
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -1790,7 +1825,7 @@ func TestTXTRegistryRecordsWithEmptyTargets(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
hook := testutils.LogsUnderTestWithLogLevel(log.ErrorLevel, t)
|
||||
records, err := r.Records(ctx)
|
||||
require.NoError(t, err)
|
||||
@ -1994,7 +2029,7 @@ func TestTXTRegistryRecreatesMissingRecords(t *testing.T) {
|
||||
|
||||
// When: Apply changes to recreate missing A records
|
||||
managedRecords := []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeAAAA, endpoint.RecordTypeTXT}
|
||||
registry, err := NewTXTRegistry(p, "", "", ownerId, time.Hour, "", managedRecords, nil, false, nil)
|
||||
registry, err := NewTXTRegistry(p, "", "", ownerId, time.Hour, "", managedRecords, nil, false, nil, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedRecords := append(existing, expectedCreate...)
|
||||
@ -2028,3 +2063,49 @@ func TestTXTRegistryRecreatesMissingRecords(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTXTRecordMigration(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
|
||||
r, _ := NewTXTRegistry(p, "%{record_type}-", "", "foo", time.Hour, "", []string{}, []string{}, false, nil, "")
|
||||
|
||||
r.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
// records on cluster using A record for ingress address
|
||||
newEndpointWithOwnerAndLabels("bar.test-zone.example.org", "1.2.3.4", endpoint.RecordTypeA, "foo", endpoint.Labels{endpoint.OwnerLabelKey: "owner"}),
|
||||
},
|
||||
})
|
||||
|
||||
createdRecords, _ := r.Records(ctx)
|
||||
|
||||
newTXTRecord := r.generateTXTRecord(createdRecords[0])
|
||||
|
||||
expectedTXTRecords := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a-bar.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=foo\""},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedTXTRecords[0].Targets, newTXTRecord[0].Targets)
|
||||
|
||||
r, _ = NewTXTRegistry(p, "%{record_type}-", "", "foobar", time.Hour, "", []string{}, []string{}, false, nil, "foo")
|
||||
|
||||
updatedRecords, _ := r.Records(ctx)
|
||||
|
||||
updatedTXTRecord := r.generateTXTRecord(updatedRecords[0])
|
||||
|
||||
expectedFinalTXT := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a-bar.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=foobar\""},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, updatedTXTRecord[0].Targets, expectedFinalTXT[0].Targets)
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user