diff --git a/controller/execute.go b/controller/execute.go index 5782db8daa..934eadc751 100644 --- a/controller/execute.go +++ b/controller/execute.go @@ -241,7 +241,7 @@ func buildProvider( case "dnsimple": p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun) case "coredns", "skydns": - p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun) + p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.TXTOwnerID, cfg.DryRun) case "exoscale": p, err = exoscale.NewExoscaleProvider( cfg.ExoscaleAPIEnvironment, diff --git a/provider/coredns/coredns.go b/provider/coredns/coredns.go index 55e0cd1764..16e9ec03dd 100644 --- a/provider/coredns/coredns.go +++ b/provider/coredns/coredns.go @@ -58,6 +58,7 @@ type coreDNSProvider struct { coreDNSPrefix string domainFilter *endpoint.DomainFilter client coreDNSClient + setIdentifier string } // Service represents CoreDNS etcd record @@ -84,6 +85,9 @@ type Service struct { // Etcd key where we found this service and ignored from json un-/marshaling Key string `json:"-"` + + // SetIdentifier allows to distinguish between multiple clusters of external-dns (only external-dns) + SetIdentifier string `json:"setIdentifier,omitempty"` } type etcdClient struct { @@ -196,7 +200,7 @@ func newETCDClient() (coreDNSClient, error) { } // NewCoreDNSProvider is a CoreDNS provider constructor -func NewCoreDNSProvider(domainFilter *endpoint.DomainFilter, prefix string, dryRun bool) (provider.Provider, error) { +func NewCoreDNSProvider(domainFilter *endpoint.DomainFilter, prefix, setIdentifier string, dryRun bool) (provider.Provider, error) { client, err := newETCDClient() if err != nil { return nil, err @@ -207,14 +211,15 @@ func NewCoreDNSProvider(domainFilter *endpoint.DomainFilter, prefix string, dryR dryRun: dryRun, coreDNSPrefix: prefix, domainFilter: domainFilter, + setIdentifier: setIdentifier, }, nil } // findEp takes an Endpoint slice and looks for an element in it. If found it will // return Endpoint, otherwise it will return nil and a bool of false. -func findEp(slice []*endpoint.Endpoint, dnsName string) (*endpoint.Endpoint, bool) { +func findEp(slice []*endpoint.Endpoint, dnsName string, setIdentifier string) (*endpoint.Endpoint, bool) { for _, item := range slice { - if item.DNSName == dnsName { + if item.DNSName == dnsName && (item.SetIdentifier == setIdentifier || item.SetIdentifier == "") { // empty string is used to migrate to set identifier return item, true } } @@ -250,7 +255,7 @@ func (p coreDNSProvider) Records(_ context.Context) ([]*endpoint.Endpoint, error log.Debugf("Getting service (%v) with service host (%s)", service, service.Host) prefix := strings.Join(domains[:service.TargetStrip], ".") if service.Host != "" { - ep, found := findEp(result, dnsName) + ep, found := findEp(result, dnsName, service.SetIdentifier) if found { ep.Targets = append(ep.Targets, service.Host) log.Debugf("Extending ep (%s) with new service host (%s)", ep, service.Host) @@ -269,6 +274,7 @@ func (p coreDNSProvider) Records(_ context.Context) ([]*endpoint.Endpoint, error ep.Labels["originalText"] = service.Text ep.Labels[randomPrefixLabel] = prefix ep.Labels[service.Host] = prefix + ep.SetIdentifier = service.SetIdentifier result = append(result, ep) } if service.Text != "" { @@ -278,6 +284,7 @@ func (p coreDNSProvider) Records(_ context.Context) ([]*endpoint.Endpoint, error service.Text, ) ep.Labels[randomPrefixLabel] = prefix + ep.SetIdentifier = service.SetIdentifier result = append(result, ep) } } @@ -355,15 +362,19 @@ func (p coreDNSProvider) createServicesForEndpoint(dnsName string, ep *endpoint. group = prop } service := Service{ - Host: target, - Text: ep.Labels["originalText"], - Key: p.etcdKeyFor(prefix + "." + dnsName), - TargetStrip: strings.Count(prefix, ".") + 1, - TTL: uint32(ep.RecordTTL), - Group: group, + Host: target, + Text: ep.Labels["originalText"], + Key: p.etcdKeyFor(prefix + "." + dnsName), + TargetStrip: strings.Count(prefix, ".") + 1, + TTL: uint32(ep.RecordTTL), + Group: group, + SetIdentifier: ep.SetIdentifier, } services = append(services, &service) ep.Labels[target] = prefix + if service.SetIdentifier == "" { + service.SetIdentifier = p.setIdentifier + } } // Clean outdated labels diff --git a/provider/coredns/coredns_test.go b/provider/coredns/coredns_test.go index b7d530aa81..87e7ae5bfb 100644 --- a/provider/coredns/coredns_test.go +++ b/provider/coredns/coredns_test.go @@ -23,12 +23,14 @@ import ( "reflect" "strings" "testing" + "time" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.etcd.io/etcd/api/v3/mvccpb" etcdcv3 "go.etcd.io/etcd/client/v3" + "sigs.k8s.io/external-dns/registry" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/internal/testutils" @@ -460,7 +462,7 @@ func validateServices(services map[string]Service, expectedServices map[string][ } found := false for i, expectedServiceEntry := range expectedServiceEntries { - if value.Host == expectedServiceEntry.Host && value.Text == expectedServiceEntry.Text && value.Group == expectedServiceEntry.Group { + if value.Host == expectedServiceEntry.Host && value.Text == expectedServiceEntry.Text && value.Group == expectedServiceEntry.Group && value.SetIdentifier == expectedServiceEntry.SetIdentifier { expectedServiceEntries = append(expectedServiceEntries[:i], expectedServiceEntries[i+1:]...) found = true break @@ -753,7 +755,7 @@ func TestNewCoreDNSProvider(t *testing.T) { t.Run(tt.name, func(t *testing.T) { testutils.TestHelperEnvSetter(t, tt.envs) - provider, err := NewCoreDNSProvider(&endpoint.DomainFilter{}, "/prefix/", false) + provider, err := NewCoreDNSProvider(&endpoint.DomainFilter{}, "/prefix/", "", false) if tt.wantErr { require.Error(t, err) assert.EqualError(t, err, tt.errMsg) @@ -803,7 +805,7 @@ func TestFindEp(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, ok := findEp(tt.slice, tt.dnsName) + got, ok := findEp(tt.slice, tt.dnsName, "") assert.Equal(t, tt.wantBool, ok) if ok { assert.Equal(t, tt.dnsName, got.DNSName) @@ -908,3 +910,72 @@ func TestRecordsAWithGroupServiceTranslation(t *testing.T) { t.Errorf("got unexpected Group name: %s != %s", prop, "test1") } } + +func TestApplyChangesWithSetIdentifier(t *testing.T) { + client := fakeETCDClient{ + map[string]Service{}, + } + coredns := coreDNSProvider{ + client: client, + coreDNSPrefix: defaultCoreDNSPrefix, + setIdentifier: "bla", + } + + changes1 := &plan.Changes{ + Create: []*endpoint.Endpoint{ + endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeA, "5.5.5.5").WithSetIdentifier("asd"), + endpoint.NewEndpoint("domain2.local", endpoint.RecordTypeA, "5.5.5.6"), + endpoint.NewEndpoint("domain3.local", endpoint.RecordTypeA, "5.5.5.7"), + }, + } + coredns.ApplyChanges(context.Background(), changes1) + + expectedServices1 := map[string][]*Service{ + "/skydns/local/domain1": {{Host: "5.5.5.5", SetIdentifier: "asd"}}, + "/skydns/local/domain2": {{Host: "5.5.5.6", SetIdentifier: "bla"}}, + "/skydns/local/domain3": {{Host: "5.5.5.7", SetIdentifier: "bla"}}, + } + validateServices(client.services, expectedServices1, t, 1) +} + +func TestRecordsWithSetIdentifier(t *testing.T) { + client := fakeETCDClient{ + map[string]Service{ + "/skydns/local/a-domain1/45bd7d0d": { + Text: "\"heritage=external-dns,external-dns/owner=cluster-a,external-dns/resource=resource1\"", + TargetStrip: 1, + SetIdentifier: "cluster-a", + }, + "/skydns/local/a-domain1/56615f35": { + Text: "\"heritage=external-dns,external-dns/owner=cluster-b,external-dns/resource=resource1\"", + TargetStrip: 1, + SetIdentifier: "cluster-b", + }, + "/skydns/local/domain1/29afc1cc": { + Host: "10.15.1.1", + Text: "\"heritage=external-dns,external-dns/owner=cluster-b,external-dns/resource=resource1\"", + TargetStrip: 1, + SetIdentifier: "cluster-b", + }, + "/skydns/local/domain1/738fb9f0": { + Host: "10.25.1.1", + Text: "\"heritage=external-dns,external-dns/owner=cluster-a,external-dns/resource=resource1\"", + TargetStrip: 1, + SetIdentifier: "cluster-a", + }, + }, + } + p := coreDNSProvider{ + client: client, + coreDNSPrefix: defaultCoreDNSPrefix, + } + r, _ := registry.NewTXTRegistry(p, "", "", "cluster-b", time.Hour, "", []string{}, []string{}, false, nil) + endpoints, err := r.Records(context.Background()) + require.NoError(t, err) + assert.Len(t, endpoints, 2) + identifierMap := map[string]bool{} + for _, endpoint := range endpoints { + identifierMap[endpoint.SetIdentifier] = true + } + assert.Len(t, identifierMap, 2) +}