Skip to content

Commit

Permalink
Introduced flags to migrate ownerID with TXT registry
Browse files Browse the repository at this point in the history
  • Loading branch information
troll-os committed Oct 29, 2024
1 parent 7ef0e2e commit 08dd8b4
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 42 deletions.
20 changes: 13 additions & 7 deletions controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ type Controller struct {
ExcludeRecordTypes []string
// MinEventSyncInterval is used as window for batching events
MinEventSyncInterval time.Duration
// migrate txt-owner flag
TXTOwnerMigrate bool
// old txt-owner whitch needed to modify
TXTOwnerOld string
}

// RunOnce runs a single iteration of a reconciliation loop.
Expand Down Expand Up @@ -242,13 +246,15 @@ func (c *Controller) RunOnce(ctx context.Context) error {
registryFilter := c.Registry.GetDomainFilter()

plan := &plan.Plan{
Policies: []plan.Policy{c.Policy},
Current: records,
Desired: endpoints,
DomainFilter: endpoint.MatchAllDomainFilters{c.DomainFilter, registryFilter},
ManagedRecords: c.ManagedRecordTypes,
ExcludeRecords: c.ExcludeRecordTypes,
OwnerID: c.Registry.OwnerID(),
Policies: []plan.Policy{c.Policy},
Current: records,
Desired: endpoints,
DomainFilter: endpoint.MatchAllDomainFilters{c.DomainFilter, registryFilter},
ManagedRecords: c.ManagedRecordTypes,
ExcludeRecords: c.ExcludeRecordTypes,
OwnerID: c.Registry.OwnerID(),
TXTOwnerMigrate: c.TXTOwnerMigrate,
TXTOwnerOld: c.TXTOwnerOld,
}

plan = plan.Calculate()
Expand Down
10 changes: 10 additions & 0 deletions docs/registry/txt.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,13 @@ 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.

## Owner migration

It is possible to migrate the owner of your records when using the TXT registry.

Simply use the following flags when setting up your instance : `--migrate-txt-owner`, which is the boolean enabling
the migration feature, `--from-txt-owner=<the owner you want to migrate from>` and set `--txt-owner-id` to
the new value you want it to be.

Take not that if you didn't set `--txt-owner-id` previously, the value automatically set by ExternalDNS is 'default'.
7 changes: 6 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ func main() {
if cfg.DryRun {
log.Info("running in dry-run mode. No changes to DNS records will be made.")
}
if cfg.TXTOwnerMigrate {
log.Infof("modify the previous txt-owner (%s) to the current txt-owner (%s)", cfg.TXTOwnerOld, cfg.TXTOwnerID)
}

ll, err := log.ParseLevel(cfg.LogLevel)
if err != nil {
Expand Down Expand Up @@ -387,7 +390,7 @@ func main() {
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.TXTOwnerMigrate, cfg.TXTOwnerOld)
case "aws-sd":
r, err = registry.NewAWSSDRegistry(p, cfg.TXTOwnerID)
default:
Expand All @@ -412,6 +415,8 @@ func main() {
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
ExcludeRecordTypes: cfg.ExcludeDNSRecordTypes,
MinEventSyncInterval: cfg.MinEventSyncInterval,
TXTOwnerMigrate: cfg.TXTOwnerMigrate,
TXTOwnerOld: cfg.TXTOwnerOld,
}

if cfg.Once {
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ type Config struct {
Policy string
Registry string
TXTOwnerID string
TXTOwnerOld string
TXTOwnerMigrate bool
TXTPrefix string
TXTSuffix string
TXTEncryptEnabled bool
Expand Down Expand Up @@ -290,6 +292,8 @@ var defaultConfig = &Config{
Policy: "sync",
Registry: "txt",
TXTOwnerID: "default",
TXTOwnerOld: "default",
TXTOwnerMigrate: false,
TXTPrefix: "",
TXTSuffix: "",
TXTCacheInterval: 0,
Expand Down Expand Up @@ -572,6 +576,8 @@ func (cfg *Config) ParseFlags(args []string) error {
// Flags related to the registry
app.Flag("registry", "The registry implementation to use to keep track of DNS record ownership (default: txt, options: txt, noop, dynamodb, aws-sd)").Default(defaultConfig.Registry).EnumVar(&cfg.Registry, "txt", "noop", "dynamodb", "aws-sd")
app.Flag("txt-owner-id", "When using the TXT or DynamoDB registry, a name that identifies this instance of ExternalDNS (default: default)").Default(defaultConfig.TXTOwnerID).StringVar(&cfg.TXTOwnerID)
app.Flag("from-txt-owner", "Old txt-owner-id that needs to be overwritten (default: default)").StringVar(&cfg.TXTOwnerOld)
app.Flag("migrate-txt-owner", "When enabled, modify the previous txt-owner to the current txt-owner (default: disabled)").BoolVar(&cfg.TXTOwnerMigrate)
app.Flag("txt-prefix", "When using the TXT registry, a custom string that's prefixed to each ownership DNS record (optional). Could contain record type template like '%{record_type}-prefix-'. Mutual exclusive with txt-suffix!").Default(defaultConfig.TXTPrefix).StringVar(&cfg.TXTPrefix)
app.Flag("txt-suffix", "When using the TXT registry, a custom string that's suffixed to the host portion of each ownership DNS record (optional). Could contain record type template like '-%{record_type}-suffix'. Mutual exclusive with txt-prefix!").Default(defaultConfig.TXTSuffix).StringVar(&cfg.TXTSuffix)
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)
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ var (
Policy: "upsert-only",
Registry: "noop",
TXTOwnerID: "owner-1",
TXTOwnerOld: "owner-old",
TXTOwnerMigrate: true,
TXTPrefix: "associated-txt-record",
TXTCacheInterval: 12 * time.Hour,
Interval: 10 * time.Minute,
Expand Down Expand Up @@ -333,6 +335,8 @@ func TestParseFlags(t *testing.T) {
"--policy=upsert-only",
"--registry=noop",
"--txt-owner-id=owner-1",
"--migrate-txt-owner",
"--from-txt-owner=owner-old",
"--txt-prefix=associated-txt-record",
"--txt-cache-interval=12h",
"--dynamodb-table=custom-table",
Expand Down Expand Up @@ -445,6 +449,8 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_POLICY": "upsert-only",
"EXTERNAL_DNS_REGISTRY": "noop",
"EXTERNAL_DNS_TXT_OWNER_ID": "owner-1",
"EXTERNAL_DNS_FROM_TXT_OWNER": "owner-old",
"EXTERNAL_DNS_MIGRATE_TXT_OWNER": "true",
"EXTERNAL_DNS_TXT_PREFIX": "associated-txt-record",
"EXTERNAL_DNS_TXT_CACHE_INTERVAL": "12h",
"EXTERNAL_DNS_INTERVAL": "10m",
Expand Down
28 changes: 27 additions & 1 deletion plan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ type Plan struct {
ExcludeRecords []string
// OwnerID of records to manage
OwnerID string
// modify owner flag
TXTOwnerMigrate bool
// old txt-owner whitch needed to modify
TXTOwnerOld string
}

// Changes holds lists of actions to be executed by dns providers
Expand Down Expand Up @@ -179,6 +183,7 @@ func (p *Plan) Calculate() *Plan {
}

changes := &Changes{}
var hasMig bool

for key, row := range t.rows {
// dns name not taken
Expand Down Expand Up @@ -221,6 +226,23 @@ func (p *Plan) Calculate() *Plan {
if records.current != nil && len(records.candidates) > 0 {
update := t.resolver.ResolveUpdate(records.current, records.candidates)

// Change the specified old txt-owner to the new txt-owner (if TXTOwnerMigrate==true and set the from-txt-owner)
if p.TXTOwnerMigrate && records.current.Labels[endpoint.OwnerLabelKey] == p.TXTOwnerOld {
hasMig = true
oldRecord := records.current
changes.UpdateOld = append(changes.UpdateOld, oldRecord)
recordToMigrate := update
recordToMigrate.Labels[endpoint.OwnerLabelKey] = p.OwnerID
log.WithFields(log.Fields{
"previousOwner": oldRecord.Labels[endpoint.OwnerLabelKey],
"newOwner": p.OwnerID,
"dnsName": records.current.DNSName,
"recordType": records.current.RecordType,
}).Info("Found record to migrate")
changes.UpdateNew = append(changes.UpdateNew, update)
continue
}

if shouldUpdateTTL(update, records.current) || targetChanged(update, records.current) || p.shouldUpdateProviderSpecific(update, records.current) {
inheritOwner(records.current, update)
changes.UpdateNew = append(changes.UpdateNew, update)
Expand Down Expand Up @@ -253,7 +275,11 @@ func (p *Plan) Calculate() *Plan {
if p.OwnerID != "" {
changes.Delete = endpoint.FilterEndpointsByOwnerID(p.OwnerID, changes.Delete)
changes.Delete = endpoint.RemoveDuplicates(changes.Delete)
changes.UpdateOld = endpoint.FilterEndpointsByOwnerID(p.OwnerID, changes.UpdateOld)
// When running a migration we don't want to filter out endpoint duplicates on ownerId value so that old records
// are deleted correctly
if !hasMig {
changes.UpdateOld = endpoint.FilterEndpointsByOwnerID(p.OwnerID, changes.UpdateOld)
}
changes.UpdateNew = endpoint.FilterEndpointsByOwnerID(p.OwnerID, changes.UpdateNew)
}

Expand Down
45 changes: 45 additions & 0 deletions plan/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type PlanTestSuite struct {
domainFilterFiltered2 *endpoint.Endpoint
domainFilterFiltered3 *endpoint.Endpoint
domainFilterExcluded *endpoint.Endpoint
migrateTXTOwnerOld *endpoint.Endpoint
migrateTXTOwnerNew *endpoint.Endpoint
}

func (suite *PlanTestSuite) SetupTest() {
Expand Down Expand Up @@ -243,6 +245,24 @@ func (suite *PlanTestSuite) SetupTest() {
Targets: endpoint.Targets{"1.1.1.1"},
RecordType: "A",
}
suite.migrateTXTOwnerOld = &endpoint.Endpoint{
DNSName: "migrated.domain.tld",
Targets: endpoint.Targets{"1.2.3.4"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-v1",
endpoint.OwnerLabelKey: "owner-old",
},
}
suite.migrateTXTOwnerNew = &endpoint.Endpoint{
DNSName: "migrated.domain.tld",
Targets: endpoint.Targets{"1.2.3.4"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-v1",
endpoint.OwnerLabelKey: "owner",
},
}
}

func (suite *PlanTestSuite) TestSyncFirstRound() {
Expand Down Expand Up @@ -742,6 +762,31 @@ func (suite *PlanTestSuite) TestIgnoreTargetCase() {
validateEntries(suite.T(), changes.Delete, expectedDelete)
}

func (suite *PlanTestSuite) TestMigrateTXTRecord() {
current := []*endpoint.Endpoint{suite.migrateTXTOwnerOld}
desired := []*endpoint.Endpoint{suite.migrateTXTOwnerNew}
expectedCreate := []*endpoint.Endpoint{}
expectedUpdateOld := []*endpoint.Endpoint{suite.migrateTXTOwnerOld}
expectedUpdateNew := []*endpoint.Endpoint{suite.migrateTXTOwnerNew}
expectedDelete := []*endpoint.Endpoint{}

p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
TXTOwnerMigrate: true,
TXTOwnerOld: "owner-old",
OwnerID: "owner",
ManagedRecords: []string{endpoint.RecordTypeA},
}

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 (suite *PlanTestSuite) TestRemoveEndpoint() {
current := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar192A}
desired := []*endpoint.Endpoint{suite.fooV1Cname}
Expand Down
3 changes: 2 additions & 1 deletion provider/ovh/ovh.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,11 @@ func (p *OVHProvider) change(change ovhChange) error {
log.Debugf("OVH: Add an entry to %s", change.String())
return p.client.Post(fmt.Sprintf("/domain/zone/%s/record", change.Zone), change.ovhRecordFields, nil)
case ovhDelete:
log.Debugf("OVH: Delete an entry to %s", change.String())
if change.ID == 0 {
log.Debugf("OVH: No ID found when deleting entry: %s", change.String())
return ErrRecordToMutateNotFound
}
log.Debugf("OVH: Delete an entry to %s", change.String())
return p.client.Delete(fmt.Sprintf("/domain/zone/%s/record/%d", change.Zone, change.ID), nil)
}
return nil
Expand Down
12 changes: 11 additions & 1 deletion registry/txt.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,14 @@ type TXTRegistry struct {
// encrypt text records
txtEncryptEnabled bool
txtEncryptAESKey []byte

//Handle Owner ID migration
isMigrationEnabled bool
oldOwnerID string
}

// NewTXTRegistry returns new TXTRegistry object
func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration, txtWildcardReplacement string, managedRecordTypes, excludeRecordTypes []string, txtEncryptEnabled bool, txtEncryptAESKey []byte) (*TXTRegistry, error) {
func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration, txtWildcardReplacement string, managedRecordTypes, excludeRecordTypes []string, txtEncryptEnabled bool, txtEncryptAESKey []byte, isMigrationEnabled bool, oldOwnerID string) (*TXTRegistry, error) {
if ownerID == "" {
return nil, errors.New("owner id cannot be empty")
}
Expand Down Expand Up @@ -88,6 +92,8 @@ func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID st
excludeRecordTypes: excludeRecordTypes,
txtEncryptEnabled: txtEncryptEnabled,
txtEncryptAESKey: txtEncryptAESKey,
isMigrationEnabled: isMigrationEnabled,
oldOwnerID: oldOwnerID,
}, nil
}

Expand Down Expand Up @@ -250,6 +256,10 @@ func (im *TXTRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes)
UpdateOld: endpoint.FilterEndpointsByOwnerID(im.ownerID, changes.UpdateOld),
Delete: endpoint.FilterEndpointsByOwnerID(im.ownerID, changes.Delete),
}
if im.isMigrationEnabled {
filteredChanges.UpdateOld = append(filteredChanges.UpdateOld, endpoint.FilterEndpointsByOwnerID(im.oldOwnerID, changes.UpdateOld)...)
filteredChanges.Delete = append(filteredChanges.Delete, endpoint.FilterEndpointsByOwnerID(im.oldOwnerID, changes.Delete)...)
}
for _, r := range filteredChanges.Create {
if r.Labels == nil {
r.Labels = make(map[string]string)
Expand Down
Loading

0 comments on commit 08dd8b4

Please sign in to comment.