diff --git a/controller/controller.go b/controller/controller.go index 1a87d335a..839327bb9 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -117,6 +117,8 @@ type Controller struct { nextRunAt time.Time // The nextRunAtMux is for atomic updating of nextRunAt nextRunAtMux sync.Mutex + // DNS record types that will be considered for management + ManagedRecordTypes []string } // RunOnce runs a single iteration of a reconciliation loop. @@ -147,6 +149,7 @@ func (c *Controller) RunOnce(ctx context.Context) error { Desired: endpoints, DomainFilter: c.DomainFilter, PropertyComparator: c.Registry.PropertyValuesEqual, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } plan = plan.Calculate() diff --git a/main.go b/main.go index 3b2cf7923..ccfd1d247 100644 --- a/main.go +++ b/main.go @@ -326,11 +326,12 @@ func main() { } ctrl := controller.Controller{ - Source: endpointsSource, - Registry: r, - Policy: policy, - Interval: cfg.Interval, - DomainFilter: domainFilter, + Source: endpointsSource, + Registry: r, + Policy: policy, + Interval: cfg.Interval, + DomainFilter: domainFilter, + ManagedRecordTypes: cfg.ManagedDNSRecordTypes, } if cfg.Once { diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 01a09cdfa..141a62b7e 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -22,6 +22,8 @@ import ( "strconv" "time" + "sigs.k8s.io/external-dns/endpoint" + "github.com/alecthomas/kingpin" "github.com/sirupsen/logrus" @@ -148,6 +150,7 @@ type Config struct { TransIPAccountName string TransIPPrivateKeyFile string DigitalOceanAPIPageSize int + ManagedDNSRecordTypes []string } var defaultConfig = &Config{ @@ -250,6 +253,7 @@ var defaultConfig = &Config{ TransIPAccountName: "", TransIPPrivateKeyFile: "", DigitalOceanAPIPageSize: 50, + ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } // NewConfig returns new Config object @@ -327,6 +331,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("crd-source-apiversion", "API version of the CRD for crd source, e.g. `externaldns.k8s.io/v1alpha1`, valid only when using crd source").Default(defaultConfig.CRDSourceAPIVersion).StringVar(&cfg.CRDSourceAPIVersion) app.Flag("crd-source-kind", "Kind of the CRD for the crd source in API group and version specified by crd-source-apiversion").Default(defaultConfig.CRDSourceKind).StringVar(&cfg.CRDSourceKind) app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter) + app.Flag("managed-record-types", "Comma separated list of record types to manage (default: A, CNAME) (supported records: CNAME, A, NS").Default("A", "CNAME").StringsVar(&cfg.ManagedDNSRecordTypes) // Flags related to providers app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns") diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 02fd16ffd..9cd8db364 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -22,6 +22,8 @@ import ( "testing" "time" + "sigs.k8s.io/external-dns/endpoint" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -102,6 +104,7 @@ var ( TransIPAccountName: "", TransIPPrivateKeyFile: "", DigitalOceanAPIPageSize: 50, + ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } overriddenConfig = &Config{ @@ -186,6 +189,7 @@ var ( TransIPAccountName: "transip", TransIPPrivateKeyFile: "/path/to/transip.key", DigitalOceanAPIPageSize: 100, + ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } ) diff --git a/plan/plan.go b/plan/plan.go index 3e577d600..3ae346fcc 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -43,6 +43,8 @@ type Plan struct { DomainFilter endpoint.DomainFilter // Property comparator compares custom properties of providers PropertyComparator PropertyComparator + // DNS record types that will be considered for management + ManagedRecords []string } // Changes holds lists of actions to be executed by dns providers @@ -119,10 +121,10 @@ func (t planTable) addCandidate(e *endpoint.Endpoint) { func (p *Plan) Calculate() *Plan { t := newPlanTable() - for _, current := range filterRecordsForPlan(p.Current, p.DomainFilter) { + for _, current := range filterRecordsForPlan(p.Current, p.DomainFilter, p.ManagedRecords) { t.addCurrent(current) } - for _, desired := range filterRecordsForPlan(p.Desired, p.DomainFilter) { + for _, desired := range filterRecordsForPlan(p.Desired, p.DomainFilter, p.ManagedRecords) { t.addCandidate(desired) } @@ -155,9 +157,10 @@ func (p *Plan) Calculate() *Plan { } plan := &Plan{ - Current: p.Current, - Desired: p.Desired, - Changes: changes, + Current: p.Current, + Desired: p.Desired, + Changes: changes, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } return plan @@ -224,7 +227,7 @@ func (p *Plan) shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) // Per RFC 1034, CNAME records conflict with all other records - it is the // only record with this property. The behavior of the planner may need to be // made more sophisticated to codify this. -func filterRecordsForPlan(records []*endpoint.Endpoint, domainFilter endpoint.DomainFilter) []*endpoint.Endpoint { +func filterRecordsForPlan(records []*endpoint.Endpoint, domainFilter endpoint.DomainFilter, managedRecords []string) []*endpoint.Endpoint { filtered := []*endpoint.Endpoint{} for _, record := range records { @@ -232,14 +235,8 @@ func filterRecordsForPlan(records []*endpoint.Endpoint, domainFilter endpoint.Do if !domainFilter.Match(record.DNSName) { continue } - - // Explicitly specify which records we want to use for planning. - // TODO: Add AAAA records as well when they are supported. - switch record.RecordType { - case endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS: + if isManagedRecord(record.RecordType, managedRecords) { filtered = append(filtered, record) - default: - continue } } @@ -280,3 +277,12 @@ func CompareBoolean(defaultValue bool, name, current, previous string) bool { return v1 == v2 } + +func isManagedRecord(record string, managedRecords []string) bool { + for _, r := range managedRecords { + if record == r { + return true + } + } + return false +} diff --git a/plan/plan_test.go b/plan/plan_test.go index 5ea0f55ab..18b889143 100644 --- a/plan/plan_test.go +++ b/plan/plan_test.go @@ -205,9 +205,10 @@ func (suite *PlanTestSuite) TestSyncFirstRound() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -226,9 +227,10 @@ func (suite *PlanTestSuite) TestSyncSecondRound() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -247,9 +249,10 @@ func (suite *PlanTestSuite) TestSyncSecondRoundMigration() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -268,9 +271,10 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithTTLChange() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -289,9 +293,10 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificChange() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -316,6 +321,7 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificDefaultFalse( PropertyComparator: func(name, previous, current string) bool { return CompareBoolean(false, name, previous, current) }, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -368,9 +374,10 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -410,9 +417,10 @@ func (suite *PlanTestSuite) TestDifferentTypes() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -431,9 +439,10 @@ func (suite *PlanTestSuite) TestIgnoreTXT() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -452,9 +461,10 @@ func (suite *PlanTestSuite) TestRemoveEndpoint() { expectedDelete := []*endpoint.Endpoint{suite.bar192A} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -473,9 +483,10 @@ func (suite *PlanTestSuite) TestRemoveEndpointWithUpsert() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&UpsertOnlyPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&UpsertOnlyPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -495,9 +506,10 @@ func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceReplace() { expectedDelete := []*endpoint.Endpoint{suite.bar192A} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -518,9 +530,10 @@ func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceRetain() { expectedDelete := []*endpoint.Endpoint{suite.bar192A} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -540,9 +553,10 @@ func (suite *PlanTestSuite) TestMultipleRecordsSameNameDifferentSetIdentifier() expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -562,9 +576,10 @@ func (suite *PlanTestSuite) TestSetIdentifierUpdateCreatesAndDeletes() { expectedDelete := []*endpoint.Endpoint{suite.multiple2} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -584,10 +599,11 @@ func (suite *PlanTestSuite) TestDomainFiltersInitial() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, - DomainFilter: endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"}), + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + DomainFilter: endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"}), + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -607,10 +623,11 @@ func (suite *PlanTestSuite) TestDomainFiltersUpdate() { expectedDelete := []*endpoint.Endpoint{} p := &Plan{ - Policies: []Policy{&SyncPolicy{}}, - Current: current, - Desired: desired, - DomainFilter: endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"}), + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + DomainFilter: endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"}), + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } changes := p.Calculate().Changes @@ -808,6 +825,7 @@ func TestShouldUpdateProviderSpecific(tt *testing.T) { Current: []*endpoint.Endpoint{test.current}, Desired: []*endpoint.Endpoint{test.desired}, PropertyComparator: test.propertyComparator, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } b := plan.shouldUpdateProviderSpecific(test.desired, test.current) assert.Equal(t, test.shouldUpdate, b) diff --git a/provider/aws/aws_test.go b/provider/aws/aws_test.go index b83e892ec..40e522a49 100644 --- a/provider/aws/aws_test.go +++ b/provider/aws/aws_test.go @@ -1152,6 +1152,7 @@ func TestAWSHealthTargetAnnotation(tt *testing.T) { Current: []*endpoint.Endpoint{test.current}, Desired: []*endpoint.Endpoint{test.desired}, PropertyComparator: provider.PropertyValuesEqual, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } plan = plan.Calculate() assert.Equal(t, test.shouldUpdate, len(plan.Changes.UpdateNew) == 1) diff --git a/provider/cloudflare/cloudflare_test.go b/provider/cloudflare/cloudflare_test.go index 440cc8b65..fae840b2b 100644 --- a/provider/cloudflare/cloudflare_test.go +++ b/provider/cloudflare/cloudflare_test.go @@ -229,7 +229,7 @@ func (m *mockCloudFlareClient) ZoneDetails(zoneID string) (cloudflare.Zone, erro return cloudflare.Zone{}, errors.New("Unknown zoneID: " + zoneID) } -func AssertActions(t *testing.T, provider *CloudFlareProvider, endpoints []*endpoint.Endpoint, actions []MockAction, args ...interface{}) { +func AssertActions(t *testing.T, provider *CloudFlareProvider, endpoints []*endpoint.Endpoint, actions []MockAction, managedRecords []string, args ...interface{}) { t.Helper() var client *mockCloudFlareClient @@ -250,9 +250,10 @@ func AssertActions(t *testing.T, provider *CloudFlareProvider, endpoints []*endp } plan := &plan.Plan{ - Current: records, - Desired: endpoints, - DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + Current: records, + Desired: endpoints, + DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + ManagedRecords: managedRecords, } changes := plan.Calculate().Changes @@ -305,7 +306,10 @@ func TestCloudflareA(t *testing.T) { Proxied: false, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) + } func TestCloudflareCname(t *testing.T) { @@ -340,7 +344,9 @@ func TestCloudflareCname(t *testing.T) { Proxied: false, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareCustomTTL(t *testing.T) { @@ -365,7 +371,9 @@ func TestCloudflareCustomTTL(t *testing.T) { Proxied: false, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareProxiedDefault(t *testing.T) { @@ -389,7 +397,9 @@ func TestCloudflareProxiedDefault(t *testing.T) { Proxied: true, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareProxiedOverrideTrue(t *testing.T) { @@ -419,7 +429,9 @@ func TestCloudflareProxiedOverrideTrue(t *testing.T) { Proxied: true, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareProxiedOverrideFalse(t *testing.T) { @@ -449,7 +461,9 @@ func TestCloudflareProxiedOverrideFalse(t *testing.T) { Proxied: false, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareProxiedOverrideIllegal(t *testing.T) { @@ -479,7 +493,9 @@ func TestCloudflareProxiedOverrideIllegal(t *testing.T) { Proxied: true, }, }, - }) + }, + []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, + ) } func TestCloudflareSetProxied(t *testing.T) { @@ -525,7 +541,7 @@ func TestCloudflareSetProxied(t *testing.T) { Proxied: testCase.proxiable, }, }, - }, testCase.recordType+" record on "+testCase.domain) + }, []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS}, testCase.recordType+" record on "+testCase.domain) } } @@ -1038,6 +1054,7 @@ func TestProviderPropertiesIdempotency(t *testing.T) { Current: current, Desired: desired, PropertyComparator: provider.PropertyValuesEqual, + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } plan = *plan.Calculate() @@ -1091,7 +1108,8 @@ func TestCloudflareComplexUpdate(t *testing.T) { }, }, }, - DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } planned := plan.Calculate() @@ -1178,9 +1196,10 @@ func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) { provider.AdjustEndpoints(endpoints) plan := &plan.Plan{ - Current: records, - Desired: endpoints, - DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + Current: records, + Desired: endpoints, + DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}), + ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, } planned := plan.Calculate() diff --git a/registry/txt_test.go b/registry/txt_test.go index d4993cb71..b3e20dd6e 100644 --- a/registry/txt_test.go +++ b/registry/txt_test.go @@ -172,9 +172,9 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) { }, }, { - DNSName: "*.wildcard.test-zone.example.org", - Targets: endpoint.Targets{"foo.loadbalancer.com"}, - RecordType: endpoint.RecordTypeCNAME, + DNSName: "*.wildcard.test-zone.example.org", + Targets: endpoint.Targets{"foo.loadbalancer.com"}, + RecordType: endpoint.RecordTypeCNAME, Labels: map[string]string{ endpoint.OwnerLabelKey: "owner", },