diff --git a/main.go b/main.go index 295594560..4f8cbb3da 100644 --- a/main.go +++ b/main.go @@ -75,6 +75,7 @@ func main() { PublishInternal: cfg.PublishInternal, PublishHostIP: cfg.PublishHostIP, ConnectorServer: cfg.ConnectorSourceServer, + ServiceTypeFilter: cfg.ServiceTypeFilter, } // Lookup all the selected sources by names and pass them the desired configuration. diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index de55a2af3..5917f3c82 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -91,6 +91,7 @@ type Config struct { ExoscaleEndpoint string ExoscaleAPIKey string ExoscaleAPISecret string + ServiceTypeFilter []string } var defaultConfig = &Config{ @@ -144,6 +145,7 @@ var defaultConfig = &Config{ ExoscaleEndpoint: "https://api.exoscale.ch/dns", ExoscaleAPIKey: "", ExoscaleAPISecret: "", + ServiceTypeFilter: []string{}, } // NewConfig returns new Config object @@ -197,6 +199,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal) app.Flag("publish-host-ip", "Allow external-dns to publish host-ip for headless services (optional)").BoolVar(&cfg.PublishHostIP) app.Flag("connector-source-server", "The server to connect for connector source, valid only when using connector source").Default(defaultConfig.ConnectorSourceServer).StringVar(&cfg.ConnectorSourceServer) + app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter) // Flags related to providers app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, cloudflare, digitalocean, dnsimple, infoblox, dyn, designate, coredns, skydns, inmemory, pdns, oci, exoscale, linode)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "cloudflare", "digitalocean", "dnsimple", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "pdns", "oci", "exoscale", "linode") diff --git a/source/service.go b/source/service.go index 83c8ec990..cc6639ab7 100644 --- a/source/service.go +++ b/source/service.go @@ -52,10 +52,11 @@ type serviceSource struct { combineFQDNAnnotation bool publishInternal bool publishHostIP bool + serviceTypeFilter map[string]struct{} } // NewServiceSource creates a new serviceSource with the given config. -func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool) (Source, error) { +func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, serviceTypeFilter []string) (Source, error) { var ( tmpl *template.Template err error @@ -69,6 +70,13 @@ func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilt } } + // Transform the slice into a map so it will + // be way much easier and fast to filter later + serviceTypes := make(map[string]struct{}) + for _, serviceType := range serviceTypeFilter { + serviceTypes[serviceType] = struct{}{} + } + return &serviceSource{ client: kubeClient, namespace: namespace, @@ -78,6 +86,7 @@ func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilt combineFQDNAnnotation: combineFqdnAnnotation, publishInternal: publishInternal, publishHostIP: publishHostIP, + serviceTypeFilter: serviceTypes, }, nil } @@ -92,6 +101,11 @@ func (sc *serviceSource) Endpoints() ([]*endpoint.Endpoint, error) { return nil, err } + // filter on service types if at least one has been provided + if len(sc.serviceTypeFilter) > 0 { + services.Items = sc.filterByServiceType(services.Items) + } + // get the ip addresses of all the nodes and cache them for this run nodeTargets, err := sc.extractNodeTargets() if err != nil { @@ -254,6 +268,19 @@ func (sc *serviceSource) filterByAnnotations(services []v1.Service) ([]v1.Servic return filteredList, nil } +// filterByServiceType filters services according their types +func (sc *serviceSource) filterByServiceType(services []v1.Service) []v1.Service { + filteredList := []v1.Service{} + for _, service := range services { + // Check if the service is of the given type or not + if _, ok := sc.serviceTypeFilter[string(service.Spec.Type)]; ok { + filteredList = append(filteredList, service) + } + } + + return filteredList +} + func (sc *serviceSource) setResourceLabel(service v1.Service, endpoints []*endpoint.Endpoint) { for _, ep := range endpoints { ep.Labels[endpoint.ResourceLabelKey] = fmt.Sprintf("service/%s/%s", service.Namespace, service.Name) diff --git a/source/service_test.go b/source/service_test.go index dc06d61ec..edc4666ab 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -50,6 +50,7 @@ func (suite *ServiceSuite) SetupTest() { "", false, false, + []string{}, ) suite.fooWithTargets = &v1.Service{ Spec: v1.ServiceSpec{ @@ -99,10 +100,11 @@ func testServiceSourceImplementsSource(t *testing.T) { // testServiceSourceNewServiceSource tests that NewServiceSource doesn't return an error. func testServiceSourceNewServiceSource(t *testing.T) { for _, ti := range []struct { - title string - annotationFilter string - fqdnTemplate string - expectError bool + title string + annotationFilter string + fqdnTemplate string + serviceTypesFilter []string + expectError bool }{ { title: "invalid template", @@ -123,6 +125,11 @@ func testServiceSourceNewServiceSource(t *testing.T) { expectError: false, annotationFilter: "kubernetes.io/ingress.class=nginx", }, + { + title: "non-empty service types filter", + expectError: false, + serviceTypesFilter: []string{string(v1.ServiceTypeClusterIP)}, + }, } { t.Run(ti.title, func(t *testing.T) { _, err := NewServiceSource( @@ -134,6 +141,7 @@ func testServiceSourceNewServiceSource(t *testing.T) { "", false, false, + ti.serviceTypesFilter, ) if ti.expectError { @@ -161,6 +169,7 @@ func testServiceSourceEndpoints(t *testing.T) { annotations map[string]string clusterIP string lbs []string + serviceTypesFilter []string expected []*endpoint.Endpoint expectError bool }{ @@ -178,6 +187,7 @@ func testServiceSourceEndpoints(t *testing.T) { map[string]string{}, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -197,6 +207,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -218,6 +229,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "1.2.3.4", []string{}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -235,6 +247,7 @@ func testServiceSourceEndpoints(t *testing.T) { map[string]string{}, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}}, @@ -257,6 +270,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, @@ -281,6 +295,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, @@ -303,6 +318,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, @@ -325,6 +341,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"lb.example.com"}, // Kubernetes omits the trailing dot + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}}, }, @@ -346,6 +363,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}}, @@ -369,6 +387,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -391,6 +410,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -410,6 +430,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -431,6 +452,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -450,6 +472,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -472,6 +495,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -494,6 +518,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -514,6 +539,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{}, true, }, @@ -534,6 +560,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -556,6 +583,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -575,6 +603,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -594,6 +623,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4", "8.8.8.8"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4", "8.8.8.8"}}, }, @@ -615,6 +645,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -634,6 +665,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -657,6 +689,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, @@ -677,6 +710,7 @@ func testServiceSourceEndpoints(t *testing.T) { map[string]string{}, "", []string{"1.2.3.4", "elb.com"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"elb.com"}}, @@ -699,6 +733,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4", "elb.com"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"elb.com"}}, @@ -721,6 +756,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "mate.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -740,6 +776,7 @@ func testServiceSourceEndpoints(t *testing.T) { map[string]string{}, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{}, true, }, @@ -759,6 +796,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, }, @@ -781,6 +819,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, }, @@ -803,6 +842,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(10)}, }, @@ -825,11 +865,54 @@ func testServiceSourceEndpoints(t *testing.T) { }, "", []string{"1.2.3.4"}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, }, false, }, + { + "filter on service types should include matching services", + "", + "", + "testing", + "foo", + v1.ServiceTypeLoadBalancer, + "", + "", + false, + map[string]string{}, + map[string]string{ + hostnameAnnotationKey: "foo.example.org.", + }, + "", + []string{"1.2.3.4"}, + []string{string(v1.ServiceTypeLoadBalancer)}, + []*endpoint.Endpoint{ + {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, + }, + false, + }, + { + "filter on service types should exclude non-matching services", + "", + "", + "testing", + "foo", + v1.ServiceTypeNodePort, + "", + "", + false, + map[string]string{}, + map[string]string{ + hostnameAnnotationKey: "foo.example.org.", + }, + "", + []string{"1.2.3.4"}, + []string{string(v1.ServiceTypeLoadBalancer)}, + []*endpoint.Endpoint{}, + false, + }, } { t.Run(tc.title, func(t *testing.T) { // Create a Kubernetes testing client @@ -876,6 +959,7 @@ func testServiceSourceEndpoints(t *testing.T) { tc.compatibility, false, false, + tc.serviceTypesFilter, ) require.NoError(t, err) @@ -1010,6 +1094,7 @@ func TestClusterIpServices(t *testing.T) { tc.compatibility, true, false, + []string{}, ) require.NoError(t, err) @@ -1206,6 +1291,7 @@ func TestNodePortServices(t *testing.T) { tc.compatibility, true, false, + []string{}, ) require.NoError(t, err) @@ -1406,6 +1492,7 @@ func TestHeadlessServices(t *testing.T) { tc.compatibility, true, false, + []string{}, ) require.NoError(t, err) @@ -1606,6 +1693,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { tc.compatibility, true, true, + []string{}, ) require.NoError(t, err) @@ -1646,7 +1734,7 @@ func BenchmarkServiceEndpoints(b *testing.B) { _, err := kubernetes.CoreV1().Services(service.Namespace).Create(service) require.NoError(b, err) - client, err := NewServiceSource(kubernetes, v1.NamespaceAll, "", "", false, "", false, false) + client, err := NewServiceSource(kubernetes, v1.NamespaceAll, "", "", false, "", false, false, []string{}) require.NoError(b, err) for i := 0; i < b.N; i++ { diff --git a/source/store.go b/source/store.go index d082a5b54..e6a24ce81 100644 --- a/source/store.go +++ b/source/store.go @@ -44,6 +44,7 @@ type Config struct { PublishInternal bool PublishHostIP bool ConnectorServer string + ServiceTypeFilter []string } // ClientGenerator provides clients @@ -92,7 +93,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err if err != nil { return nil, err } - return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP) + return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.ServiceTypeFilter) case "ingress": client, err := p.KubeClient() if err != nil {