From f04430e61860b733849d927426bd308fd138fd9a Mon Sep 17 00:00:00 2001 From: ivan katliarchuk Date: Sun, 15 Mar 2026 10:03:04 +0000 Subject: [PATCH 1/5] refactor(registry): introduce factory with uniform New constructors Signed-off-by: ivan katliarchuk --- controller/controller_test.go | 12 ++-- controller/execute.go | 4 +- controller/metrics_test.go | 6 +- pkg/apis/externaldns/constants.go | 24 ++++++++ pkg/apis/externaldns/types.go | 4 +- registry/awssd/registry.go | 11 +++- registry/awssd/registry_test.go | 10 ++-- registry/dynamodb/registry.go | 33 ++++++---- registry/dynamodb/registry_test.go | 26 ++++---- registry/factory/registry.go | 53 ++++++++++++++++ registry/{ => factory}/registry_test.go | 19 +++--- registry/noop/noop.go | 13 +++- registry/noop/noop_test.go | 7 ++- registry/registry.go | 45 -------------- registry/txt/encryption_test.go | 10 ++-- registry/txt/registry.go | 14 ++++- registry/txt/registry_test.go | 80 ++++++++++++------------- 17 files changed, 220 insertions(+), 151 deletions(-) create mode 100644 pkg/apis/externaldns/constants.go create mode 100644 registry/factory/registry.go rename registry/{ => factory}/registry_test.go (88%) diff --git a/controller/controller_test.go b/controller/controller_test.go index d9a15dc6bf..83839be6f5 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -31,7 +31,7 @@ import ( "sigs.k8s.io/external-dns/pkg/events/fake" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" - "sigs.k8s.io/external-dns/registry" + registryfactory "sigs.k8s.io/external-dns/registry/factory" "sigs.k8s.io/external-dns/registry/noop" "github.com/stretchr/testify/assert" @@ -154,7 +154,7 @@ func getTestSource() *testutils.MockSource { func getTestConfig() *externaldns.Config { cfg := externaldns.NewConfig() - cfg.Registry = registry.NOOP + cfg.Registry = externaldns.RegistryNoop cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME} return cfg } @@ -213,7 +213,7 @@ func TestRunOnce(t *testing.T) { emitter := fake.NewFakeEventEmitter() - r, err := registry.SelectRegistry(cfg, provider) + r, err := registryfactory.SelectRegistry(cfg, provider) require.NoError(t, err) // Run our controller once to trigger the validation. @@ -243,7 +243,7 @@ func TestRun(t *testing.T) { cfg := getTestConfig() provider := getTestProvider() - r, err := registry.SelectRegistry(cfg, provider) + r, err := registryfactory.SelectRegistry(cfg, provider) require.NoError(t, err) // Run our controller once to trigger the validation. @@ -331,7 +331,7 @@ func TestShouldRunOnce(t *testing.T) { func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter *endpoint.DomainFilter, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) { t.Helper() cfg := externaldns.NewConfig() - cfg.Registry = registry.NOOP + cfg.Registry = externaldns.RegistryNoop cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME} source := new(testutils.MockSource) @@ -341,7 +341,7 @@ func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint. provider := &filteredMockProvider{ RecordsStore: providerEndpoints, } - r, err := registry.SelectRegistry(cfg, provider) + r, err := registryfactory.SelectRegistry(cfg, provider) require.NoError(t, err) ctrl := &Controller{ diff --git a/controller/execute.go b/controller/execute.go index 50d93e2e68..3f005efccb 100644 --- a/controller/execute.go +++ b/controller/execute.go @@ -66,7 +66,7 @@ import ( "sigs.k8s.io/external-dns/provider/transip" "sigs.k8s.io/external-dns/provider/webhook" webhookapi "sigs.k8s.io/external-dns/provider/webhook/api" - "sigs.k8s.io/external-dns/registry" + registryfactory "sigs.k8s.io/external-dns/registry/factory" "sigs.k8s.io/external-dns/source" "sigs.k8s.io/external-dns/source/annotations" "sigs.k8s.io/external-dns/source/wrappers" @@ -373,7 +373,7 @@ func buildController( if !ok { return nil, fmt.Errorf("unknown policy: %s", cfg.Policy) } - reg, err := registry.SelectRegistry(cfg, p) + reg, err := registryfactory.SelectRegistry(cfg, p) if err != nil { return nil, err } diff --git a/controller/metrics_test.go b/controller/metrics_test.go index a72bb14ef2..a1f8aa79d4 100644 --- a/controller/metrics_test.go +++ b/controller/metrics_test.go @@ -25,7 +25,7 @@ import ( "sigs.k8s.io/external-dns/internal/testutils" "sigs.k8s.io/external-dns/pkg/apis/externaldns" "sigs.k8s.io/external-dns/plan" - "sigs.k8s.io/external-dns/registry" + registryfactory "sigs.k8s.io/external-dns/registry/factory" ) func TestVerifyARecords(t *testing.T) { @@ -323,7 +323,7 @@ func newMixedRecordsFixture() *Controller { }) cfg := externaldns.NewConfig() - cfg.Registry = registry.NOOP + cfg.Registry = externaldns.RegistryNoop cfg.ManagedDNSRecordTypes = endpoint.KnownRecordTypes source := new(testutils.MockSource) @@ -332,7 +332,7 @@ func newMixedRecordsFixture() *Controller { provider := &filteredMockProvider{ RecordsStore: providerEndpoints, } - r, _ := registry.SelectRegistry(cfg, provider) + r, _ := registryfactory.SelectRegistry(cfg, provider) return &Controller{ Source: source, diff --git a/pkg/apis/externaldns/constants.go b/pkg/apis/externaldns/constants.go new file mode 100644 index 0000000000..20a7c6b413 --- /dev/null +++ b/pkg/apis/externaldns/constants.go @@ -0,0 +1,24 @@ +/* +Copyright 2026 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package externaldns + +const ( + RegistryTXT = "txt" + RegistryNoop = "noop" + RegistryDynamoDB = "dynamodb" + RegistryAWSSD = "aws-sd" +) diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index f299e7cb44..3923d8f45d 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -347,7 +347,7 @@ var defaultConfig = &Config{ PublishInternal: false, RegexDomainExclude: regexp.MustCompile(""), RegexDomainFilter: regexp.MustCompile(""), - Registry: "txt", + Registry: RegistryTXT, RequestTimeout: time.Second * 30, RFC2136BatchChangeSize: 50, RFC2136GSSTSIG: false, @@ -682,7 +682,7 @@ func bindFlags(b flags.FlagBinder, cfg *Config) { b.EnumVar("policy", "Modify how DNS records are synchronized between sources and providers (default: sync, options: sync, upsert-only, create-only)", defaultConfig.Policy, &cfg.Policy, "sync", "upsert-only", "create-only") // Flags related to the registry - b.EnumVar("registry", "The registry implementation to use to keep track of DNS record ownership (default: txt, options: txt, noop, dynamodb, aws-sd)", defaultConfig.Registry, &cfg.Registry, "txt", "noop", "dynamodb", "aws-sd") + b.EnumVar("registry", "The registry implementation to use to keep track of DNS record ownership (default: txt, options: txt, noop, dynamodb, aws-sd)", defaultConfig.Registry, &cfg.Registry, RegistryTXT, RegistryNoop, RegistryDynamoDB, RegistryAWSSD) b.StringVar("txt-owner-id", "When using the TXT or DynamoDB registry, a name that identifies this instance of ExternalDNS (default: default)", defaultConfig.TXTOwnerID, &cfg.TXTOwnerID) b.StringVar("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!", defaultConfig.TXTPrefix, &cfg.TXTPrefix) b.StringVar("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!", defaultConfig.TXTSuffix, &cfg.TXTSuffix) diff --git a/registry/awssd/registry.go b/registry/awssd/registry.go index 56dc642f11..48491b11c9 100644 --- a/registry/awssd/registry.go +++ b/registry/awssd/registry.go @@ -21,8 +21,10 @@ import ( "errors" "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/pkg/apis/externaldns" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" + "sigs.k8s.io/external-dns/registry" ) // AWSSDRegistry implements registry interface with ownership information associated via the Description field of SD Service @@ -31,8 +33,13 @@ type AWSSDRegistry struct { ownerID string } -// NewAWSSDRegistry returns implementation of registry for AWS SD -func NewAWSSDRegistry(provider provider.Provider, ownerID string) (*AWSSDRegistry, error) { +// New creates an AWSSDRegistry from the given configuration. +func New(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) { + return newRegistry(p, cfg.TXTOwnerID) +} + +// newRegistry returns implementation of registry for AWS SD +func newRegistry(provider provider.Provider, ownerID string) (*AWSSDRegistry, error) { if ownerID == "" { return nil, errors.New("owner id cannot be empty") } diff --git a/registry/awssd/registry_test.go b/registry/awssd/registry_test.go index 2fb7e5df89..8c0160503b 100644 --- a/registry/awssd/registry_test.go +++ b/registry/awssd/registry_test.go @@ -51,12 +51,12 @@ func newInMemoryProvider(endpoints []*endpoint.Endpoint, onApplyChanges func(cha } } -func TestAWSSDRegistry_NewAWSSDRegistry(t *testing.T) { +func TestAWSSDRegistry_newRegistry(t *testing.T) { p := newInMemoryProvider(nil, nil) - _, err := NewAWSSDRegistry(p, "") + _, err := newRegistry(p, "") require.Error(t, err) - _, err = NewAWSSDRegistry(p, "owner") + _, err = newRegistry(p, "owner") require.NoError(t, err) } @@ -102,7 +102,7 @@ func TestAWSSDRegistryTest_Records(t *testing.T) { }, } - r, _ := NewAWSSDRegistry(p, "records-owner") + r, _ := newRegistry(p, "records-owner") records, _ := r.Records(t.Context()) assert.True(t, testutils.SameEndpoints(records, expectedRecords)) @@ -155,7 +155,7 @@ func TestAWSSDRegistry_Records_ApplyChanges(t *testing.T) { } assert.True(t, testutils.SamePlanChanges(mGot, mExpected)) }) - r, err := NewAWSSDRegistry(p, "owner") + r, err := newRegistry(p, "owner") require.NoError(t, err) err = r.ApplyChanges(t.Context(), changes) diff --git a/registry/dynamodb/registry.go b/registry/dynamodb/registry.go index def57d3467..e7324e9a92 100644 --- a/registry/dynamodb/registry.go +++ b/registry/dynamodb/registry.go @@ -27,22 +27,25 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" + awsdynamodb "github.com/aws/aws-sdk-go-v2/service/dynamodb" dynamodbtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/pkg/apis/externaldns" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" + provideraws "sigs.k8s.io/external-dns/provider/aws" + "sigs.k8s.io/external-dns/registry" "sigs.k8s.io/external-dns/registry/mapper" ) // DynamoDBAPI is the subset of the AWS DynamoDB API that we actually use. Add methods as required. Signatures must match exactly. type DynamoDBAPI interface { - DescribeTable(context.Context, *dynamodb.DescribeTableInput, ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error) - Scan(context.Context, *dynamodb.ScanInput, ...func(*dynamodb.Options)) (*dynamodb.ScanOutput, error) - BatchExecuteStatement(context.Context, *dynamodb.BatchExecuteStatementInput, ...func(*dynamodb.Options)) (*dynamodb.BatchExecuteStatementOutput, error) + DescribeTable(context.Context, *awsdynamodb.DescribeTableInput, ...func(*awsdynamodb.Options)) (*awsdynamodb.DescribeTableOutput, error) + Scan(context.Context, *awsdynamodb.ScanInput, ...func(*awsdynamodb.Options)) (*awsdynamodb.ScanOutput, error) + BatchExecuteStatement(context.Context, *awsdynamodb.BatchExecuteStatementInput, ...func(*awsdynamodb.Options)) (*awsdynamodb.BatchExecuteStatementOutput, error) } // DynamoDBRegistry implements registry interface with ownership implemented via an AWS DynamoDB table. @@ -75,8 +78,16 @@ const dynamodbAttributeMigrate = "dynamodb/needs-migration" // DynamoDB allows a maximum batch size of 25 items. var dynamodbMaxBatchSize uint8 = 25 -// NewDynamoDBRegistry returns a new DynamoDBRegistry object. -func NewDynamoDBRegistry( +// New creates a DynamoDBRegistry from the given configuration. +func New(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) { + client := awsdynamodb.NewFromConfig(provideraws.CreateDefaultV2Config(cfg), WithRegion(cfg.AWSDynamoDBRegion)) + return newRegistry(p, cfg.TXTOwnerID, client, + cfg.AWSDynamoDBTable, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTWildcardReplacement, + cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, []byte(cfg.TXTEncryptAESKey), cfg.TXTCacheInterval) +} + +// newRegistry returns a new DynamoDBRegistry object. +func newRegistry( provider provider.Provider, ownerID string, dynamodbAPI DynamoDBAPI, table, txtPrefix, txtSuffix, txtWildcardReplacement string, @@ -371,7 +382,7 @@ func (im *DynamoDBRegistry) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]* } func (im *DynamoDBRegistry) readLabels(ctx context.Context) error { - table, err := im.dynamodbAPI.DescribeTable(ctx, &dynamodb.DescribeTableInput{ + table, err := im.dynamodbAPI.DescribeTable(ctx, &awsdynamodb.DescribeTableInput{ TableName: aws.String(im.table), }) if err != nil { @@ -399,7 +410,7 @@ func (im *DynamoDBRegistry) readLabels(ctx context.Context) error { } labels := map[endpoint.EndpointKey]endpoint.Labels{} - scanPaginator := dynamodb.NewScanPaginator(im.dynamodbAPI, &dynamodb.ScanInput{ + scanPaginator := awsdynamodb.NewScanPaginator(im.dynamodbAPI, &awsdynamodb.ScanInput{ TableName: aws.String(im.table), FilterExpression: aws.String("o = :ownerval"), ExpressionAttributeValues: map[string]dynamodbtypes.AttributeValue{ @@ -528,7 +539,7 @@ func (im *DynamoDBRegistry) executeStatements(ctx context.Context, statements [] statements = nil } - output, err := im.dynamodbAPI.BatchExecuteStatement(ctx, &dynamodb.BatchExecuteStatementInput{ + output, err := im.dynamodbAPI.BatchExecuteStatement(ctx, &awsdynamodb.BatchExecuteStatementInput{ Statements: chunk, }) if err != nil { @@ -580,11 +591,11 @@ func (im *DynamoDBRegistry) removeFromCache(ep *endpoint.Endpoint) { } } -func WithRegion(region string) func(*dynamodb.Options) { +func WithRegion(region string) func(*awsdynamodb.Options) { if region == "" { return nil } - return func(opts *dynamodb.Options) { + return func(opts *awsdynamodb.Options) { opts.Region = region } } diff --git a/registry/dynamodb/registry_test.go b/registry/dynamodb/registry_test.go index def8499820..c76cfdcd63 100644 --- a/registry/dynamodb/registry_test.go +++ b/registry/dynamodb/registry_test.go @@ -44,31 +44,31 @@ const ( func TestDynamoDBRegistryNew(t *testing.T) { api, p := newDynamoDBAPIStub(t, nil) - _, err := NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, []byte(""), time.Hour) + _, err := newRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, []byte(""), time.Hour) require.NoError(t, err) - _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "testPrefix", "", "", []string{}, []string{}, []byte(""), time.Hour) + _, err = newRegistry(p, "test-owner", api, "test-table", "testPrefix", "", "", []string{}, []string{}, []byte(""), time.Hour) require.NoError(t, err) - _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "testSuffix", "", []string{}, []string{}, []byte(""), time.Hour) + _, err = newRegistry(p, "test-owner", api, "test-table", "", "testSuffix", "", []string{}, []string{}, []byte(""), time.Hour) require.NoError(t, err) - _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "testWildcard", []string{}, []string{}, []byte(""), time.Hour) + _, err = newRegistry(p, "test-owner", api, "test-table", "", "", "testWildcard", []string{}, []string{}, []byte(""), time.Hour) require.NoError(t, err) - _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "testWildcard", []string{}, []string{}, []byte(";k&l)nUC/33:{?d{3)54+,AD?]SX%yh^"), time.Hour) + _, err = newRegistry(p, "test-owner", api, "test-table", "", "", "testWildcard", []string{}, []string{}, []byte(";k&l)nUC/33:{?d{3)54+,AD?]SX%yh^"), time.Hour) require.NoError(t, err) - _, err = NewDynamoDBRegistry(p, "", api, "test-table", "", "", "", []string{}, []string{}, []byte(""), time.Hour) + _, err = newRegistry(p, "", api, "test-table", "", "", "", []string{}, []string{}, []byte(""), time.Hour) require.EqualError(t, err, "owner id cannot be empty") - _, err = NewDynamoDBRegistry(p, "test-owner", api, "", "", "", "", []string{}, []string{}, []byte(""), time.Hour) + _, err = newRegistry(p, "test-owner", api, "", "", "", "", []string{}, []string{}, []byte(""), time.Hour) require.EqualError(t, err, "table cannot be empty") - _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, []byte(";k&l)nUC/33:{?d{3)54+,AD?]SX%yh^x"), time.Hour) + _, err = newRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, []byte(";k&l)nUC/33:{?d{3)54+,AD?]SX%yh^x"), time.Hour) require.EqualError(t, err, "the AES Encryption key must be 32 bytes long, in either plain text or base64-encoded format") - _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "testPrefix", "testSuffix", "", []string{}, []string{}, []byte(""), time.Hour) + _, err = newRegistry(p, "test-owner", api, "test-table", "testPrefix", "testSuffix", "", []string{}, []string{}, []byte(""), time.Hour) require.EqualError(t, err, "txt-prefix and txt-suffix are mutually exclusive") } @@ -101,7 +101,7 @@ func TestDynamoDBRegistryNew_EncryptionConfig(t *testing.T) { }, } for _, test := range tests { - actual, err := NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, test.aesKeyRaw, time.Hour) + actual, err := newRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, test.aesKeyRaw, time.Hour) if test.errorExpected { require.Error(t, err) } else { @@ -157,7 +157,7 @@ func TestDynamoDBRegistryRecordsBadTable(t *testing.T) { api, p := newDynamoDBAPIStub(t, nil) tc.setup(&api.tableDescription) - r, _ := NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, nil, time.Hour) + r, _ := newRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, nil, time.Hour) _, err := r.Records(t.Context()) assert.EqualError(t, err, tc.expected) @@ -243,7 +243,7 @@ func TestDynamoDBRegistryRecords(t *testing.T) { }, } - r, _ := NewDynamoDBRegistry(p, "test-owner", api, "test-table", "txt.", "", "", []string{}, []string{}, nil, time.Hour) + r, _ := newRegistry(p, "test-owner", api, "test-table", "txt.", "", "", []string{}, []string{}, nil, time.Hour) _ = p.(*wrappedProvider).Provider.ApplyChanges(t.Context(), &plan.Changes{ Create: []*endpoint.Endpoint{ endpoint.NewEndpoint("migrate.test-zone.example.org", endpoint.RecordTypeA, "3.3.3.3").WithSetIdentifier("set-3"), @@ -1083,7 +1083,7 @@ func TestDynamoDBRegistryApplyChanges(t *testing.T) { ctx := t.Context() - r, _ := NewDynamoDBRegistry(p, "test-owner", api, "test-table", "txt.", "", "", []string{}, []string{}, nil, time.Hour) + r, _ := newRegistry(p, "test-owner", api, "test-table", "txt.", "", "", []string{}, []string{}, nil, time.Hour) _, err := r.Records(ctx) require.NoError(t, err) diff --git a/registry/factory/registry.go b/registry/factory/registry.go new file mode 100644 index 0000000000..8753ec63fc --- /dev/null +++ b/registry/factory/registry.go @@ -0,0 +1,53 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package factory + +import ( + "fmt" + + "sigs.k8s.io/external-dns/pkg/apis/externaldns" + "sigs.k8s.io/external-dns/provider" + "sigs.k8s.io/external-dns/registry" + "sigs.k8s.io/external-dns/registry/awssd" + "sigs.k8s.io/external-dns/registry/dynamodb" + "sigs.k8s.io/external-dns/registry/noop" + "sigs.k8s.io/external-dns/registry/txt" +) + +// RegistryConstructor is a function that creates a Registry from configuration and a provider. +type RegistryConstructor func(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) + +// SelectRegistry creates a registry based on the given configuration. +func SelectRegistry(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) { + constructor, ok := registries(cfg.Registry) + if !ok { + return nil, fmt.Errorf("unknown registry: %s", cfg.Registry) + } + return constructor(cfg, p) +} + +// registries looks up the constructor for the named registry. +func registries(selector string) (RegistryConstructor, bool) { + m := map[string]RegistryConstructor{ + externaldns.RegistryDynamoDB: dynamodb.New, + externaldns.RegistryNoop: noop.New, + externaldns.RegistryTXT: txt.New, + externaldns.RegistryAWSSD: awssd.New, + } + c, ok := m[selector] + return c, ok +} diff --git a/registry/registry_test.go b/registry/factory/registry_test.go similarity index 88% rename from registry/registry_test.go rename to registry/factory/registry_test.go index 787b67b8fe..f2ff45fdfa 100644 --- a/registry/registry_test.go +++ b/registry/factory/registry_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry +package factory import ( "reflect" @@ -27,6 +27,7 @@ import ( "sigs.k8s.io/external-dns/provider" fakeprovider "sigs.k8s.io/external-dns/provider/fakes" "sigs.k8s.io/external-dns/provider/inmemory" + "sigs.k8s.io/external-dns/registry" "sigs.k8s.io/external-dns/registry/awssd" "sigs.k8s.io/external-dns/registry/dynamodb" "sigs.k8s.io/external-dns/registry/noop" @@ -34,10 +35,10 @@ import ( ) var ( - _ Registry = &awssd.AWSSDRegistry{} - _ Registry = &dynamodb.DynamoDBRegistry{} - _ Registry = &noop.NoopRegistry{} - _ Registry = &txt.TXTRegistry{} + _ registry.Registry = &awssd.AWSSDRegistry{} + _ registry.Registry = &dynamodb.DynamoDBRegistry{} + _ registry.Registry = &noop.NoopRegistry{} + _ registry.Registry = &txt.TXTRegistry{} ) func TestSelectRegistry(t *testing.T) { @@ -51,7 +52,7 @@ func TestSelectRegistry(t *testing.T) { { name: "dynamoDB registry", cfg: &externaldns.Config{ - Registry: DYNAMODB, + Registry: externaldns.RegistryDynamoDB, AWSDynamoDBRegion: "us-west-2", AWSDynamoDBTable: "test-table", TXTOwnerID: "owner-id", @@ -67,7 +68,7 @@ func TestSelectRegistry(t *testing.T) { { name: "noop registry", cfg: &externaldns.Config{ - Registry: NOOP, + Registry: externaldns.RegistryNoop, }, provider: &fakeprovider.MockProvider{}, wantErr: false, @@ -76,7 +77,7 @@ func TestSelectRegistry(t *testing.T) { { name: "TXT registry", cfg: &externaldns.Config{ - Registry: TXT, + Registry: externaldns.RegistryTXT, TXTPrefix: "prefix", TXTOwnerID: "owner-id", TXTCacheInterval: 60, @@ -91,7 +92,7 @@ func TestSelectRegistry(t *testing.T) { { name: "aws-sd registry", cfg: &externaldns.Config{ - Registry: AWSSD, + Registry: externaldns.RegistryAWSSD, TXTOwnerID: "owner-id", }, provider: &fakeprovider.MockProvider{}, diff --git a/registry/noop/noop.go b/registry/noop/noop.go index ab7618bd60..3e44c4b80d 100644 --- a/registry/noop/noop.go +++ b/registry/noop/noop.go @@ -20,8 +20,10 @@ import ( "context" "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/pkg/apis/externaldns" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" + "sigs.k8s.io/external-dns/registry" ) // NoopRegistry implements registry interface without ownership directly propagating changes to dns provider @@ -29,11 +31,16 @@ type NoopRegistry struct { provider provider.Provider } -// NewNoopRegistry returns new NoopRegistry object -func NewNoopRegistry(provider provider.Provider) (*NoopRegistry, error) { +// New creates a NoopRegistry from the given configuration. +func New(_ *externaldns.Config, p provider.Provider) (registry.Registry, error) { + return newRegistry(p), nil +} + +// newRegistry returns new NoopRegistry object +func newRegistry(provider provider.Provider) *NoopRegistry { return &NoopRegistry{ provider: provider, - }, nil + } } func (im *NoopRegistry) GetDomainFilter() endpoint.DomainFilterInterface { diff --git a/registry/noop/noop_test.go b/registry/noop/noop_test.go index 1590834a43..a8e124a03b 100644 --- a/registry/noop/noop_test.go +++ b/registry/noop/noop_test.go @@ -36,7 +36,8 @@ func TestNoopRegistry(t *testing.T) { func testNoopInit(t *testing.T) { p := inmemory.NewInMemoryProvider() - r, err := NewNoopRegistry(p) + r := newRegistry(p) + var err error require.NoError(t, err) assert.Equal(t, p, r.provider) } @@ -56,7 +57,7 @@ func testNoopRecords(t *testing.T) { Create: inmemoryRecords, }) - r, _ := NewNoopRegistry(p) + r := newRegistry(p) eps, err := r.Records(ctx) require.NoError(t, err) @@ -93,7 +94,7 @@ func testNoopApplyChanges(t *testing.T) { }) // wrong changes - r, _ := NewNoopRegistry(p) + r := newRegistry(p) err := r.ApplyChanges(ctx, &plan.Changes{ Create: []*endpoint.Endpoint{ { diff --git a/registry/registry.go b/registry/registry.go index 95450009da..3117c42056 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -18,26 +18,9 @@ package registry import ( "context" - "fmt" - - aws_dynamodb "github.com/aws/aws-sdk-go-v2/service/dynamodb" "sigs.k8s.io/external-dns/endpoint" - "sigs.k8s.io/external-dns/pkg/apis/externaldns" "sigs.k8s.io/external-dns/plan" - "sigs.k8s.io/external-dns/provider" - "sigs.k8s.io/external-dns/provider/aws" - "sigs.k8s.io/external-dns/registry/awssd" - "sigs.k8s.io/external-dns/registry/dynamodb" - "sigs.k8s.io/external-dns/registry/noop" - "sigs.k8s.io/external-dns/registry/txt" -) - -const ( - DYNAMODB = "dynamodb" - NOOP = "noop" - TXT = "txt" - AWSSD = "aws-sd" ) // Registry is an interface which should enables ownership concept in external-dns @@ -51,31 +34,3 @@ type Registry interface { GetDomainFilter() endpoint.DomainFilterInterface OwnerID() string } - -// SelectRegistry selects the appropriate registry implementation based on the configuration in cfg. -// It initializes and returns a registry along with any error encountered during setup. -// Supported registry types include: dynamodb, noop, txt, and aws-sd. -func SelectRegistry(cfg *externaldns.Config, p provider.Provider) (Registry, error) { - var r Registry - var err error - switch cfg.Registry { - case DYNAMODB: - r, err = dynamodb.NewDynamoDBRegistry( - p, cfg.TXTOwnerID, aws_dynamodb.NewFromConfig(aws.CreateDefaultV2Config(cfg), dynamodb.WithRegion(cfg.AWSDynamoDBRegion)), - cfg.AWSDynamoDBTable, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, - cfg.ExcludeDNSRecordTypes, []byte(cfg.TXTEncryptAESKey), cfg.TXTCacheInterval) - case NOOP: - r, err = noop.NewNoopRegistry(p) - case TXT: - r, err = txt.NewTXTRegistry( - p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, - cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, - cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, - cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey), cfg.TXTOwnerOld) - case AWSSD: - r, err = awssd.NewAWSSDRegistry(p, cfg.TXTOwnerID) - default: - err = fmt.Errorf("unknown registry: %s", cfg.Registry) - } - return r, err -} diff --git a/registry/txt/encryption_test.go b/registry/txt/encryption_test.go index 78720bc28c..ab069fe1c5 100644 --- a/registry/txt/encryption_test.go +++ b/registry/txt/encryption_test.go @@ -60,7 +60,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 := newRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, test.encEnabled, test.aesKeyRaw, "") if test.errorExpected { require.Error(t, err) } else { @@ -106,7 +106,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 := newRegistry(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)) @@ -143,7 +143,7 @@ func TestApplyRecordsWithEncryption(t *testing.T) { key := []byte("ZPitL0NGVQBZbTD6DwXJzD8RiStSazzYXQsdUowLURY=") - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, key, "") + r, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, key, "") _ = r.ApplyChanges(ctx, &plan.Changes{ Create: []*endpoint.Endpoint{ @@ -201,7 +201,7 @@ func TestApplyRecordsWithEncryptionKeyChanged(t *testing.T) { } for _, key := range withEncryptionKeys { - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key), "") + r, _ := newRegistry(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"), @@ -231,7 +231,7 @@ func TestApplyRecordsOnEncryptionKeyChangeWithKeyIdLabel(t *testing.T) { } for i, key := range withEncryptionKeys { - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte(key), "") + r, _ := newRegistry(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), diff --git a/registry/txt/registry.go b/registry/txt/registry.go index a9b54dab18..c932aaf25b 100644 --- a/registry/txt/registry.go +++ b/registry/txt/registry.go @@ -28,6 +28,8 @@ import ( log "github.com/sirupsen/logrus" + "sigs.k8s.io/external-dns/pkg/apis/externaldns" + "sigs.k8s.io/external-dns/registry" "sigs.k8s.io/external-dns/registry/mapper" "sigs.k8s.io/external-dns/endpoint" @@ -113,10 +115,18 @@ func (im *existingTXTs) reset() { im.entries = make(map[recordKey]struct{}) } -// NewTXTRegistry returns a new TXTRegistry object. When newFormatOnly is true, it will only +// New creates a TXTRegistry from the given configuration. +func New(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) { + return newRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, + cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, + cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, + cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey), cfg.TXTOwnerOld) +} + +// newRegistry returns a new TXTRegistry object. When newFormatOnly is true, it will only // generate new format TXT records, otherwise it generates both old and new formats for // backwards compatibility. -func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, +func newRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration, txtWildcardReplacement string, managedRecordTypes, excludeRecordTypes []string, txtEncryptEnabled bool, txtEncryptAESKey []byte, diff --git a/registry/txt/registry_test.go b/registry/txt/registry_test.go index 1228509dd0..3b5fb178de 100644 --- a/registry/txt/registry_test.go +++ b/registry/txt/registry_test.go @@ -52,20 +52,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 := newRegistry(p, "txt", "", "", time.Hour, "", []string{}, []string{}, false, nil, "") require.Error(t, err) - _, err = NewTXTRegistry(p, "", "txt", "", time.Hour, "", []string{}, []string{}, false, nil, "") + _, err = newRegistry(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 := newRegistry(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 = newRegistry(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 = newRegistry(p, "txt", "txt", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") require.Error(t, err) _, ok := r.mapper.(mapper.AffixNameMapper) @@ -74,16 +74,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 = newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") require.NoError(t, err) - _, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, aesKey, "") + _, err = newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, aesKey, "") require.NoError(t, err) - _, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, nil, "") + _, err = newRegistry(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 = newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, aesKey, "") require.NoError(t, err) _, ok = r.mapper.(mapper.AffixNameMapper) @@ -261,13 +261,13 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) { }, } - r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(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, _ = newRegistry(p, "TxT.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "") records, _ = r.Records(ctx) assert.True(t, testutils.SameEndpoints(records, expectedRecords)) @@ -426,13 +426,13 @@ func testTXTRegistryRecordsSuffixed(t *testing.T) { }, } - r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(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, _ = newRegistry(p, "", "-TxT", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") records, _ = r.Records(ctx) assert.True(t, testutils.SameEndpointLabels(records, expectedRecords)) @@ -583,7 +583,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) { }, } - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") records, _ := r.Records(ctx) assert.True(t, testutils.SameEndpoints(records, expectedRecords)) @@ -622,12 +622,12 @@ func testTXTRegistryRecordsPrefixedTemplated(t *testing.T) { }, } - r, _ := NewTXTRegistry(p, "txt-%{record_type}.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(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, _ = newRegistry(p, "TxT-%{record_type}.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "") records, _ = r.Records(ctx) assert.True(t, testutils.SameEndpoints(records, expectedRecords)) @@ -666,12 +666,12 @@ func testTXTRegistryRecordsSuffixedTemplated(t *testing.T) { }, } - r, _ := NewTXTRegistry(p, "", "txt%{record_type}", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(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, _ = newRegistry(p, "", "TxT%{record_type}", "owner", time.Hour, "wc", []string{}, []string{}, false, nil, "") records, _ = r.Records(ctx) assert.True(t, testutils.SameEndpoints(records, expectedRecords)) @@ -714,7 +714,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, _ := newRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") changes := &plan.Changes{ Create: []*endpoint.Endpoint{ @@ -795,7 +795,7 @@ func testTXTRegistryApplyChangesWithTemplatedPrefix(t *testing.T) { _ = p.ApplyChanges(ctx, &plan.Changes{ Create: []*endpoint.Endpoint{}, }) - r, _ := NewTXTRegistry(p, "prefix%{record_type}.", "", "owner-1", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "prefix%{record_type}.", "", "owner-1", time.Hour, "", []string{}, []string{}, false, nil, "") changes := &plan.Changes{ Create: []*endpoint.Endpoint{ newCNAMEEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", "owner-1", "ingress/default/my-ingress"), @@ -838,7 +838,7 @@ func testTXTRegistryApplyChangesWithTemplatedSuffix(t *testing.T) { p.OnApplyChanges = func(ctx context.Context, _ *plan.Changes) { assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey)) } - r, _ := NewTXTRegistry(p, "", "-%{record_type}suffix", "owner-2", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "-%{record_type}suffix", "owner-2", time.Hour, "", []string{}, []string{}, false, nil, "") changes := &plan.Changes{ Create: []*endpoint.Endpoint{ newCNAMEEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", "owner-2", "ingress/default/my-ingress"), @@ -905,7 +905,7 @@ func testTXTRegistryApplyChangesWithSuffix(t *testing.T) { }, }) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "wildcard", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "-txt", "owner", time.Hour, "wildcard", []string{}, []string{}, false, nil, "") changes := &plan.Changes{ Create: []*endpoint.Endpoint{ @@ -1001,7 +1001,7 @@ func testTXTRegistryApplyChangesNoPrefix(t *testing.T) { }, }) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") changes := &plan.Changes{ Create: []*endpoint.Endpoint{ @@ -1161,7 +1161,7 @@ func testTXTRegistryMissingRecordsNoPrefix(t *testing.T) { }, } - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS}, []string{}, false, nil, "") + r, _ := newRegistry(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)) @@ -1273,7 +1273,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, _ := newRegistry(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)) @@ -1370,7 +1370,7 @@ func TestNewTXTScheme(t *testing.T) { }, }) require.NoError(t, err) - r, err := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, err := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") require.NoError(t, err) changes := &plan.Changes{ @@ -1437,7 +1437,7 @@ func TestGenerateTXT(t *testing.T) { p := inmemory.NewInMemoryProvider() err := p.CreateZone(testZone) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") gotTXT := r.generateTXTRecord(record) assert.Equal(t, expectedTXT, gotTXT) } @@ -1457,7 +1457,7 @@ func TestGenerateTXTWithMigration(t *testing.T) { p := inmemory.NewInMemoryProvider() err := p.CreateZone(testZone) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") gotTXTBeforeMigration := r.generateTXTRecord(record) assert.Equal(t, expectedTXTBeforeMigration, gotTXTBeforeMigration) @@ -1472,7 +1472,7 @@ func TestGenerateTXTWithMigration(t *testing.T) { }, } - rMigrated, _ := NewTXTRegistry(p, "", "", "foobar", time.Hour, "", []string{}, []string{}, false, nil, "owner") + rMigrated, _ := newRegistry(p, "", "", "foobar", time.Hour, "", []string{}, []string{}, false, nil, "owner") gotTXTAfterMigration := rMigrated.generateTXTRecord(record) assert.Equal(t, expectedTXTAfterMigration, gotTXTAfterMigration) @@ -1493,7 +1493,7 @@ func TestGenerateTXTForAAAA(t *testing.T) { p := inmemory.NewInMemoryProvider() err := p.CreateZone(testZone) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") gotTXT := r.generateTXTRecord(record) assert.Equal(t, expectedTXT, gotTXT) } @@ -1511,7 +1511,7 @@ func TestFailGenerateTXT(t *testing.T) { p := inmemory.NewInMemoryProvider() err := p.CreateZone(testZone) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") gotTXT := r.generateTXTRecord(cnameRecord) assert.Equal(t, expectedTXT, gotTXT) } @@ -1531,7 +1531,7 @@ func TestTXTRegistryApplyChangesEncrypt(t *testing.T) { }) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte("12345678901234567890123456789012"), "") + r, _ := newRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte("12345678901234567890123456789012"), "") records, _ := r.Records(ctx) changes := &plan.Changes{ Delete: records, @@ -1579,7 +1579,7 @@ func TestMultiClusterDifferentRecordTypeOwnership(t *testing.T) { }) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "_owner.", "", "bar", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(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 @@ -1662,7 +1662,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, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") records := r.generateTXTRecord(tc.endpoint) assert.Len(t, records, tc.expectedRecords, tc.description) @@ -1692,7 +1692,7 @@ func TestApplyChangesWithNewFormatOnly(t *testing.T) { require.NoError(t, err) ctx := t.Context() - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") changes := &plan.Changes{ Create: []*endpoint.Endpoint{ @@ -1742,7 +1742,7 @@ func TestTXTRegistryRecordsWithEmptyTargets(t *testing.T) { }) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil, "") hook := logtest.LogsUnderTestWithLogLevel(log.ErrorLevel, t) records, err := r.Records(ctx) require.NoError(t, err) @@ -1947,7 +1947,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 := newRegistry(p, "", "", ownerId, time.Hour, "", managedRecords, nil, false, nil, "") assert.NoError(t, err) expectedRecords := append(existing, expectedCreate...) // nolint:gocritic @@ -1988,7 +1988,7 @@ func TestTXTRecordMigration(t *testing.T) { err := p.CreateZone(testZone) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "%{record_type}-", "", "foo", time.Hour, "", []string{}, []string{}, false, nil, "") + r, _ := newRegistry(p, "%{record_type}-", "", "foo", time.Hour, "", []string{}, []string{}, false, nil, "") err = r.ApplyChanges(ctx, &plan.Changes{ Create: []*endpoint.Endpoint{ @@ -2012,7 +2012,7 @@ func TestTXTRecordMigration(t *testing.T) { assert.Equal(t, expectedTXTRecords[0].Targets, newTXTRecord[0].Targets) - r, _ = NewTXTRegistry(p, "%{record_type}-", "", "foobar", time.Hour, "", []string{}, []string{}, false, nil, "foo") + r, _ = newRegistry(p, "%{record_type}-", "", "foobar", time.Hour, "", []string{}, []string{}, false, nil, "foo") updatedRecords, _ := r.Records(ctx) @@ -2040,7 +2040,7 @@ func TestRecreateRecordAfterDeletion(t *testing.T) { err := p.CreateZone(testZone) require.NoError(t, err) - r, _ := NewTXTRegistry(p, "%{record_type}-", "", "foo", 0, "", []string{endpoint.RecordTypeA}, []string{}, false, nil, "") + r, _ := newRegistry(p, "%{record_type}-", "", "foo", 0, "", []string{endpoint.RecordTypeA}, []string{}, false, nil, "") createdRecords := newEndpointWithOwnerAndLabels("bar.test-zone.example.org", "1.2.3.4", endpoint.RecordTypeA, ownerID, nil) txtRecord := r.generateTXTRecord(createdRecords) From 9c2a7886b035a9680f08018e98af714c47caac56 Mon Sep 17 00:00:00 2001 From: ivan katliarchuk Date: Sun, 15 Mar 2026 10:10:08 +0000 Subject: [PATCH 2/5] refactor(registry): introduce factory with uniform New constructors Signed-off-by: ivan katliarchuk --- registry/registry.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 3117c42056..3c723c89b8 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -23,14 +23,16 @@ import ( "sigs.k8s.io/external-dns/plan" ) -// Registry is an interface which should enables ownership concept in external-dns -// Records() returns ALL records registered with DNS provider -// each entry includes owner information -// ApplyChanges(changes *plan.Changes) propagates the changes to the DNS Provider API and correspondingly updates ownership depending on type of registry being used +// Registry tracks ownership of DNS records managed by external-dns. type Registry interface { + // Records returns all DNS records known to the registry, including ownership metadata. Records(ctx context.Context) ([]*endpoint.Endpoint, error) + // ApplyChanges propagates the given changes to the DNS provider and updates ownership records accordingly. ApplyChanges(ctx context.Context, changes *plan.Changes) error + // AdjustEndpoints normalises endpoints before they are processed by the planner. AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpoint.Endpoint, error) + // GetDomainFilter returns the domain filter configured for the underlying provider. GetDomainFilter() endpoint.DomainFilterInterface + // OwnerID returns the owner identifier used to claim DNS records. OwnerID() string } From 82e1a5bbe062f867af3d6116f62f75518a0f401d Mon Sep 17 00:00:00 2001 From: ivan katliarchuk Date: Sun, 15 Mar 2026 10:35:13 +0000 Subject: [PATCH 3/5] refactor(registry): introduce factory with uniform New constructors Signed-off-by: ivan katliarchuk --- controller/controller_test.go | 6 +++--- controller/execute.go | 2 +- controller/metrics_test.go | 2 +- registry/factory/registry.go | 4 ++-- registry/factory/registry_test.go | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/controller/controller_test.go b/controller/controller_test.go index 83839be6f5..8e1c32ccaa 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -213,7 +213,7 @@ func TestRunOnce(t *testing.T) { emitter := fake.NewFakeEventEmitter() - r, err := registryfactory.SelectRegistry(cfg, provider) + r, err := registryfactory.Select(cfg, provider) require.NoError(t, err) // Run our controller once to trigger the validation. @@ -243,7 +243,7 @@ func TestRun(t *testing.T) { cfg := getTestConfig() provider := getTestProvider() - r, err := registryfactory.SelectRegistry(cfg, provider) + r, err := registryfactory.Select(cfg, provider) require.NoError(t, err) // Run our controller once to trigger the validation. @@ -341,7 +341,7 @@ func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint. provider := &filteredMockProvider{ RecordsStore: providerEndpoints, } - r, err := registryfactory.SelectRegistry(cfg, provider) + r, err := registryfactory.Select(cfg, provider) require.NoError(t, err) ctrl := &Controller{ diff --git a/controller/execute.go b/controller/execute.go index 3f005efccb..36a7615225 100644 --- a/controller/execute.go +++ b/controller/execute.go @@ -373,7 +373,7 @@ func buildController( if !ok { return nil, fmt.Errorf("unknown policy: %s", cfg.Policy) } - reg, err := registryfactory.SelectRegistry(cfg, p) + reg, err := registryfactory.Select(cfg, p) if err != nil { return nil, err } diff --git a/controller/metrics_test.go b/controller/metrics_test.go index a1f8aa79d4..d44cdebd51 100644 --- a/controller/metrics_test.go +++ b/controller/metrics_test.go @@ -332,7 +332,7 @@ func newMixedRecordsFixture() *Controller { provider := &filteredMockProvider{ RecordsStore: providerEndpoints, } - r, _ := registryfactory.SelectRegistry(cfg, provider) + r, _ := registryfactory.Select(cfg, provider) return &Controller{ Source: source, diff --git a/registry/factory/registry.go b/registry/factory/registry.go index 8753ec63fc..e122e6f12e 100644 --- a/registry/factory/registry.go +++ b/registry/factory/registry.go @@ -31,8 +31,8 @@ import ( // RegistryConstructor is a function that creates a Registry from configuration and a provider. type RegistryConstructor func(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) -// SelectRegistry creates a registry based on the given configuration. -func SelectRegistry(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) { +// Select creates a registry based on the given configuration. +func Select(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) { constructor, ok := registries(cfg.Registry) if !ok { return nil, fmt.Errorf("unknown registry: %s", cfg.Registry) diff --git a/registry/factory/registry_test.go b/registry/factory/registry_test.go index f2ff45fdfa..45a87a8f59 100644 --- a/registry/factory/registry_test.go +++ b/registry/factory/registry_test.go @@ -112,7 +112,7 @@ func TestSelectRegistry(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - reg, err := SelectRegistry(tt.cfg, tt.provider) + reg, err := Select(tt.cfg, tt.provider) if tt.wantErr { require.Nil(t, reg) @@ -130,7 +130,7 @@ func TestSelectRegistryUnknown(t *testing.T) { cfg := externaldns.NewConfig() cfg.Registry = "nope" - reg, err := SelectRegistry(cfg, inmemory.NewInMemoryProvider()) + reg, err := Select(cfg, inmemory.NewInMemoryProvider()) require.Error(t, err) require.Nil(t, reg) } From 2a7b885dc271c152074b6c63c56381fcd38685b4 Mon Sep 17 00:00:00 2001 From: ivan katliarchuk Date: Sun, 15 Mar 2026 23:56:39 +0000 Subject: [PATCH 4/5] refactor(registry): introduce factory with uniform New constructors Signed-off-by: ivan katliarchuk --- controller/controller_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/controller_test.go b/controller/controller_test.go index fc5ba86654..5dd0dbd577 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -33,7 +33,7 @@ import ( "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" "sigs.k8s.io/external-dns/provider/fakes" - "sigs.k8s.io/external-dns/registry" + registryfactory "sigs.k8s.io/external-dns/registry/factory" "sigs.k8s.io/external-dns/registry/noop" "github.com/stretchr/testify/assert" @@ -584,7 +584,7 @@ func TestRunOnce_EmitChangeEvent(t *testing.T) { WithRefObject(&events.ObjectReference{}), }, nil) - r, err := registry.SelectRegistry(getTestConfig(), &fakes.MockProvider{ApplyChangesErr: tt.applyErr}) + r, err := registryfactory.Select(getTestConfig(), &fakes.MockProvider{ApplyChangesErr: tt.applyErr}) require.NoError(t, err) emitter := fake.NewFakeEventEmitter() From 635d7a381ab5ce73605cf3a04a6515617ea56913 Mon Sep 17 00:00:00 2001 From: ivan katliarchuk Date: Mon, 16 Mar 2026 08:29:23 +0000 Subject: [PATCH 5/5] refactor(registry): introduce factory with uniform New constructors Signed-off-by: ivan katliarchuk --- pkg/apis/externaldns/constants.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/apis/externaldns/constants.go b/pkg/apis/externaldns/constants.go index 10610df977..5c529896d3 100644 --- a/pkg/apis/externaldns/constants.go +++ b/pkg/apis/externaldns/constants.go @@ -21,7 +21,7 @@ const ( RegistryNoop = "noop" RegistryDynamoDB = "dynamodb" RegistryAWSSD = "aws-sd" - + ProviderAkamai = "akamai" ProviderAlibabaCloud = "alibabacloud" ProviderAWS = "aws"