diff --git a/main.go b/main.go index 3e2f81a1fe..867de5da9f 100644 --- a/main.go +++ b/main.go @@ -100,6 +100,7 @@ func main() { sourceCfg := &source.Config{ Namespace: cfg.Namespace, AnnotationFilter: cfg.AnnotationFilter, + LabelFilter: cfg.LabelFilter, FQDNTemplate: cfg.FQDNTemplate, CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation, IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation, diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 037d9bfbd6..c6f96ae1a1 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -47,6 +47,7 @@ type Config struct { Sources []string Namespace string AnnotationFilter string + LabelFilter string FQDNTemplate string CombineFQDNAndAnnotation bool IgnoreHostnameAnnotation bool @@ -157,6 +158,7 @@ var defaultConfig = &Config{ Sources: nil, Namespace: "", AnnotationFilter: "", + LabelFilter: "", FQDNTemplate: "", CombineFQDNAndAnnotation: false, IgnoreHostnameAnnotation: false, @@ -310,6 +312,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace) app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter) + app.Flag("label-filter", "Filter sources managed by external-dns via label selector when listing all resources; currently only supported by source CRD").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter) app.Flag("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate) app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation) app.Flag("ignore-hostname-annotation", "Ignore hostname annotation when generating DNS names, valid only when using fqdn-template is set (optional, default: false)").BoolVar(&cfg.IgnoreHostnameAnnotation) diff --git a/source/crd.go b/source/crd.go index 26ac77e505..a897d45aa1 100644 --- a/source/crd.go +++ b/source/crd.go @@ -43,6 +43,7 @@ type crdSource struct { crdResource string codec runtime.ParameterCodec annotationFilter string + labelFilter string } func addKnownTypes(scheme *runtime.Scheme, groupVersion schema.GroupVersion) error { @@ -102,11 +103,12 @@ func NewCRDClientForAPIVersionKind(client kubernetes.Interface, kubeConfig, apiS } // NewCRDSource creates a new crdSource with the given config. -func NewCRDSource(crdClient rest.Interface, namespace, kind string, annotationFilter string, scheme *runtime.Scheme) (Source, error) { +func NewCRDSource(crdClient rest.Interface, namespace, kind string, annotationFilter string, labelFilter string, scheme *runtime.Scheme) (Source, error) { return &crdSource{ crdResource: strings.ToLower(kind) + "s", namespace: namespace, annotationFilter: annotationFilter, + labelFilter: labelFilter, crdClient: crdClient, codec: runtime.NewParameterCodec(scheme), }, nil @@ -119,12 +121,22 @@ func (cs *crdSource) AddEventHandler(ctx context.Context, handler func()) { func (cs *crdSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { endpoints := []*endpoint.Endpoint{} - result, err := cs.List(ctx, &metav1.ListOptions{}) + var ( + result *endpoint.DNSEndpointList + err error + ) + + if cs.labelFilter != "" { + result, err = cs.List(ctx, &metav1.ListOptions{LabelSelector: cs.labelFilter}) + } else { + result, err = cs.List(ctx, &metav1.ListOptions{}) + } if err != nil { return nil, err } result, err = cs.filterByAnnotations(result) + if err != nil { return nil, err } diff --git a/source/crd_test.go b/source/crd_test.go index c4f8829906..c669b50803 100644 --- a/source/crd_test.go +++ b/source/crd_test.go @@ -57,7 +57,7 @@ func objBody(codec runtime.Encoder, obj runtime.Object) io.ReadCloser { return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) } -func startCRDServerToServeTargets(endpoints []*endpoint.Endpoint, apiVersion, kind, namespace, name string, annotations map[string]string, t *testing.T) rest.Interface { +func startCRDServerToServeTargets(endpoints []*endpoint.Endpoint, apiVersion, kind, namespace, name string, annotations map[string]string, labels map[string]string, t *testing.T) rest.Interface { groupVersion, _ := schema.ParseGroupVersion(apiVersion) scheme := runtime.NewScheme() addKnownTypes(scheme, groupVersion) @@ -72,6 +72,7 @@ func startCRDServerToServeTargets(endpoints []*endpoint.Endpoint, apiVersion, ki Name: name, Namespace: namespace, Annotations: annotations, + Labels: labels, Generation: 1, }, Spec: endpoint.DNSEndpointSpec{ @@ -139,7 +140,9 @@ func testCRDSourceEndpoints(t *testing.T) { expectEndpoints bool expectError bool annotationFilter string + labelFilter string annotations map[string]string + labels map[string]string }{ { title: "invalid crd api version", @@ -308,16 +311,56 @@ func testCRDSourceEndpoints(t *testing.T) { expectEndpoints: true, expectError: false, }, + { + title: "valid crd gvk with label and non matching label filter", + registeredAPIVersion: "test.k8s.io/v1alpha1", + apiVersion: "test.k8s.io/v1alpha1", + registeredKind: "DNSEndpoint", + kind: "DNSEndpoint", + namespace: "foo", + registeredNamespace: "foo", + labels: map[string]string{"test": "that"}, + labelFilter: "test=filter_something_else", + endpoints: []*endpoint.Endpoint{ + {DNSName: "abc.example.org", + Targets: endpoint.Targets{"1.2.3.4"}, + RecordType: endpoint.RecordTypeA, + RecordTTL: 180, + }, + }, + expectEndpoints: false, + expectError: false, + }, + { + title: "valid crd gvk with label and matching label filter", + registeredAPIVersion: "test.k8s.io/v1alpha1", + apiVersion: "test.k8s.io/v1alpha1", + registeredKind: "DNSEndpoint", + kind: "DNSEndpoint", + namespace: "foo", + registeredNamespace: "foo", + labels: map[string]string{"test": "that"}, + labelFilter: "test=that", + endpoints: []*endpoint.Endpoint{ + {DNSName: "abc.example.org", + Targets: endpoint.Targets{"1.2.3.4"}, + RecordType: endpoint.RecordTypeA, + RecordTTL: 180, + }, + }, + expectEndpoints: true, + expectError: false, + }, } { t.Run(ti.title, func(t *testing.T) { - restClient := startCRDServerToServeTargets(ti.endpoints, ti.registeredAPIVersion, ti.registeredKind, ti.registeredNamespace, "test", ti.annotations, t) + restClient := startCRDServerToServeTargets(ti.endpoints, ti.registeredAPIVersion, ti.registeredKind, ti.registeredNamespace, "test", ti.annotations, ti.labels, t) groupVersion, err := schema.ParseGroupVersion(ti.apiVersion) require.NoError(t, err) scheme := runtime.NewScheme() addKnownTypes(scheme, groupVersion) - cs, _ := NewCRDSource(restClient, ti.namespace, ti.kind, ti.annotationFilter, scheme) + cs, _ := NewCRDSource(restClient, ti.namespace, ti.kind, ti.annotationFilter, ti.labelFilter, scheme) receivedEndpoints, err := cs.Endpoints(context.Background()) if ti.expectError { diff --git a/source/store.go b/source/store.go index a06571f827..3f3a1321e5 100644 --- a/source/store.go +++ b/source/store.go @@ -42,6 +42,7 @@ var ErrSourceNotFound = errors.New("source not found") type Config struct { Namespace string AnnotationFilter string + LabelFilter string FQDNTemplate string CombineFQDNAndAnnotation bool IgnoreHostnameAnnotation bool @@ -247,7 +248,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err if err != nil { return nil, err } - return NewCRDSource(crdClient, cfg.Namespace, cfg.CRDSourceKind, cfg.AnnotationFilter, scheme) + return NewCRDSource(crdClient, cfg.Namespace, cfg.CRDSourceKind, cfg.AnnotationFilter, cfg.LabelFilter, scheme) case "skipper-routegroup": apiServerURL := cfg.APIServerURL tokenPath := ""