Repository: kubernetes-incubator/external-dns (go)

Issue:
[CoreDNS] Change default etcd path
Dear all,

I try to use external-dns to publish ingress IP in our CoreDNS but i'm facing an issue on the default path "/skydns" that we don't use as default in CoreDNS config.

Is it possible to external-dns to publish DNS entry in another ETCD path ? Like "/coredns" for example.

Regards

Pull request description:
add option for coredns to specify prefix name
fixes #1087

Patch:
diff --git a/main.go b/main.go
index 825f6d4f..d9eb4237 100644
--- a/main.go
+++ b/main.go
@@ -176,7 +176,7 @@ func main() {
 			},
 		)
 	case "coredns", "skydns":
-		p, err = provider.NewCoreDNSProvider(domainFilter, cfg.DryRun)
+		p, err = provider.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
 	case "rdns":
 		p, err = provider.NewRDNSProvider(
 			provider.RDNSConfig{
diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go
index 998e60cd..c2b02f79 100644
--- a/pkg/apis/externaldns/types.go
+++ b/pkg/apis/externaldns/types.go
@@ -69,6 +69,7 @@ type Config struct {
 	AzureResourceGroup          string
 	CloudflareProxied           bool
 	CloudflareZonesPerPage      int
+	CoreDNSPrefix               string
 	RcodezeroTXTEncrypt         bool
 	InfobloxGridHost            string
 	InfobloxWapiPort            int
@@ -155,6 +156,7 @@ var defaultConfig = &Config{
 	AzureResourceGroup:          "",
 	CloudflareProxied:           false,
 	CloudflareZonesPerPage:      50,
+	CoreDNSPrefix:               "/skydns/",
 	RcodezeroTXTEncrypt:         false,
 	InfobloxGridHost:            "",
 	InfobloxWapiPort:            443,
@@ -294,6 +296,7 @@ func (cfg *Config) ParseFlags(args []string) error {
 	app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup)
 	app.Flag("cloudflare-proxied", "When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled)").BoolVar(&cfg.CloudflareProxied)
 	app.Flag("cloudflare-zones-per-page", "When using the Cloudflare provider, specify how many zones per page listed, max. possible 50 (default: 50)").Default(strconv.Itoa(defaultConfig.CloudflareZonesPerPage)).IntVar(&cfg.CloudflareZonesPerPage)
+	app.Flag("coredns-prefix", "When using the CoreDNS provider, specify the prefix name").Default(defaultConfig.CoreDNSPrefix).StringVar(&cfg.CoreDNSPrefix)
 	app.Flag("infoblox-grid-host", "When using the Infoblox provider, specify the Grid Manager host (required when --provider=infoblox)").Default(defaultConfig.InfobloxGridHost).StringVar(&cfg.InfobloxGridHost)
 	app.Flag("infoblox-wapi-port", "When using the Infoblox provider, specify the WAPI port (default: 443)").Default(strconv.Itoa(defaultConfig.InfobloxWapiPort)).IntVar(&cfg.InfobloxWapiPort)
 	app.Flag("infoblox-wapi-username", "When using the Infoblox provider, specify the WAPI username (default: admin)").Default(defaultConfig.InfobloxWapiUsername).StringVar(&cfg.InfobloxWapiUsername)
@@ -308,7 +311,6 @@ func (cfg *Config) ParseFlags(args []string) error {
 	app.Flag("dyn-min-ttl", "Minimal TTL (in seconds) for records. This value will be used if the provided TTL for a service/ingress is lower than this.").IntVar(&cfg.DynMinTTLSeconds)
 	app.Flag("oci-config-file", "When using the OCI provider, specify the OCI configuration file (required when --provider=oci").Default(defaultConfig.OCIConfigFile).StringVar(&cfg.OCIConfigFile)
 	app.Flag("rcodezero-txt-encrypt", "When using the Rcodezero provider with txt registry option, set if TXT rrs are encrypted (default: false)").Default(strconv.FormatBool(defaultConfig.RcodezeroTXTEncrypt)).BoolVar(&cfg.RcodezeroTXTEncrypt)
-
 	app.Flag("inmemory-zone", "Provide a list of pre-configured zones for the inmemory provider; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.InMemoryZones)
 	app.Flag("pdns-server", "When using the PowerDNS/PDNS provider, specify the URL to the pdns server (required when --provider=pdns)").Default(defaultConfig.PDNSServer).StringVar(&cfg.PDNSServer)
 	app.Flag("pdns-api-key", "When using the PowerDNS/PDNS provider, specify the API key to use to authorize requests (required when --provider=pdns)").Default(defaultConfig.PDNSAPIKey).StringVar(&cfg.PDNSAPIKey)
diff --git a/provider/coredns.go b/provider/coredns.go
index bd09f40a..019003ce 100644
--- a/provider/coredns.go
+++ b/provider/coredns.go
@@ -45,8 +45,6 @@ const (
 	priority    = 10 // default priority when nothing is set
 	etcdTimeout = 5 * time.Second
 
-	coreDNSPrefix = "/skydns/"
-
 	randomPrefixLabel = "prefix"
 )
 
@@ -58,9 +56,10 @@ type coreDNSClient interface {
 }
 
 type coreDNSProvider struct {
-	dryRun       bool
-	domainFilter DomainFilter
-	client       coreDNSClient
+	dryRun        bool
+	coreDNSPrefix string
+	domainFilter  DomainFilter
+	client        coreDNSClient
 }
 
 // Service represents CoreDNS etcd record
@@ -245,15 +244,17 @@ func newETCDClient() (coreDNSClient, error) {
 }
 
 // NewCoreDNSProvider is a CoreDNS provider constructor
-func NewCoreDNSProvider(domainFilter DomainFilter, dryRun bool) (Provider, error) {
+func NewCoreDNSProvider(domainFilter DomainFilter, prefix string, dryRun bool) (Provider, error) {
 	client, err := newETCDClient()
 	if err != nil {
 		return nil, err
 	}
+
 	return coreDNSProvider{
-		client:       client,
-		dryRun:       dryRun,
-		domainFilter: domainFilter,
+		client:        client,
+		dryRun:        dryRun,
+		coreDNSPrefix: prefix,
+		domainFilter:  domainFilter,
 	}, nil
 }
 
@@ -261,12 +262,12 @@ func NewCoreDNSProvider(domainFilter DomainFilter, dryRun bool) (Provider, error
 // it may be mapped to one or two records of type A, CNAME, TXT, A+TXT, CNAME+TXT
 func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) {
 	var result []*endpoint.Endpoint
-	services, err := p.client.GetServices(coreDNSPrefix)
+	services, err := p.client.GetServices(p.coreDNSPrefix)
 	if err != nil {
 		return nil, err
 	}
 	for _, service := range services {
-		domains := strings.Split(strings.TrimPrefix(service.Key, coreDNSPrefix), "/")
+		domains := strings.Split(strings.TrimPrefix(service.Key, p.coreDNSPrefix), "/")
 		reverse(domains)
 		dnsName := strings.Join(domains[service.TargetStrip:], ".")
 		if !p.domainFilter.Match(dnsName) {
@@ -327,7 +328,7 @@ func (p coreDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
 				service := Service{
 					Host:        target,
 					Text:        ep.Labels["originalText"],
-					Key:         etcdKeyFor(prefix + "." + dnsName),
+					Key:         p.etcdKeyFor(prefix + "." + dnsName),
 					TargetStrip: strings.Count(prefix, ".") + 1,
 					TTL:         uint32(ep.RecordTTL),
 				}
@@ -345,7 +346,7 @@ func (p coreDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
 					prefix = fmt.Sprintf("%08x", rand.Int31())
 				}
 				services = append(services, Service{
-					Key:         etcdKeyFor(prefix + "." + dnsName),
+					Key:         p.etcdKeyFor(prefix + "." + dnsName),
 					TargetStrip: strings.Count(prefix, ".") + 1,
 					TTL:         uint32(ep.RecordTTL),
 				})
@@ -374,7 +375,7 @@ func (p coreDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
 		if ep.Labels[randomPrefixLabel] != "" {
 			dnsName = ep.Labels[randomPrefixLabel] + "." + dnsName
 		}
-		key := etcdKeyFor(dnsName)
+		key := p.etcdKeyFor(dnsName)
 		log.Infof("Delete key %s", key)
 		if !p.dryRun {
 			err := p.client.DeleteService(key)
@@ -387,6 +388,12 @@ func (p coreDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
 	return nil
 }
 
+func (p coreDNSProvider) etcdKeyFor(dnsName string) string {
+	domains := strings.Split(dnsName, ".")
+	reverse(domains)
+	return p.coreDNSPrefix + strings.Join(domains, "/")
+}
+
 func guessRecordType(target string) string {
 	if net.ParseIP(target) != nil {
 		return endpoint.RecordTypeA
@@ -394,12 +401,6 @@ func guessRecordType(target string) string {
 	return endpoint.RecordTypeCNAME
 }
 
-func etcdKeyFor(dnsName string) string {
-	domains := strings.Split(dnsName, ".")
-	reverse(domains)
-	return coreDNSPrefix + strings.Join(domains, "/")
-}
-
 func reverse(slice []string) {
 	for i := 0; i < len(slice)/2; i++ {
 		j := len(slice) - i - 1


Test patch:
diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go
index 78c50d97..16f77a75 100644
--- a/pkg/apis/externaldns/types_test.go
+++ b/pkg/apis/externaldns/types_test.go
@@ -54,6 +54,7 @@ var (
 		AzureResourceGroup:          "",
 		CloudflareProxied:           false,
 		CloudflareZonesPerPage:      50,
+		CoreDNSPrefix:               "/skydns/",
 		InfobloxGridHost:            "",
 		InfobloxWapiPort:            443,
 		InfobloxWapiUsername:        "admin",
@@ -115,6 +116,7 @@ var (
 		AzureResourceGroup:          "arg",
 		CloudflareProxied:           true,
 		CloudflareZonesPerPage:      20,
+		CoreDNSPrefix:               "/coredns/",
 		InfobloxGridHost:            "127.0.0.1",
 		InfobloxWapiPort:            8443,
 		InfobloxWapiUsername:        "infoblox",
@@ -182,6 +184,7 @@ var (
 		AzureResourceGroup:          "",
 		CloudflareProxied:           false,
 		CloudflareZonesPerPage:      50,
+		CoreDNSPrefix:               "/skydns/",
 		InfobloxGridHost:            "",
 		InfobloxWapiPort:            443,
 		InfobloxWapiUsername:        "admin",
@@ -250,6 +253,7 @@ func TestParseFlags(t *testing.T) {
 				"--azure-resource-group=arg",
 				"--cloudflare-proxied",
 				"--cloudflare-zones-per-page=20",
+				"--coredns-prefix=/coredns/",
 				"--infoblox-grid-host=127.0.0.1",
 				"--infoblox-wapi-port=8443",
 				"--infoblox-wapi-username=infoblox",
@@ -325,6 +329,7 @@ func TestParseFlags(t *testing.T) {
 				"EXTERNAL_DNS_AZURE_RESOURCE_GROUP":       "arg",
 				"EXTERNAL_DNS_CLOUDFLARE_PROXIED":         "1",
 				"EXTERNAL_DNS_CLOUDFLARE_ZONES_PER_PAGE":  "20",
+				"EXTERNAL_DNS_COREDNS_PREFIX":             "/coredns/",
 				"EXTERNAL_DNS_INFOBLOX_GRID_HOST":         "127.0.0.1",
 				"EXTERNAL_DNS_INFOBLOX_WAPI_PORT":         "8443",
 				"EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME":     "infoblox",
diff --git a/provider/coredns_test.go b/provider/coredns_test.go
index 698207a0..e86cc2fe 100644
--- a/provider/coredns_test.go
+++ b/provider/coredns_test.go
@@ -25,6 +25,8 @@ import (
 	"github.com/kubernetes-incubator/external-dns/plan"
 )
 
+const defaultCoreDNSPrefix = "/skydns/"
+
 type fakeETCDClient struct {
 	services map[string]*Service
 }
@@ -60,7 +62,10 @@ func TestAServiceTranslation(t *testing.T) {
 			"/skydns/com/example": {Host: expectedTarget},
 		},
 	}
-	provider := coreDNSProvider{client: client}
+	provider := coreDNSProvider{
+		client:        client,
+		coreDNSPrefix: defaultCoreDNSPrefix,
+	}
 	endpoints, err := provider.Records()
 	if err != nil {
 		t.Fatal(err)
@@ -89,7 +94,10 @@ func TestCNAMEServiceTranslation(t *testing.T) {
 			"/skydns/com/example": {Host: expectedTarget},
 		},
 	}
-	provider := coreDNSProvider{client: client}
+	provider := coreDNSProvider{
+		client:        client,
+		coreDNSPrefix: defaultCoreDNSPrefix,
+	}
 	endpoints, err := provider.Records()
 	if err != nil {
 		t.Fatal(err)
@@ -118,7 +126,10 @@ func TestTXTServiceTranslation(t *testing.T) {
 			"/skydns/com/example": {Text: expectedTarget},
 		},
 	}
-	provider := coreDNSProvider{client: client}
+	provider := coreDNSProvider{
+		client:        client,
+		coreDNSPrefix: defaultCoreDNSPrefix,
+	}
 	endpoints, err := provider.Records()
 	if err != nil {
 		t.Fatal(err)
@@ -149,7 +160,10 @@ func TestAWithTXTServiceTranslation(t *testing.T) {
 			"/skydns/com/example": {Host: "1.2.3.4", Text: "string"},
 		},
 	}
-	provider := coreDNSProvider{client: client}
+	provider := coreDNSProvider{
+		client:        client,
+		coreDNSPrefix: defaultCoreDNSPrefix,
+	}
 	endpoints, err := provider.Records()
 	if err != nil {
 		t.Fatal(err)
@@ -188,7 +202,10 @@ func TestCNAMEWithTXTServiceTranslation(t *testing.T) {
 			"/skydns/com/example": {Host: "example.net", Text: "string"},
 		},
 	}
-	provider := coreDNSProvider{client: client}
+	provider := coreDNSProvider{
+		client:        client,
+		coreDNSPrefix: defaultCoreDNSPrefix,
+	}
 	endpoints, err := provider.Records()
 	if err != nil {
 		t.Fatal(err)
@@ -219,7 +236,10 @@ func TestCoreDNSApplyChanges(t *testing.T) {
 	client := fakeETCDClient{
 		map[string]*Service{},
 	}
-	coredns := coreDNSProvider{client: client}
+	coredns := coreDNSProvider{
+		client:        client,
+		coreDNSPrefix: defaultCoreDNSPrefix,
+	}
 
 	changes1 := &plan.Changes{
 		Create: []*endpoint.Endpoint{