mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
Allow for custom property comparators
Fixes issue #1463 Co-authored-by: Alastair Houghton <alastair@alastairs-place.net>
This commit is contained in:
parent
2ef1503572
commit
f008e894df
@ -135,10 +135,11 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
|||||||
sourceEndpointsTotal.Set(float64(len(endpoints)))
|
sourceEndpointsTotal.Set(float64(len(endpoints)))
|
||||||
|
|
||||||
plan := &plan.Plan{
|
plan := &plan.Plan{
|
||||||
Policies: []plan.Policy{c.Policy},
|
Policies: []plan.Policy{c.Policy},
|
||||||
Current: records,
|
Current: records,
|
||||||
Desired: endpoints,
|
Desired: endpoints,
|
||||||
DomainFilter: c.DomainFilter,
|
DomainFilter: c.DomainFilter,
|
||||||
|
PropertyComparator: c.Registry.PropertyValuesEqual,
|
||||||
}
|
}
|
||||||
|
|
||||||
plan = plan.Calculate()
|
plan = plan.Calculate()
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
|
|
||||||
// mockProvider returns mock endpoints and validates changes.
|
// mockProvider returns mock endpoints and validates changes.
|
||||||
type mockProvider struct {
|
type mockProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
RecordsStore []*endpoint.Endpoint
|
RecordsStore []*endpoint.Endpoint
|
||||||
ExpectChanges *plan.Changes
|
ExpectChanges *plan.Changes
|
||||||
}
|
}
|
||||||
|
93
plan/plan.go
93
plan/plan.go
@ -18,11 +18,15 @@ package plan
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"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,
|
// Plan can convert a list of desired and current records to a series of create,
|
||||||
// update and delete actions.
|
// update and delete actions.
|
||||||
type Plan struct {
|
type Plan struct {
|
||||||
@ -37,6 +41,8 @@ type Plan struct {
|
|||||||
Changes *Changes
|
Changes *Changes
|
||||||
// DomainFilter matches DNS names
|
// DomainFilter matches DNS names
|
||||||
DomainFilter endpoint.DomainFilter
|
DomainFilter endpoint.DomainFilter
|
||||||
|
// Property comparator compares custom properties of providers
|
||||||
|
PropertyComparator PropertyComparator
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changes holds lists of actions to be executed by dns providers
|
// 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
|
if row.current != nil && len(row.candidates) > 0 { //dns name is taken
|
||||||
update := t.resolver.ResolveUpdate(row.current, row.candidates)
|
update := t.resolver.ResolveUpdate(row.current, row.candidates)
|
||||||
// compare "update" to "current" to figure out if actual update is required
|
// 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)
|
inheritOwner(row.current, update)
|
||||||
changes.UpdateNew = append(changes.UpdateNew, update)
|
changes.UpdateNew = append(changes.UpdateNew, update)
|
||||||
changes.UpdateOld = append(changes.UpdateOld, row.current)
|
changes.UpdateOld = append(changes.UpdateOld, row.current)
|
||||||
@ -178,45 +184,40 @@ func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool {
|
|||||||
return desired.RecordTTL != current.RecordTTL
|
return desired.RecordTTL != current.RecordTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool {
|
func (p *Plan) shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool {
|
||||||
if current.ProviderSpecific == nil && len(desired.ProviderSpecific) == 0 {
|
desiredProperties := map[string]endpoint.ProviderSpecificProperty{}
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
if desired.ProviderSpecific != nil {
|
||||||
for _, d := range desired.ProviderSpecific {
|
for _, d := range desired.ProviderSpecific {
|
||||||
if d.Name == c.Name {
|
desiredProperties[d.Name] = d
|
||||||
if d.Value != c.Value {
|
}
|
||||||
// provider-specific attribute updated
|
}
|
||||||
|
if current.ProviderSpecific != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
return true
|
||||||
}
|
}
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
|
||||||
// provider-specific attribute deleted
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, d := range desired.ProviderSpecific {
|
|
||||||
found := false
|
|
||||||
for _, c := range current.ProviderSpecific {
|
|
||||||
if d.Name == c.Name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
// provider-specific attribute added
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@ -260,3 +261,27 @@ func normalizeDNSName(dnsName string) string {
|
|||||||
}
|
}
|
||||||
return s
|
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
|
||||||
|
}
|
||||||
|
@ -38,6 +38,7 @@ type PlanTestSuite struct {
|
|||||||
bar127AWithTTL *endpoint.Endpoint
|
bar127AWithTTL *endpoint.Endpoint
|
||||||
bar127AWithProviderSpecificTrue *endpoint.Endpoint
|
bar127AWithProviderSpecificTrue *endpoint.Endpoint
|
||||||
bar127AWithProviderSpecificFalse *endpoint.Endpoint
|
bar127AWithProviderSpecificFalse *endpoint.Endpoint
|
||||||
|
bar127AWithProviderSpecificUnset *endpoint.Endpoint
|
||||||
bar192A *endpoint.Endpoint
|
bar192A *endpoint.Endpoint
|
||||||
multiple1 *endpoint.Endpoint
|
multiple1 *endpoint.Endpoint
|
||||||
multiple2 *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{
|
suite.bar192A = &endpoint.Endpoint{
|
||||||
DNSName: "bar",
|
DNSName: "bar",
|
||||||
Targets: endpoint.Targets{"192.168.0.1"},
|
Targets: endpoint.Targets{"192.168.0.1"},
|
||||||
@ -291,6 +301,54 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificChange() {
|
|||||||
validateEntries(suite.T(), changes.Delete, expectedDelete)
|
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() {
|
func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() {
|
||||||
current := []*endpoint.Endpoint{suite.fooV1Cname}
|
current := []*endpoint.Endpoint{suite.fooV1Cname}
|
||||||
desired := []*endpoint.Endpoint{suite.fooV2Cname}
|
desired := []*endpoint.Endpoint{suite.fooV2Cname}
|
||||||
|
@ -61,6 +61,7 @@ type AkamaiConfig struct {
|
|||||||
|
|
||||||
// AkamaiProvider implements the DNS provider for Akamai.
|
// AkamaiProvider implements the DNS provider for Akamai.
|
||||||
type AkamaiProvider struct {
|
type AkamaiProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter provider.ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
config edgegrid.Config
|
config edgegrid.Config
|
||||||
|
@ -67,6 +67,7 @@ type AlibabaCloudPrivateZoneAPI interface {
|
|||||||
|
|
||||||
// AlibabaCloudProvider implements the DNS provider for Alibaba Cloud.
|
// AlibabaCloudProvider implements the DNS provider for Alibaba Cloud.
|
||||||
type AlibabaCloudProvider struct {
|
type AlibabaCloudProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter provider.ZoneIDFilter // Private Zone only
|
zoneIDFilter provider.ZoneIDFilter // Private Zone only
|
||||||
MaxChangeCount int
|
MaxChangeCount int
|
||||||
|
@ -114,6 +114,7 @@ type Route53API interface {
|
|||||||
|
|
||||||
// AWSProvider is an implementation of Provider for AWS Route53.
|
// AWSProvider is an implementation of Provider for AWS Route53.
|
||||||
type AWSProvider struct {
|
type AWSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client Route53API
|
client Route53API
|
||||||
dryRun bool
|
dryRun bool
|
||||||
batchChangeSize int
|
batchChangeSize int
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -73,6 +74,7 @@ type AWSSDClient interface {
|
|||||||
|
|
||||||
// AWSSDProvider is an implementation of Provider for AWS Cloud Map.
|
// AWSSDProvider is an implementation of Provider for AWS Cloud Map.
|
||||||
type AWSSDProvider struct {
|
type AWSSDProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client AWSSDClient
|
client AWSSDClient
|
||||||
dryRun bool
|
dryRun bool
|
||||||
// only consider namespaces ending in this suffix
|
// only consider namespaces ending in this suffix
|
||||||
|
@ -67,6 +67,7 @@ type RecordSetsClient interface {
|
|||||||
|
|
||||||
// AzureProvider implements the DNS provider for Microsoft's Azure cloud platform.
|
// AzureProvider implements the DNS provider for Microsoft's Azure cloud platform.
|
||||||
type AzureProvider struct {
|
type AzureProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter provider.ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
|
@ -46,6 +46,7 @@ type PrivateRecordSetsClient interface {
|
|||||||
|
|
||||||
// AzurePrivateDNSProvider implements the DNS provider for Microsoft's Azure Private DNS service
|
// AzurePrivateDNSProvider implements the DNS provider for Microsoft's Azure Private DNS service
|
||||||
type AzurePrivateDNSProvider struct {
|
type AzurePrivateDNSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter provider.ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
|
@ -101,6 +101,7 @@ func (z zoneService) ListZonesContext(ctx context.Context, opts ...cloudflare.Re
|
|||||||
|
|
||||||
// CloudFlareProvider is an implementation of Provider for CloudFlare DNS.
|
// CloudFlareProvider is an implementation of Provider for CloudFlare DNS.
|
||||||
type CloudFlareProvider struct {
|
type CloudFlareProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
Client cloudFlareDNS
|
Client cloudFlareDNS
|
||||||
// only consider hosted zones managing domains ending in this suffix
|
// only consider hosted zones managing domains ending in this suffix
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
@ -211,6 +212,14 @@ func (p *CloudFlareProvider) ApplyChanges(ctx context.Context, changes *plan.Cha
|
|||||||
return p.submitChanges(ctx, combinedChanges)
|
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.
|
// 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 {
|
func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloudFlareChange) error {
|
||||||
// return early if there is nothing to change
|
// return early if there is nothing to change
|
||||||
|
@ -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) {
|
func TestCloudflareComplexUpdate(t *testing.T) {
|
||||||
client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{
|
client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{
|
||||||
"001": ExampleDomain,
|
"001": ExampleDomain,
|
||||||
|
@ -57,6 +57,7 @@ type coreDNSClient interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type coreDNSProvider struct {
|
type coreDNSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
dryRun bool
|
dryRun bool
|
||||||
coreDNSPrefix string
|
coreDNSPrefix string
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
|
@ -227,6 +227,7 @@ func (c designateClient) DeleteRecordSet(zoneID, recordSetID string) error {
|
|||||||
|
|
||||||
// designate provider type
|
// designate provider type
|
||||||
type designateProvider struct {
|
type designateProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client designateClientInterface
|
client designateClientInterface
|
||||||
|
|
||||||
// only consider hosted zones managing domains ending in this suffix
|
// only consider hosted zones managing domains ending in this suffix
|
||||||
|
@ -45,6 +45,7 @@ const (
|
|||||||
|
|
||||||
// DigitalOceanProvider is an implementation of Provider for Digital Ocean's DNS.
|
// DigitalOceanProvider is an implementation of Provider for Digital Ocean's DNS.
|
||||||
type DigitalOceanProvider struct {
|
type DigitalOceanProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
Client godo.DomainsService
|
Client godo.DomainsService
|
||||||
// only consider hosted zones managing domains ending in this suffix
|
// only consider hosted zones managing domains ending in this suffix
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
|
@ -77,6 +77,7 @@ func (z dnsimpleZoneService) UpdateRecord(ctx context.Context, accountID string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
type dnsimpleProvider struct {
|
type dnsimpleProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client dnsimpleZoneServiceInterface
|
client dnsimpleZoneServiceInterface
|
||||||
identity dnsimpleIdentityService
|
identity dnsimpleIdentityService
|
||||||
accountID string
|
accountID string
|
||||||
|
@ -104,6 +104,7 @@ func (snap *ZoneSnapshot) StoreRecordsForSerial(zone string, serial int, records
|
|||||||
|
|
||||||
// DynProvider is the actual interface impl.
|
// DynProvider is the actual interface impl.
|
||||||
type dynProviderState struct {
|
type dynProviderState struct {
|
||||||
|
provider.BaseProvider
|
||||||
DynConfig
|
DynConfig
|
||||||
LastLoginErrorTime int64
|
LastLoginErrorTime int64
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EgoscaleClientI for replaceable implementation
|
// EgoscaleClientI for replaceable implementation
|
||||||
@ -38,6 +39,7 @@ type EgoscaleClientI interface {
|
|||||||
|
|
||||||
// ExoscaleProvider initialized as dns provider with no records
|
// ExoscaleProvider initialized as dns provider with no records
|
||||||
type ExoscaleProvider struct {
|
type ExoscaleProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domain endpoint.DomainFilter
|
domain endpoint.DomainFilter
|
||||||
client EgoscaleClientI
|
client EgoscaleClientI
|
||||||
filter *zoneFilter
|
filter *zoneFilter
|
||||||
|
@ -99,6 +99,7 @@ func (c changesService) Create(project string, managedZone string, change *dns.C
|
|||||||
|
|
||||||
// GoogleProvider is an implementation of Provider for Google CloudDNS.
|
// GoogleProvider is an implementation of Provider for Google CloudDNS.
|
||||||
type GoogleProvider struct {
|
type GoogleProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
// The Google project to work in
|
// The Google project to work in
|
||||||
project string
|
project string
|
||||||
// Enabled dry-run will print any modifying actions rather than execute them.
|
// Enabled dry-run will print any modifying actions rather than execute them.
|
||||||
|
@ -49,6 +49,7 @@ type InfobloxConfig struct {
|
|||||||
|
|
||||||
// InfobloxProvider implements the DNS provider for Infoblox.
|
// InfobloxProvider implements the DNS provider for Infoblox.
|
||||||
type InfobloxProvider struct {
|
type InfobloxProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client ibclient.IBConnector
|
client ibclient.IBConnector
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter provider.ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -43,6 +44,7 @@ var (
|
|||||||
// InMemoryProvider - dns provider only used for testing purposes
|
// InMemoryProvider - dns provider only used for testing purposes
|
||||||
// initialized as dns provider with no records
|
// initialized as dns provider with no records
|
||||||
type InMemoryProvider struct {
|
type InMemoryProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domain endpoint.DomainFilter
|
domain endpoint.DomainFilter
|
||||||
client *inMemoryClient
|
client *inMemoryClient
|
||||||
filter *filter
|
filter *filter
|
||||||
|
@ -44,6 +44,7 @@ type LinodeDomainClient interface {
|
|||||||
|
|
||||||
// LinodeProvider is an implementation of Provider for Digital Ocean's DNS.
|
// LinodeProvider is an implementation of Provider for Digital Ocean's DNS.
|
||||||
type LinodeProvider struct {
|
type LinodeProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
Client LinodeDomainClient
|
Client LinodeDomainClient
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
DryRun bool
|
DryRun bool
|
||||||
|
@ -94,6 +94,7 @@ type NS1Config struct {
|
|||||||
|
|
||||||
// NS1Provider is the NS1 provider
|
// NS1Provider is the NS1 provider
|
||||||
type NS1Provider struct {
|
type NS1Provider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client NS1DomainClient
|
client NS1DomainClient
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter provider.ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
|
@ -53,6 +53,7 @@ type OCIConfig struct {
|
|||||||
// OCIProvider is an implementation of Provider for Oracle Cloud Infrastructure
|
// OCIProvider is an implementation of Provider for Oracle Cloud Infrastructure
|
||||||
// (OCI) DNS.
|
// (OCI) DNS.
|
||||||
type OCIProvider struct {
|
type OCIProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client ociDNSClient
|
client ociDNSClient
|
||||||
cfg OCIConfig
|
cfg OCIConfig
|
||||||
|
|
||||||
|
@ -46,6 +46,8 @@ var (
|
|||||||
|
|
||||||
// OVHProvider is an implementation of Provider for OVH DNS.
|
// OVHProvider is an implementation of Provider for OVH DNS.
|
||||||
type OVHProvider struct {
|
type OVHProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
|
|
||||||
client ovhClient
|
client ovhClient
|
||||||
|
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
|
@ -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
|
// PDNSProvider is an implementation of the Provider interface for PowerDNS
|
||||||
type PDNSProvider struct {
|
type PDNSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client PDNSAPIProvider
|
client PDNSAPIProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,14 @@ import (
|
|||||||
type Provider interface {
|
type Provider interface {
|
||||||
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||||
ApplyChanges(ctx context.Context, changes *plan.Changes) 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 {
|
type contextKey struct {
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
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")
|
||||||
|
}
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
|
|
||||||
// RcodeZeroProvider implements the DNS provider for RcodeZero Anycast DNS.
|
// RcodeZeroProvider implements the DNS provider for RcodeZero Anycast DNS.
|
||||||
type RcodeZeroProvider struct {
|
type RcodeZeroProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
Client *rc0.Client
|
Client *rc0.Client
|
||||||
|
|
||||||
DomainFilter endpoint.DomainFilter
|
DomainFilter endpoint.DomainFilter
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -66,6 +67,7 @@ type RDNSConfig struct {
|
|||||||
|
|
||||||
// RDNSProvider is an implementation of Provider for Rancher DNS(RDNS).
|
// RDNSProvider is an implementation of Provider for Rancher DNS(RDNS).
|
||||||
type RDNSProvider struct {
|
type RDNSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client RDNSClient
|
client RDNSClient
|
||||||
dryRun bool
|
dryRun bool
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
|
|
||||||
// rfc2136 provider type
|
// rfc2136 provider type
|
||||||
type rfc2136Provider struct {
|
type rfc2136Provider struct {
|
||||||
|
provider.BaseProvider
|
||||||
nameserver string
|
nameserver string
|
||||||
zoneName string
|
zoneName string
|
||||||
tsigKeyName string
|
tsigKeyName string
|
||||||
|
@ -39,6 +39,7 @@ const (
|
|||||||
|
|
||||||
// TransIPProvider is an implementation of Provider for TransIP.
|
// TransIPProvider is an implementation of Provider for TransIP.
|
||||||
type TransIPProvider struct {
|
type TransIPProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client gotransip.SOAPClient
|
client gotransip.SOAPClient
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
|
@ -48,6 +48,7 @@ type vinyldnsZoneInterface interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type vinyldnsProvider struct {
|
type vinyldnsProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client vinyldnsZoneInterface
|
client vinyldnsZoneInterface
|
||||||
zoneFilter provider.ZoneIDFilter
|
zoneFilter provider.ZoneIDFilter
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
|
@ -39,6 +39,7 @@ const (
|
|||||||
|
|
||||||
// VultrProvider is an implementation of Provider for Vultr DNS.
|
// VultrProvider is an implementation of Provider for Vultr DNS.
|
||||||
type VultrProvider struct {
|
type VultrProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client govultr.Client
|
client govultr.Client
|
||||||
|
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
|
@ -87,3 +87,7 @@ func (sdr *AWSSDRegistry) updateLabels(endpoints []*endpoint.Endpoint) {
|
|||||||
ep.Labels[endpoint.AWSSDDescriptionLabel] = ep.Labels.Serialize(false)
|
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)
|
||||||
|
}
|
||||||
|
@ -26,9 +26,11 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/internal/testutils"
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inMemoryProvider struct {
|
type inMemoryProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
endpoints []*endpoint.Endpoint
|
endpoints []*endpoint.Endpoint
|
||||||
onApplyChanges func(changes *plan.Changes)
|
onApplyChanges func(changes *plan.Changes)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
func (im *NoopRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||||
return im.provider.ApplyChanges(ctx, changes)
|
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)
|
||||||
|
}
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
type Registry interface {
|
type Registry interface {
|
||||||
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||||
ApplyChanges(ctx context.Context, changes *plan.Changes) error
|
ApplyChanges(ctx context.Context, changes *plan.Changes) error
|
||||||
|
PropertyValuesEqual(attribute string, previous string, current string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO(ideahitme): consider moving this to Plan
|
//TODO(ideahitme): consider moving this to Plan
|
||||||
|
@ -187,6 +187,11 @@ func (im *TXTRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
|||||||
return im.provider.ApplyChanges(ctx, filteredChanges)
|
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
|
TXT registry specific private methods
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user