diff --git a/controller/controller.go b/controller/controller.go index cc4feefb72..a18cf6237e 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -135,10 +135,11 @@ func (c *Controller) RunOnce(ctx context.Context) error { sourceEndpointsTotal.Set(float64(len(endpoints))) plan := &plan.Plan{ - Policies: []plan.Policy{c.Policy}, - Current: records, - Desired: endpoints, - DomainFilter: c.DomainFilter, + Policies: []plan.Policy{c.Policy}, + Current: records, + Desired: endpoints, + DomainFilter: c.DomainFilter, + PropertyComparator: c.Registry.PropertyValuesEqual, } plan = plan.Calculate() diff --git a/controller/controller_test.go b/controller/controller_test.go index 5a7cb74250..81579f642f 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -35,6 +35,7 @@ import ( // mockProvider returns mock endpoints and validates changes. type mockProvider struct { + provider.BaseProvider RecordsStore []*endpoint.Endpoint ExpectChanges *plan.Changes } diff --git a/plan/plan.go b/plan/plan.go index 675eecf5fc..27ff694b9b 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -18,11 +18,15 @@ package plan import ( "fmt" + "strconv" "strings" + log "github.com/sirupsen/logrus" "sigs.k8s.io/external-dns/endpoint" ) +type PropertyComparator func(name string, previous string, current string) bool + // Plan can convert a list of desired and current records to a series of create, // update and delete actions. type Plan struct { @@ -37,6 +41,8 @@ type Plan struct { Changes *Changes // DomainFilter matches DNS names DomainFilter endpoint.DomainFilter + // Property comparator compares custom properties of providers + PropertyComparator PropertyComparator } // Changes holds lists of actions to be executed by dns providers @@ -135,7 +141,7 @@ func (p *Plan) Calculate() *Plan { if row.current != nil && len(row.candidates) > 0 { //dns name is taken update := t.resolver.ResolveUpdate(row.current, row.candidates) // compare "update" to "current" to figure out if actual update is required - if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || shouldUpdateProviderSpecific(update, row.current) { + if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || p.shouldUpdateProviderSpecific(update, row.current) { inheritOwner(row.current, update) changes.UpdateNew = append(changes.UpdateNew, update) changes.UpdateOld = append(changes.UpdateOld, row.current) @@ -178,44 +184,39 @@ func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool { return desired.RecordTTL != current.RecordTTL } -func shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool { - if current.ProviderSpecific == nil && len(desired.ProviderSpecific) == 0 { - return false - } - for _, c := range current.ProviderSpecific { - // don't consider target health when detecting changes - // see: https://github.com/kubernetes-sigs/external-dns/issues/869#issuecomment-458576954 - if c.Name == "aws/evaluate-target-health" { - continue - } +func (p *Plan) shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool { + desiredProperties := map[string]endpoint.ProviderSpecificProperty{} - found := false + if desired.ProviderSpecific != nil { for _, d := range desired.ProviderSpecific { - if d.Name == c.Name { - if d.Value != c.Value { - // provider-specific attribute updated - return true - } - found = true - break - } - } - if !found { - // provider-specific attribute deleted - return true + desiredProperties[d.Name] = d } } - for _, d := range desired.ProviderSpecific { - found := false + if current.ProviderSpecific != nil { for _, c := range current.ProviderSpecific { - if d.Name == c.Name { - found = true - break + // don't consider target health when detecting changes + // see: https://github.com/kubernetes-sigs/external-dns/issues/869#issuecomment-458576954 + if c.Name == "aws/evaluate-target-health" { + continue + } + + if d, ok := desiredProperties[c.Name]; ok { + if p.PropertyComparator != nil { + if !p.PropertyComparator(c.Name, c.Value, d.Value) { + return true + } + } else if c.Value != d.Value { + return true + } + } else { + if p.PropertyComparator != nil { + if !p.PropertyComparator(c.Name, c.Value, "") { + return true + } + } else if c.Value != "" { + return true + } } - } - if !found { - // provider-specific attribute added - return true } } @@ -260,3 +261,27 @@ func normalizeDNSName(dnsName string) string { } return s } + +func CompareBoolean(defaultValue bool, name, current, previous string) bool { + var err error + + v1, v2 := defaultValue, defaultValue + + if previous != "" { + v1, err = strconv.ParseBool(previous) + if err != nil { + log.Errorf("Failed to parse previous property [%s]: %v", name, previous) + v1 = defaultValue + } + } + + if current != "" { + v2, err = strconv.ParseBool(current) + if err != nil { + log.Errorf("Failed to parse current property [%s]: %v", name, current) + v2 = defaultValue + } + } + + return v1 == v2 +} diff --git a/plan/plan_test.go b/plan/plan_test.go index fafcb2ce8f..1a34161968 100644 --- a/plan/plan_test.go +++ b/plan/plan_test.go @@ -38,6 +38,7 @@ type PlanTestSuite struct { bar127AWithTTL *endpoint.Endpoint bar127AWithProviderSpecificTrue *endpoint.Endpoint bar127AWithProviderSpecificFalse *endpoint.Endpoint + bar127AWithProviderSpecificUnset *endpoint.Endpoint bar192A *endpoint.Endpoint multiple1 *endpoint.Endpoint multiple2 *endpoint.Endpoint @@ -138,6 +139,15 @@ func (suite *PlanTestSuite) SetupTest() { }, }, } + suite.bar127AWithProviderSpecificUnset = &endpoint.Endpoint{ + DNSName: "bar", + Targets: endpoint.Targets{"127.0.0.1"}, + RecordType: "A", + Labels: map[string]string{ + endpoint.ResourceLabelKey: "ingress/default/bar-127", + }, + ProviderSpecific: endpoint.ProviderSpecific{}, + } suite.bar192A = &endpoint.Endpoint{ DNSName: "bar", Targets: endpoint.Targets{"192.168.0.1"}, @@ -291,6 +301,54 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificChange() { validateEntries(suite.T(), changes.Delete, expectedDelete) } +func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificDefaultFalse() { + current := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificFalse} + desired := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificUnset} + expectedCreate := []*endpoint.Endpoint{} + expectedUpdateOld := []*endpoint.Endpoint{} + expectedUpdateNew := []*endpoint.Endpoint{} + expectedDelete := []*endpoint.Endpoint{} + + p := &Plan{ + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + PropertyComparator: func(name, previous, current string) bool { + return CompareBoolean(false, name, previous, current) + }, + } + + 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) TestSyncSecondRoundWithProviderSpecificDefualtTrue() { + current := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificTrue} + desired := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificUnset} + expectedCreate := []*endpoint.Endpoint{} + expectedUpdateOld := []*endpoint.Endpoint{} + expectedUpdateNew := []*endpoint.Endpoint{} + expectedDelete := []*endpoint.Endpoint{} + + p := &Plan{ + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + PropertyComparator: func(name, previous, current string) bool { + return CompareBoolean(true, name, previous, current) + }, + } + + 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) TestSyncSecondRoundWithOwnerInherited() { current := []*endpoint.Endpoint{suite.fooV1Cname} desired := []*endpoint.Endpoint{suite.fooV2Cname} diff --git a/provider/akamai/akamai.go b/provider/akamai/akamai.go index 9e3c720887..e612d127aa 100644 --- a/provider/akamai/akamai.go +++ b/provider/akamai/akamai.go @@ -61,6 +61,7 @@ type AkamaiConfig struct { // AkamaiProvider implements the DNS provider for Akamai. type AkamaiProvider struct { + provider.BaseProvider domainFilter endpoint.DomainFilter zoneIDFilter provider.ZoneIDFilter config edgegrid.Config diff --git a/provider/alibabacloud/alibaba_cloud.go b/provider/alibabacloud/alibaba_cloud.go index 79a8351ffc..84a71fbf22 100644 --- a/provider/alibabacloud/alibaba_cloud.go +++ b/provider/alibabacloud/alibaba_cloud.go @@ -67,6 +67,7 @@ type AlibabaCloudPrivateZoneAPI interface { // AlibabaCloudProvider implements the DNS provider for Alibaba Cloud. type AlibabaCloudProvider struct { + provider.BaseProvider domainFilter endpoint.DomainFilter zoneIDFilter provider.ZoneIDFilter // Private Zone only MaxChangeCount int diff --git a/provider/aws/aws.go b/provider/aws/aws.go index 91961c9e1f..1b009777c2 100644 --- a/provider/aws/aws.go +++ b/provider/aws/aws.go @@ -114,6 +114,7 @@ type Route53API interface { // AWSProvider is an implementation of Provider for AWS Route53. type AWSProvider struct { + provider.BaseProvider client Route53API dryRun bool batchChangeSize int diff --git a/provider/awssd/aws_sd.go b/provider/awssd/aws_sd.go index 15eb64c529..34016a7e31 100644 --- a/provider/awssd/aws_sd.go +++ b/provider/awssd/aws_sd.go @@ -37,6 +37,7 @@ import ( "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" ) const ( @@ -73,6 +74,7 @@ type AWSSDClient interface { // AWSSDProvider is an implementation of Provider for AWS Cloud Map. type AWSSDProvider struct { + provider.BaseProvider client AWSSDClient dryRun bool // only consider namespaces ending in this suffix diff --git a/provider/azure/azure.go b/provider/azure/azure.go index 6fb819f88b..d216f5dde2 100644 --- a/provider/azure/azure.go +++ b/provider/azure/azure.go @@ -67,6 +67,7 @@ type RecordSetsClient interface { // AzureProvider implements the DNS provider for Microsoft's Azure cloud platform. type AzureProvider struct { + provider.BaseProvider domainFilter endpoint.DomainFilter zoneIDFilter provider.ZoneIDFilter dryRun bool diff --git a/provider/azure/azure_private_dns.go b/provider/azure/azure_private_dns.go index f4c8fd0a1d..d07279d81e 100644 --- a/provider/azure/azure_private_dns.go +++ b/provider/azure/azure_private_dns.go @@ -46,6 +46,7 @@ type PrivateRecordSetsClient interface { // AzurePrivateDNSProvider implements the DNS provider for Microsoft's Azure Private DNS service type AzurePrivateDNSProvider struct { + provider.BaseProvider domainFilter endpoint.DomainFilter zoneIDFilter provider.ZoneIDFilter dryRun bool diff --git a/provider/cloudflare/cloudflare.go b/provider/cloudflare/cloudflare.go index 2edbaf6243..eb8d67c116 100644 --- a/provider/cloudflare/cloudflare.go +++ b/provider/cloudflare/cloudflare.go @@ -101,6 +101,7 @@ func (z zoneService) ListZonesContext(ctx context.Context, opts ...cloudflare.Re // CloudFlareProvider is an implementation of Provider for CloudFlare DNS. type CloudFlareProvider struct { + provider.BaseProvider Client cloudFlareDNS // only consider hosted zones managing domains ending in this suffix domainFilter endpoint.DomainFilter @@ -211,6 +212,14 @@ func (p *CloudFlareProvider) ApplyChanges(ctx context.Context, changes *plan.Cha return p.submitChanges(ctx, combinedChanges) } +func (p *CloudFlareProvider) PropertyValuesEqual(name string, previous string, current string) bool { + if name == source.CloudflareProxiedKey { + return plan.CompareBoolean(p.proxiedByDefault, name, previous, current) + } + + return p.BaseProvider.PropertyValuesEqual(name, previous, current) +} + // submitChanges takes a zone and a collection of Changes and sends them as a single transaction. func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloudFlareChange) error { // return early if there is nothing to change diff --git a/provider/cloudflare/cloudflare_test.go b/provider/cloudflare/cloudflare_test.go index 521cb3ba19..2b5d97d41b 100644 --- a/provider/cloudflare/cloudflare_test.go +++ b/provider/cloudflare/cloudflare_test.go @@ -903,6 +903,98 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { } } +func TestProviderPropertiesIdempotency(t *testing.T) { + testCases := []struct { + ProviderProxiedByDefault bool + RecordsAreProxied bool + ShouldBeUpdated bool + }{ + { + ProviderProxiedByDefault: false, + RecordsAreProxied: false, + ShouldBeUpdated: false, + }, + { + ProviderProxiedByDefault: true, + RecordsAreProxied: true, + ShouldBeUpdated: false, + }, + { + ProviderProxiedByDefault: true, + RecordsAreProxied: false, + ShouldBeUpdated: true, + }, + { + ProviderProxiedByDefault: false, + RecordsAreProxied: true, + ShouldBeUpdated: true, + }, + } + + for _, test := range testCases { + client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{ + "001": { + { + ID: "1234567890", + ZoneID: "001", + Name: "foobar.bar.com", + Type: endpoint.RecordTypeA, + TTL: 120, + Content: "1.2.3.4", + Proxied: test.RecordsAreProxied, + }, + }, + }) + + provider := &CloudFlareProvider{ + Client: client, + proxiedByDefault: test.ProviderProxiedByDefault, + } + ctx := context.Background() + + current, err := provider.Records(ctx) + if err != nil { + t.Errorf("should not fail, %s", err) + } + assert.Equal(t, 1, len(current)) + + desired := []*endpoint.Endpoint{} + for _, c := range current { + // Copy all except ProviderSpecific fields + desired = append(desired, &endpoint.Endpoint{ + DNSName: c.DNSName, + Targets: c.Targets, + RecordType: c.RecordType, + SetIdentifier: c.SetIdentifier, + RecordTTL: c.RecordTTL, + Labels: c.Labels, + }) + } + + plan := plan.Plan{ + Current: current, + Desired: desired, + PropertyComparator: provider.PropertyValuesEqual, + } + + plan = *plan.Calculate() + assert.NotNil(t, plan.Changes, "should have plan") + if plan.Changes == nil { + return + } + assert.Equal(t, 0, len(plan.Changes.Create), "should not have creates") + assert.Equal(t, 0, len(plan.Changes.Delete), "should not have deletes") + + if test.ShouldBeUpdated { + assert.Equal(t, 1, len(plan.Changes.UpdateNew), "should not have new updates") + assert.Equal(t, 1, len(plan.Changes.UpdateOld), "should not have old updates") + } else { + assert.Equal(t, 0, len(plan.Changes.UpdateNew), "should not have new updates") + assert.Equal(t, 0, len(plan.Changes.UpdateOld), "should not have old updates") + } + } +} + func TestCloudflareComplexUpdate(t *testing.T) { client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{ "001": ExampleDomain, diff --git a/provider/coredns/coredns.go b/provider/coredns/coredns.go index b101369a42..a54b4831bd 100644 --- a/provider/coredns/coredns.go +++ b/provider/coredns/coredns.go @@ -57,6 +57,7 @@ type coreDNSClient interface { } type coreDNSProvider struct { + provider.BaseProvider dryRun bool coreDNSPrefix string domainFilter endpoint.DomainFilter diff --git a/provider/designate/designate.go b/provider/designate/designate.go index f6b4e51ad7..68160f0b8a 100644 --- a/provider/designate/designate.go +++ b/provider/designate/designate.go @@ -227,6 +227,7 @@ func (c designateClient) DeleteRecordSet(zoneID, recordSetID string) error { // designate provider type type designateProvider struct { + provider.BaseProvider client designateClientInterface // only consider hosted zones managing domains ending in this suffix diff --git a/provider/digitalocean/digital_ocean.go b/provider/digitalocean/digital_ocean.go index 9cad43f0a1..f9b5bbaecc 100644 --- a/provider/digitalocean/digital_ocean.go +++ b/provider/digitalocean/digital_ocean.go @@ -45,6 +45,7 @@ const ( // DigitalOceanProvider is an implementation of Provider for Digital Ocean's DNS. type DigitalOceanProvider struct { + provider.BaseProvider Client godo.DomainsService // only consider hosted zones managing domains ending in this suffix domainFilter endpoint.DomainFilter diff --git a/provider/dnsimple/dnsimple.go b/provider/dnsimple/dnsimple.go index a89e24faec..da9ae44be7 100644 --- a/provider/dnsimple/dnsimple.go +++ b/provider/dnsimple/dnsimple.go @@ -77,6 +77,7 @@ func (z dnsimpleZoneService) UpdateRecord(ctx context.Context, accountID string, } type dnsimpleProvider struct { + provider.BaseProvider client dnsimpleZoneServiceInterface identity dnsimpleIdentityService accountID string diff --git a/provider/dyn/dyn.go b/provider/dyn/dyn.go index 0c3522a578..4ad78962f6 100644 --- a/provider/dyn/dyn.go +++ b/provider/dyn/dyn.go @@ -104,6 +104,7 @@ func (snap *ZoneSnapshot) StoreRecordsForSerial(zone string, serial int, records // DynProvider is the actual interface impl. type dynProviderState struct { + provider.BaseProvider DynConfig LastLoginErrorTime int64 diff --git a/provider/exoscale/exoscale.go b/provider/exoscale/exoscale.go index 9f05cf90ff..1c3c89f5a7 100644 --- a/provider/exoscale/exoscale.go +++ b/provider/exoscale/exoscale.go @@ -25,6 +25,7 @@ import ( "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" + "sigs.k8s.io/external-dns/provider" ) // EgoscaleClientI for replaceable implementation @@ -38,6 +39,7 @@ type EgoscaleClientI interface { // ExoscaleProvider initialized as dns provider with no records type ExoscaleProvider struct { + provider.BaseProvider domain endpoint.DomainFilter client EgoscaleClientI filter *zoneFilter diff --git a/provider/google/google.go b/provider/google/google.go index 9a023c1697..ef744dbe58 100644 --- a/provider/google/google.go +++ b/provider/google/google.go @@ -99,6 +99,7 @@ func (c changesService) Create(project string, managedZone string, change *dns.C // GoogleProvider is an implementation of Provider for Google CloudDNS. type GoogleProvider struct { + provider.BaseProvider // The Google project to work in project string // Enabled dry-run will print any modifying actions rather than execute them. diff --git a/provider/infoblox/infoblox.go b/provider/infoblox/infoblox.go index 9ece8d6a68..49c95a210d 100644 --- a/provider/infoblox/infoblox.go +++ b/provider/infoblox/infoblox.go @@ -49,6 +49,7 @@ type InfobloxConfig struct { // InfobloxProvider implements the DNS provider for Infoblox. type InfobloxProvider struct { + provider.BaseProvider client ibclient.IBConnector domainFilter endpoint.DomainFilter zoneIDFilter provider.ZoneIDFilter diff --git a/provider/inmemory/inmemory.go b/provider/inmemory/inmemory.go index 7402528823..0abb1c9558 100644 --- a/provider/inmemory/inmemory.go +++ b/provider/inmemory/inmemory.go @@ -25,6 +25,7 @@ import ( "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" + "sigs.k8s.io/external-dns/provider" ) var ( @@ -43,6 +44,7 @@ var ( // InMemoryProvider - dns provider only used for testing purposes // initialized as dns provider with no records type InMemoryProvider struct { + provider.BaseProvider domain endpoint.DomainFilter client *inMemoryClient filter *filter diff --git a/provider/linode/linode.go b/provider/linode/linode.go index e8096afbdf..6d9586d67b 100644 --- a/provider/linode/linode.go +++ b/provider/linode/linode.go @@ -44,6 +44,7 @@ type LinodeDomainClient interface { // LinodeProvider is an implementation of Provider for Digital Ocean's DNS. type LinodeProvider struct { + provider.BaseProvider Client LinodeDomainClient domainFilter endpoint.DomainFilter DryRun bool diff --git a/provider/ns1/ns1.go b/provider/ns1/ns1.go index a30254cd69..0af8fa4832 100644 --- a/provider/ns1/ns1.go +++ b/provider/ns1/ns1.go @@ -94,6 +94,7 @@ type NS1Config struct { // NS1Provider is the NS1 provider type NS1Provider struct { + provider.BaseProvider client NS1DomainClient domainFilter endpoint.DomainFilter zoneIDFilter provider.ZoneIDFilter diff --git a/provider/oci/oci.go b/provider/oci/oci.go index 34a6b72c82..02f11fce9d 100644 --- a/provider/oci/oci.go +++ b/provider/oci/oci.go @@ -53,6 +53,7 @@ type OCIConfig struct { // OCIProvider is an implementation of Provider for Oracle Cloud Infrastructure // (OCI) DNS. type OCIProvider struct { + provider.BaseProvider client ociDNSClient cfg OCIConfig diff --git a/provider/ovh/ovh.go b/provider/ovh/ovh.go index fb0a0d4cc8..f115b9cad1 100644 --- a/provider/ovh/ovh.go +++ b/provider/ovh/ovh.go @@ -46,6 +46,8 @@ var ( // OVHProvider is an implementation of Provider for OVH DNS. type OVHProvider struct { + provider.BaseProvider + client ovhClient domainFilter endpoint.DomainFilter diff --git a/provider/pdns/pdns.go b/provider/pdns/pdns.go index fa9d4e3b77..03bbfe4e9d 100644 --- a/provider/pdns/pdns.go +++ b/provider/pdns/pdns.go @@ -222,6 +222,7 @@ func (c *PDNSAPIClient) PatchZone(zoneID string, zoneStruct pgo.Zone) (resp *htt // PDNSProvider is an implementation of the Provider interface for PowerDNS type PDNSProvider struct { + provider.BaseProvider client PDNSAPIProvider } diff --git a/provider/provider.go b/provider/provider.go index c16bab748b..4490583869 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -29,6 +29,14 @@ import ( type Provider interface { Records(ctx context.Context) ([]*endpoint.Endpoint, error) ApplyChanges(ctx context.Context, changes *plan.Changes) error + PropertyValuesEqual(name string, previous string, current string) bool +} + +type BaseProvider struct { +} + +func (b BaseProvider) PropertyValuesEqual(name, previous, current string) bool { + return previous == current } type contextKey struct { diff --git a/provider/provider_test.go b/provider/provider_test.go index 2b10bc79c4..9140a6d0df 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -22,6 +22,7 @@ import ( "testing" log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { @@ -44,3 +45,12 @@ func TestEnsureTrailingDot(t *testing.T) { } } } + +func TestBaseProviderPropertyEquality(t *testing.T) { + p := BaseProvider{} + assert.True(t, p.PropertyValuesEqual("some.property", "", ""), "Both properties not present") + assert.False(t, p.PropertyValuesEqual("some.property", "", "Foo"), "First property missing") + assert.False(t, p.PropertyValuesEqual("some.property", "Foo", ""), "Second property missing") + assert.True(t, p.PropertyValuesEqual("some.property", "Foo", "Foo"), "Properties the same") + assert.False(t, p.PropertyValuesEqual("some.property", "Foo", "Bar"), "Attributes differ") +} diff --git a/provider/rcode0/rcode0.go b/provider/rcode0/rcode0.go index 11bac1bda2..e7822deb00 100644 --- a/provider/rcode0/rcode0.go +++ b/provider/rcode0/rcode0.go @@ -33,6 +33,7 @@ import ( // RcodeZeroProvider implements the DNS provider for RcodeZero Anycast DNS. type RcodeZeroProvider struct { + provider.BaseProvider Client *rc0.Client DomainFilter endpoint.DomainFilter diff --git a/provider/rdns/rdns.go b/provider/rdns/rdns.go index 197430b24d..b64325bb7d 100644 --- a/provider/rdns/rdns.go +++ b/provider/rdns/rdns.go @@ -35,6 +35,7 @@ import ( "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" + "sigs.k8s.io/external-dns/provider" ) const ( @@ -66,6 +67,7 @@ type RDNSConfig struct { // RDNSProvider is an implementation of Provider for Rancher DNS(RDNS). type RDNSProvider struct { + provider.BaseProvider client RDNSClient dryRun bool domainFilter endpoint.DomainFilter diff --git a/provider/rfc2136/rfc2136.go b/provider/rfc2136/rfc2136.go index 77c92464c7..41d98d6da4 100644 --- a/provider/rfc2136/rfc2136.go +++ b/provider/rfc2136/rfc2136.go @@ -35,6 +35,7 @@ import ( // rfc2136 provider type type rfc2136Provider struct { + provider.BaseProvider nameserver string zoneName string tsigKeyName string diff --git a/provider/transip/transip.go b/provider/transip/transip.go index 5f4941a956..47d2e14412 100644 --- a/provider/transip/transip.go +++ b/provider/transip/transip.go @@ -39,6 +39,7 @@ const ( // TransIPProvider is an implementation of Provider for TransIP. type TransIPProvider struct { + provider.BaseProvider client gotransip.SOAPClient domainFilter endpoint.DomainFilter dryRun bool diff --git a/provider/vinyldns/vinyldns.go b/provider/vinyldns/vinyldns.go index 1f293ec62b..ac6a2212f0 100644 --- a/provider/vinyldns/vinyldns.go +++ b/provider/vinyldns/vinyldns.go @@ -48,6 +48,7 @@ type vinyldnsZoneInterface interface { } type vinyldnsProvider struct { + provider.BaseProvider client vinyldnsZoneInterface zoneFilter provider.ZoneIDFilter domainFilter endpoint.DomainFilter diff --git a/provider/vultr/vultr.go b/provider/vultr/vultr.go index f445f5806f..5bfcc28da6 100644 --- a/provider/vultr/vultr.go +++ b/provider/vultr/vultr.go @@ -39,6 +39,7 @@ const ( // VultrProvider is an implementation of Provider for Vultr DNS. type VultrProvider struct { + provider.BaseProvider client govultr.Client domainFilter endpoint.DomainFilter diff --git a/registry/aws_sd_registry.go b/registry/aws_sd_registry.go index f9a0f0d65f..2e4823f71d 100644 --- a/registry/aws_sd_registry.go +++ b/registry/aws_sd_registry.go @@ -87,3 +87,7 @@ func (sdr *AWSSDRegistry) updateLabels(endpoints []*endpoint.Endpoint) { ep.Labels[endpoint.AWSSDDescriptionLabel] = ep.Labels.Serialize(false) } } + +func (sdr *AWSSDRegistry) PropertyValuesEqual(name string, previous string, current string) bool { + return sdr.provider.PropertyValuesEqual(name, previous, current) +} diff --git a/registry/aws_sd_registry_test.go b/registry/aws_sd_registry_test.go index 7aca319d70..94054ce513 100644 --- a/registry/aws_sd_registry_test.go +++ b/registry/aws_sd_registry_test.go @@ -26,9 +26,11 @@ import ( "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/internal/testutils" "sigs.k8s.io/external-dns/plan" + "sigs.k8s.io/external-dns/provider" ) type inMemoryProvider struct { + provider.BaseProvider endpoints []*endpoint.Endpoint onApplyChanges func(changes *plan.Changes) } diff --git a/registry/noop.go b/registry/noop.go index 4b91fbaf56..10e54adb22 100644 --- a/registry/noop.go +++ b/registry/noop.go @@ -45,3 +45,8 @@ func (im *NoopRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, erro func (im *NoopRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes) error { return im.provider.ApplyChanges(ctx, changes) } + +// PropertyValuesEqual compares two property values for equality +func (im *NoopRegistry) PropertyValuesEqual(attribute string, previous string, current string) bool { + return im.provider.PropertyValuesEqual(attribute, previous, current) +} diff --git a/registry/registry.go b/registry/registry.go index 746e7fdd39..1155411d63 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -32,6 +32,7 @@ import ( type Registry interface { Records(ctx context.Context) ([]*endpoint.Endpoint, error) ApplyChanges(ctx context.Context, changes *plan.Changes) error + PropertyValuesEqual(attribute string, previous string, current string) bool } //TODO(ideahitme): consider moving this to Plan diff --git a/registry/txt.go b/registry/txt.go index 2d99b0660a..d4344322b4 100644 --- a/registry/txt.go +++ b/registry/txt.go @@ -187,6 +187,11 @@ func (im *TXTRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes) return im.provider.ApplyChanges(ctx, filteredChanges) } +// PropertyValuesEqual compares two attribute values for equality +func (im *TXTRegistry) PropertyValuesEqual(name string, previous string, current string) bool { + return im.provider.PropertyValuesEqual(name, previous, current) +} + /** TXT registry specific private methods */