Skip to content

Commit

Permalink
allow filtering by source annotation (#354)
Browse files Browse the repository at this point in the history
* allow filtering by ingress class

* generic source annotation filter as opposed to ingress class filter

* rename and fix argument ordering, switch to label selector semantics

* remove redundant parameters
  • Loading branch information
khrisrichardson authored and Yerken committed Nov 9, 2017
1 parent 9d6d137 commit b23765e
Show file tree
Hide file tree
Showing 7 changed files with 393 additions and 64 deletions.
9 changes: 5 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ func main() {

// Create a source.Config from the flags passed by the user.
sourceCfg := &source.Config{
Namespace: cfg.Namespace,
FQDNTemplate: cfg.FQDNTemplate,
Compatibility: cfg.Compatibility,
PublishInternal: cfg.PublishInternal,
Namespace: cfg.Namespace,
AnnotationFilter: cfg.AnnotationFilter,
FQDNTemplate: cfg.FQDNTemplate,
Compatibility: cfg.Compatibility,
PublishInternal: cfg.PublishInternal,
}

// Lookup all the selected sources by names and pass them the desired configuration.
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Config struct {
KubeConfig string
Sources []string
Namespace string
AnnotationFilter string
FQDNTemplate string
Compatibility string
PublishInternal bool
Expand Down Expand Up @@ -68,6 +69,7 @@ var defaultConfig = &Config{
KubeConfig: "",
Sources: nil,
Namespace: "",
AnnotationFilter: "",
FQDNTemplate: "",
Compatibility: "",
PublishInternal: false,
Expand Down Expand Up @@ -124,6 +126,7 @@ func (cfg *Config) ParseFlags(args []string) error {
// Flags related to processing sources
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, fake)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "fake")
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("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)").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate)
app.Flag("compatibility", "Process annotation semantics from legacy implementations (optional, options: mate, molecule)").Default(defaultConfig.Compatibility).EnumVar(&cfg.Compatibility, "", "mate", "molecule")
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)
Expand Down
52 changes: 45 additions & 7 deletions source/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
log "github.com/sirupsen/logrus"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/apis/extensions/v1beta1"

Expand All @@ -36,13 +37,14 @@ import (
// Use targetAnnotationKey to explicitly set Endpoint. (useful if the ingress
// controller does not update, or to override with alternative endpoint)
type ingressSource struct {
client kubernetes.Interface
namespace string
fqdnTemplate *template.Template
client kubernetes.Interface
namespace string
annotationFilter string
fqdnTemplate *template.Template
}

// NewIngressSource creates a new ingressSource with the given config.
func NewIngressSource(kubeClient kubernetes.Interface, namespace, fqdnTemplate string) (Source, error) {
func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string) (Source, error) {
var (
tmpl *template.Template
err error
Expand All @@ -57,9 +59,10 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, fqdnTemplate s
}

return &ingressSource{
client: kubeClient,
namespace: namespace,
fqdnTemplate: tmpl,
client: kubeClient,
namespace: namespace,
annotationFilter: annotationFilter,
fqdnTemplate: tmpl,
}, nil
}

Expand All @@ -70,6 +73,10 @@ func (sc *ingressSource) Endpoints() ([]*endpoint.Endpoint, error) {
if err != nil {
return nil, err
}
ingresses.Items, err = sc.filterByAnnotations(ingresses.Items)
if err != nil {
return nil, err
}

endpoints := []*endpoint.Endpoint{}

Expand Down Expand Up @@ -159,6 +166,37 @@ func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoin
return endpoints, nil
}

// filterByAnnotations filters a list of ingresses by a given annotation selector.
func (sc *ingressSource) filterByAnnotations(ingresses []v1beta1.Ingress) ([]v1beta1.Ingress, error) {
labelSelector, err := metav1.ParseToLabelSelector(sc.annotationFilter)
if err != nil {
return nil, err
}
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
if err != nil {
return nil, err
}

// empty filter returns original list
if selector.Empty() {
return ingresses, nil
}

filteredList := []v1beta1.Ingress{}

for _, ingress := range ingresses {
// convert the ingress' annotations to an equivalent label selector
annotations := labels.Set(ingress.Annotations)

// include ingress if its annotations match the selector
if selector.Matches(annotations) {
filteredList = append(filteredList, ingress)
}
}

return filteredList, nil
}

// endpointsFromIngress extracts the endpoints from ingress object
func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
Expand Down
128 changes: 119 additions & 9 deletions source/ingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ func TestIngress(t *testing.T) {

func TestNewIngressSource(t *testing.T) {
for _, ti := range []struct {
title string
fqdnTemplate string
expectError bool
title string
annotationFilter string
fqdnTemplate string
expectError bool
}{
{
title: "invalid template",
Expand All @@ -58,11 +59,17 @@ func TestNewIngressSource(t *testing.T) {
expectError: false,
fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com",
},
{
title: "non-empty annotation filter label",
expectError: false,
annotationFilter: "kubernetes.io/ingress.class=nginx",
},
} {
t.Run(ti.title, func(t *testing.T) {
_, err := NewIngressSource(
fake.NewSimpleClientset(),
"",
ti.annotationFilter,
ti.fqdnTemplate,
)
if ti.expectError {
Expand Down Expand Up @@ -167,11 +174,13 @@ func testEndpointsFromIngress(t *testing.T) {
func testIngressEndpoints(t *testing.T) {
namespace := "testing"
for _, ti := range []struct {
title string
targetNamespace string
ingressItems []fakeIngress
expected []*endpoint.Endpoint
fqdnTemplate string
title string
targetNamespace string
annotationFilter string
ingressItems []fakeIngress
expected []*endpoint.Endpoint
expectError bool
fqdnTemplate string
}{
{
title: "no ingress",
Expand Down Expand Up @@ -257,6 +266,102 @@ func testIngressEndpoints(t *testing.T) {
},
},
},
{
title: "valid matching annotation filter expression",
targetNamespace: "",
annotationFilter: "kubernetes.io/ingress.class in (alb, nginx)",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
"kubernetes.io/ingress.class": "nginx",
},
dnsnames: []string{"example.org"},
ips: []string{"8.8.8.8"},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
},
},
},
{
title: "valid non-matching annotation filter expression",
targetNamespace: "",
annotationFilter: "kubernetes.io/ingress.class in (alb, nginx)",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
"kubernetes.io/ingress.class": "tectonic",
},
dnsnames: []string{"example.org"},
ips: []string{"8.8.8.8"},
},
},
expected: []*endpoint.Endpoint{},
},
{
title: "invalid annotation filter expression",
targetNamespace: "",
annotationFilter: "kubernetes.io/ingress.name in (a b)",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
"kubernetes.io/ingress.class": "alb",
},
dnsnames: []string{"example.org"},
ips: []string{"8.8.8.8"},
},
},
expected: []*endpoint.Endpoint{},
expectError: true,
},
{
title: "valid matching annotation filter label",
targetNamespace: "",
annotationFilter: "kubernetes.io/ingress.class=nginx",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
"kubernetes.io/ingress.class": "nginx",
},
dnsnames: []string{"example.org"},
ips: []string{"8.8.8.8"},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
},
},
},
{
title: "valid non-matching annotation filter label",
targetNamespace: "",
annotationFilter: "kubernetes.io/ingress.class=nginx",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
"kubernetes.io/ingress.class": "alb",
},
dnsnames: []string{"example.org"},
ips: []string{"8.8.8.8"},
},
},
expected: []*endpoint.Endpoint{},
},
{
title: "our controller type is dns-controller",
targetNamespace: "",
Expand Down Expand Up @@ -490,6 +595,7 @@ func testIngressEndpoints(t *testing.T) {
ingressSource, _ := NewIngressSource(
fakeClient,
ti.targetNamespace,
ti.annotationFilter,
ti.fqdnTemplate,
)
for _, ingress := range ingresses {
Expand All @@ -498,7 +604,11 @@ func testIngressEndpoints(t *testing.T) {
}

res, err := ingressSource.Endpoints()
require.NoError(t, err)
if ti.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}

validateEndpoints(t, res, ti.expected)
})
Expand Down
Loading

0 comments on commit b23765e

Please sign in to comment.