diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index e074423e7..c859061e4 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -40,7 +40,7 @@ type Config struct { Provider string GoogleProject string Policy string - Compatibility bool + Compatibility string MetricsAddress string Interval time.Duration Once bool @@ -71,7 +71,7 @@ func (cfg *Config) ParseFlags(args []string) error { flags.StringVar(&cfg.Provider, "provider", "", "the DNS provider to materialize the records in: ") flags.StringVar(&cfg.GoogleProject, "google-project", "", "gcloud project to target") flags.StringVar(&cfg.Policy, "policy", "sync", "the policy to use: ") - flags.BoolVar(&cfg.Compatibility, "compatibility", false, "enable to process annotation semantics from legacy implementations") + flags.StringVar(&cfg.Compatibility, "compatibility", "", "enable to process annotation semantics from legacy implementations: ") flags.StringVar(&cfg.MetricsAddress, "metrics-address", defaultMetricsAddress, "address to expose metrics on") flags.StringVar(&cfg.LogFormat, "log-format", defaultLogFormat, "log format output: ") flags.DurationVar(&cfg.Interval, "interval", time.Minute, "interval between synchronizations") diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index fd0409819..719d65081 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -42,7 +42,7 @@ func TestParseFlags(t *testing.T) { Provider: "", GoogleProject: "", Policy: "sync", - Compatibility: false, + Compatibility: "", MetricsAddress: defaultMetricsAddress, Interval: time.Minute, Once: false, @@ -69,7 +69,7 @@ func TestParseFlags(t *testing.T) { Provider: "", GoogleProject: "", Policy: "sync", - Compatibility: false, + Compatibility: "", MetricsAddress: defaultMetricsAddress, Interval: time.Minute, Once: false, @@ -96,7 +96,7 @@ func TestParseFlags(t *testing.T) { Provider: "", GoogleProject: "", Policy: "sync", - Compatibility: false, + Compatibility: "", MetricsAddress: defaultMetricsAddress, Interval: time.Minute, Once: false, @@ -128,7 +128,7 @@ func TestParseFlags(t *testing.T) { Provider: "", GoogleProject: "", Policy: "sync", - Compatibility: false, + Compatibility: "", MetricsAddress: defaultMetricsAddress, Interval: time.Minute, Once: false, @@ -154,7 +154,7 @@ func TestParseFlags(t *testing.T) { "--provider", "provider", "--google-project", "project", "--policy", "upsert-only", - "--compatibility", + "--compatibility=mate", "--metrics-address", "127.0.0.1:9099", "--interval", "10m", "--once", @@ -175,7 +175,7 @@ func TestParseFlags(t *testing.T) { Provider: "provider", GoogleProject: "project", Policy: "upsert-only", - Compatibility: true, + Compatibility: "mate", MetricsAddress: "127.0.0.1:9099", Interval: 10 * time.Minute, Once: true, diff --git a/source/compatibility.go b/source/compatibility.go index d494848f1..b37348a89 100644 --- a/source/compatibility.go +++ b/source/compatibility.go @@ -23,13 +23,21 @@ import ( ) const ( - mateAnnotationKey = "zalando.org/dnsname" + mateAnnotationKey = "zalando.org/dnsname" + moleculeAnnotationKey = "domainName" ) // legacyEndpointsFromService tries to retrieve Endpoints from Services // annotated with legacy annotations. -func legacyEndpointsFromService(svc *v1.Service) []*endpoint.Endpoint { - return legacyEndpointsFromMateService(svc) +func legacyEndpointsFromService(svc *v1.Service, compatibility string) []*endpoint.Endpoint { + switch compatibility { + case "mate": + return legacyEndpointsFromMateService(svc) + case "molecule": + return legacyEndpointsFromMoleculeService(svc) + } + + return []*endpoint.Endpoint{} } // legacyEndpointsFromMateService tries to retrieve Endpoints from Services @@ -55,3 +63,32 @@ func legacyEndpointsFromMateService(svc *v1.Service) []*endpoint.Endpoint { return endpoints } + +// legacyEndpointsFromMoleculeService tries to retrieve Endpoints from Services +// annotated with Molecule Software's annotation semantics. +func legacyEndpointsFromMoleculeService(svc *v1.Service) []*endpoint.Endpoint { + var endpoints []*endpoint.Endpoint + + // Check that the Service opted-in to being processed. + if svc.Labels["dns"] != "route53" { + return nil + } + + // Get the desired hostname of the service from the annotation. + hostname, exists := svc.Annotations[moleculeAnnotationKey] + if !exists { + return nil + } + + // Create a corresponding endpoint for each configured external entrypoint. + for _, lb := range svc.Status.LoadBalancer.Ingress { + if lb.IP != "" { + endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.IP, "")) + } + if lb.Hostname != "" { + endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.Hostname, "")) + } + } + + return endpoints +} diff --git a/source/service.go b/source/service.go index fa18b35d2..731f8b6b0 100644 --- a/source/service.go +++ b/source/service.go @@ -36,13 +36,13 @@ import ( type serviceSource struct { client kubernetes.Interface namespace string - // set to true to process Services with legacy annotations - compatibility bool + // process Services with legacy annotations + compatibility string fqdntemplate *template.Template } // NewServiceSource creates a new serviceSource with the given client and namespace scope. -func NewServiceSource(client kubernetes.Interface, namespace, fqdntemplate string, compatibility bool) (Source, error) { +func NewServiceSource(client kubernetes.Interface, namespace, fqdntemplate string, compatibility string) (Source, error) { var tmpl *template.Template var err error if fqdntemplate != "" { @@ -81,8 +81,8 @@ func (sc *serviceSource) Endpoints() ([]*endpoint.Endpoint, error) { svcEndpoints := endpointsFromService(&svc) // process legacy annotations if no endpoints were returned and compatibility mode is enabled. - if len(svcEndpoints) == 0 && sc.compatibility { - svcEndpoints = legacyEndpointsFromService(&svc) + if len(svcEndpoints) == 0 && sc.compatibility != "" { + svcEndpoints = legacyEndpointsFromService(&svc, sc.compatibility) } // apply template if none of the above is found diff --git a/source/service_test.go b/source/service_test.go index 37f6c10ac..ad1d8f367 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -55,7 +55,7 @@ func TestNewServiceSource(t *testing.T) { }, } { t.Run(ti.title, func(t *testing.T) { - _, err := NewServiceSource(fake.NewSimpleClientset(), "", ti.fqdntemplate, false) + _, err := NewServiceSource(fake.NewSimpleClientset(), "", ti.fqdntemplate, "") if ti.expectError && err == nil { t.Error("invalid template should return err") } @@ -73,8 +73,9 @@ func testServiceEndpoints(t *testing.T) { targetNamespace string svcNamespace string svcName string - compatibility bool + compatibility string fqdntemplate string + labels map[string]string annotations map[string]string lbs []string expected []*endpoint.Endpoint @@ -84,8 +85,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{}, []string{"1.2.3.4"}, []*endpoint.Endpoint{}, @@ -95,8 +97,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, @@ -110,8 +113,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, @@ -125,8 +129,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted }, @@ -141,8 +146,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ controllerAnnotationKey: controllerAnnotationValue, hostnameAnnotationKey: "foo.example.org.", @@ -157,8 +163,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, + "", "{{.Name}}.ext-dns.test.com", + map[string]string{}, map[string]string{ controllerAnnotationKey: "some-other-tool", hostnameAnnotationKey: "foo.example.org.", @@ -171,8 +178,9 @@ func testServiceEndpoints(t *testing.T) { "testing", "testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, @@ -186,8 +194,9 @@ func testServiceEndpoints(t *testing.T) { "testing", "other-testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, @@ -199,8 +208,9 @@ func testServiceEndpoints(t *testing.T) { "", "other-testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, @@ -214,8 +224,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, @@ -227,8 +238,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, @@ -243,8 +255,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, "", + "", + map[string]string{}, map[string]string{ "zalando.org/dnsname": "foo.example.org.", }, @@ -256,8 +269,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - true, + "mate", "", + map[string]string{}, map[string]string{ "zalando.org/dnsname": "foo.example.org.", }, @@ -266,14 +280,33 @@ func testServiceEndpoints(t *testing.T) { {DNSName: "foo.example.org", Target: "1.2.3.4"}, }, }, + { + "services annotated with legacy molecule annotations return an endpoint in compatibility mode", + "", + "testing", + "foo", + "molecule", + "", + map[string]string{ + "dns": "route53", + }, + map[string]string{ + "domainName": "foo.example.org.", + }, + []string{"1.2.3.4"}, + []*endpoint.Endpoint{ + {DNSName: "foo.example.org", Target: "1.2.3.4"}, + }, + }, { "not annotated services with set fqdntemplate return an endpoint with target IP", "", "testing", "foo", - false, + "", "{{.Name}}.bar.example.com", map[string]string{}, + map[string]string{}, []string{"1.2.3.4", "elb.com"}, []*endpoint.Endpoint{ {DNSName: "foo.bar.example.com", Target: "1.2.3.4"}, @@ -285,9 +318,10 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - false, + "", "{{.Calibre}}.bar.example.com", map[string]string{}, + map[string]string{}, []string{"1.2.3.4"}, []*endpoint.Endpoint{}, }, @@ -296,8 +330,9 @@ func testServiceEndpoints(t *testing.T) { "", "testing", "foo", - true, + "mate", "{{.Name}}.bar.example.com", + map[string]string{}, map[string]string{ "zalando.org/dnsname": "mate.example.org.", }, @@ -325,6 +360,7 @@ func testServiceEndpoints(t *testing.T) { ObjectMeta: v1.ObjectMeta{ Namespace: tc.svcNamespace, Name: tc.svcName, + Labels: tc.labels, Annotations: tc.annotations, }, Status: v1.ServiceStatus{ @@ -379,7 +415,7 @@ func BenchmarkServiceEndpoints(b *testing.B) { b.Fatal(err) } - client, _ := NewServiceSource(kubernetes, v1.NamespaceAll, "", false) + client, _ := NewServiceSource(kubernetes, v1.NamespaceAll, "", "") for i := 0; i < b.N; i++ { _, err := client.Endpoints()