diff --git a/docs/faq.md b/docs/faq.md index 044ee7aa86..43731a3712 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -257,24 +257,33 @@ spec: ### Running an internal and external dns service Sometimes you need to run an internal and an external dns service. -The internal one should provision hostnames used on the internal network (perhaps inside a VPC), and the external -one to expose DNS to the internet. +The internal one should provision hostnames used on the internal network (perhaps inside a VPC), and the external one to expose DNS to the internet. -To do this with ExternalDNS you can use the `--annotation-filter` to specifically tie an instance of ExternalDNS to -an instance of an ingress controller. Let's assume you have two ingress controllers `nginx-internal` and `nginx-external` -then you can start two ExternalDNS providers one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-internal)` -and one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)`. +To do this with ExternalDNS you can use the `--ingress-class` flag to specifically tie an instance of ExternalDNS to an instance of a ingress controller. +Let's assume you have two ingress controllers, `internal` and `external`. +You can then start two ExternalDNS providers, one with `--ingress-class=internal` and one with `--ingress-class=external`. -If you need to search for multiple values of said annotation, you can provide a comma separated list, like so: -`--annotation-filter=kubernetes.io/ingress.class in (nginx-internal, alb-ingress-internal)`. +If you need to search for multiple ingress classes, you can specify the flag multiple times, like so: +`--ingress-class=internal --ingress-class=external`. -Beware when using multiple sources, e.g. `--source=service --source=ingress`, `--annotation-filter` will filter every given source objects. -If you need to filter only one specific source you have to run a separated external dns service containing only the wanted `--source` and `--annotation-filter`. +The `--ingress-class` flag will check both the `spec.ingressClassName` field and the deprecated `kubernetes.io/ingress.class` annotation. +The `spec.ingressClassName` tasks precedence over the annotation if both are supplied. -**Note:** Filtering based on annotation means that the external-dns controller will receive all resources of that kind and then filter on the client-side. -In larger clusters with many resources which change frequently this can cause performance issues. If only some resources need to be managed by an instance -of external-dns then label filtering can be used instead of annotation filtering. This means that only those resources which match the selector specified -in `--label-filter` will be passed to the controller. +**Backward compatibility** + +The previous `--annotation-filter` flag can still be used to restrict which objects ExternalDNS considers; for example, `--annotation-filter=kubernetes.io/ingress.class in (public,dmz)`. + +However, beware when using annotation filters with multiple sources, e.g. `--source=service --source=ingress`, since `--annotation-filter` will filter every given source object. +If you need to use annotation filters against a specific source you have to run a separated external dns service containing only the wanted `--source` and `--annotation-filter`. + +Note: the `--ingress-class` flag cannot be used at the same time as the `--annotation-filter=kubernetes.io/ingress.class in (...)` flag; if you do this an error will be raised. + +**Performance considerations** + +Filtering based on ingress class name or annotations means that the external-dns controller will receive all resources of that kind and then filter on the client-side. +In larger clusters with many resources which change frequently this can cause performance issues. +If only some resources need to be managed by an instance of external-dns then label filtering can be used instead of ingress class filtering (or legacy annotation filtering). +This means that only those resources which match the selector specified in `--label-filter` will be passed to the controller. ### How do I specify that I want the DNS record to point to either the Node's public or private IP when it has both? diff --git a/docs/tutorials/alibabacloud.md b/docs/tutorials/alibabacloud.md index ee0477edea..f7653237e4 100644 --- a/docs/tutorials/alibabacloud.md +++ b/docs/tutorials/alibabacloud.md @@ -233,9 +233,8 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: foo - annotations: - kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller. spec: + ingressClassName: nginx # use the one that corresponds to your ingress controller. rules: - host: foo.external-dns-test.com http: diff --git a/docs/tutorials/aws-load-balancer-controller.md b/docs/tutorials/aws-load-balancer-controller.md index 9e66b1e6da..98bc5da693 100644 --- a/docs/tutorials/aws-load-balancer-controller.md +++ b/docs/tutorials/aws-load-balancer-controller.md @@ -24,7 +24,7 @@ as Kubernetes does with the AWS cloud provider. In the examples that follow, it is assumed that you configured the ALB Ingress Controller with the `ingress-class=alb` argument (not to be confused with the same argument to ExternalDNS) so that the controller will only respect Ingress -objects with the `kubernetes.io/ingress.class` annotation set to "alb". +objects with the `ingressClassName` field set to "alb". ## Deploy an example application @@ -80,7 +80,6 @@ kind: Ingress metadata: annotations: alb.ingress.kubernetes.io/scheme: internet-facing - kubernetes.io/ingress.class: alb name: echoserver spec: ingressClassName: alb @@ -120,7 +119,6 @@ metadata: annotations: alb.ingress.kubernetes.io/scheme: internet-facing external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org - kubernetes.io/ingress.class: alb name: echoserver spec: ingressClassName: alb @@ -159,7 +157,6 @@ metadata: annotations: alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/ip-address-type: dualstack - kubernetes.io/ingress.class: alb name: echoserver spec: ingressClassName: alb diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index d5e7ad92b4..f77f8017f2 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -739,9 +739,8 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx - annotations: - kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller. spec: + ingressClassName: nginx rules: - host: server.example.com http: @@ -936,7 +935,7 @@ Running several fast polling ExternalDNS instances in a given account can easily * `--source=ingress --source=service` - specify multiple times for multiple sources * `--namespace=my-app` * `--label-filter=app in (my-app)` - * `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)` - note that this filter would apply to services too.. + * `--ingress-class=nginx-external` * Limit services watched by type (not applicable to ingress or other types) * `--service-type-filter=LoadBalancer` default `all` * Limit the hosted zones considered diff --git a/docs/tutorials/azure-private-dns.md b/docs/tutorials/azure-private-dns.md index 640036e461..0e85d90267 100644 --- a/docs/tutorials/azure-private-dns.md +++ b/docs/tutorials/azure-private-dns.md @@ -416,9 +416,8 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx - annotations: - kubernetes.io/ingress.class: nginx spec: + ingressClassName: nginx rules: - host: server.example.com http: diff --git a/docs/tutorials/coredns.md b/docs/tutorials/coredns.md index f2c11b8c2f..5cd1223f29 100644 --- a/docs/tutorials/coredns.md +++ b/docs/tutorials/coredns.md @@ -198,9 +198,8 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx - annotations: - kubernetes.io/ingress.class: "nginx" spec: + ingressClassName: nginx rules: - host: nginx.example.org http: diff --git a/docs/tutorials/exoscale.md b/docs/tutorials/exoscale.md index d1e93cb0f0..047c1d6b8b 100644 --- a/docs/tutorials/exoscale.md +++ b/docs/tutorials/exoscale.md @@ -109,9 +109,9 @@ kind: Ingress metadata: name: nginx annotations: - kubernetes.io/ingress.class: nginx external-dns.alpha.kubernetes.io/target: {{ Elastic-IP-address }} spec: + ingressClassName: nginx rules: - host: via-ingress.example.com http: diff --git a/docs/tutorials/kube-ingress-aws.md b/docs/tutorials/kube-ingress-aws.md index bea5e56ac1..5cf37d4ec9 100644 --- a/docs/tutorials/kube-ingress-aws.md +++ b/docs/tutorials/kube-ingress-aws.md @@ -141,8 +141,6 @@ Create the following Ingress to expose the echoserver application to the Interne apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - annotations: - kubernetes.io/ingress.class: skipper name: echoserver spec: ingressClassName: skipper @@ -181,7 +179,6 @@ kind: Ingress metadata: annotations: external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org - kubernetes.io/ingress.class: skipper name: echoserver spec: ingressClassName: skipper @@ -218,7 +215,6 @@ kind: Ingress metadata: annotations: alb.ingress.kubernetes.io/ip-address-type: dualstack - kubernetes.io/ingress.class: skipper name: echoserver spec: ingressClassName: skipper @@ -256,7 +252,6 @@ kind: Ingress metadata: annotations: zalando.org/aws-load-balancer-type: nlb - kubernetes.io/ingress.class: skipper name: echoserver spec: ingressClassName: skipper diff --git a/docs/tutorials/nginx-ingress.md b/docs/tutorials/nginx-ingress.md index f6c170b4c4..ce79f24d92 100644 --- a/docs/tutorials/nginx-ingress.md +++ b/docs/tutorials/nginx-ingress.md @@ -294,8 +294,6 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx - annotations: - kubernetes.io/ingress.class: nginx spec: ingressClassName: nginx rules: @@ -595,8 +593,6 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx - annotations: - kubernetes.io/ingress.class: nginx spec: ingressClassName: nginx rules: diff --git a/docs/tutorials/public-private-route53.md b/docs/tutorials/public-private-route53.md index 9a4f7be6f4..66071e13d2 100644 --- a/docs/tutorials/public-private-route53.md +++ b/docs/tutorials/public-private-route53.md @@ -213,7 +213,7 @@ spec: Consult [AWS ExternalDNS setup docs](aws.md) for installation guidelines. -In ExternalDNS containers args, make sure to specify `annotation-filter` and `aws-zone-type`: +In ExternalDNS containers args, make sure to specify `aws-zone-type` and `ingress-class`: ```yaml apiVersion: apps/v1beta2 @@ -241,7 +241,7 @@ spec: - --provider=aws - --registry=txt - --txt-owner-id=external-dns - - --annotation-filter=kubernetes.io/ingress.class in (external-ingress) + - --ingress-class=external-ingress - --aws-zone-type=public image: registry.k8s.io/external-dns/external-dns:v0.13.4 name: external-dns-public @@ -251,7 +251,7 @@ spec: Consult [AWS ExternalDNS setup docs](aws.md) for installation guidelines. -In ExternalDNS containers args, make sure to specify `annotation-filter` and `aws-zone-type`: +In ExternalDNS containers args, make sure to specify `aws-zone-type` and `ingress-class`: ```yaml apiVersion: apps/v1beta2 @@ -279,7 +279,7 @@ spec: - --provider=aws - --registry=txt - --txt-owner-id=dev.k8s.nexus - - --annotation-filter=kubernetes.io/ingress.class in (internal-ingress) + - --ingress-class=internal-ingress - --aws-zone-type=private image: registry.k8s.io/external-dns/external-dns:v0.13.4 name: external-dns-private @@ -287,20 +287,19 @@ spec: ## Create application Service definitions -For this setup to work, you've to create two Service definitions for your application. +For this setup to work, you need to create two Ingress definitions for your application. -At first, create public Service definition: +At first, create a public Ingress definition: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - annotations: - kubernetes.io/ingress.class: "external-ingress" labels: app: app name: app-public spec: + ingressClassName: external-ingress rules: - host: app.domain.com http: @@ -313,18 +312,17 @@ spec: pathType: Prefix ``` -Then create private Service definition: +Then create a private Ingress definition: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - annotations: - kubernetes.io/ingress.class: "internal-ingress" labels: app: app name: app-private spec: + ingressClassName: internal-ingress rules: - host: app.domain.com http: @@ -347,12 +345,12 @@ metadata: certmanager.k8s.io/acme-challenge-type: "dns01" certmanager.k8s.io/acme-dns01-provider: "route53" certmanager.k8s.io/cluster-issuer: "letsencrypt-production" - kubernetes.io/ingress.class: "external-ingress" kubernetes.io/tls-acme: "true" labels: app: app name: app-public spec: + ingressClassName: "external-ingress" rules: - host: app.domain.com http: @@ -375,12 +373,11 @@ And reuse the requested certificate in private Service definition: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - annotations: - kubernetes.io/ingress.class: "internal-ingress" labels: app: app name: app-private spec: + ingressClassName: "internal-ingress" rules: - host: app.domain.com http: diff --git a/docs/tutorials/rdns.md b/docs/tutorials/rdns.md index 684c7c64d5..529b1765c2 100644 --- a/docs/tutorials/rdns.md +++ b/docs/tutorials/rdns.md @@ -142,9 +142,8 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx - annotations: - kubernetes.io/ingress.class: "nginx" spec: + ingressClassName: nginx rules: - host: nginx.lb.rancher.cloud http: diff --git a/main.go b/main.go index 960045ce98..cc2e08bf4e 100644 --- a/main.go +++ b/main.go @@ -114,6 +114,7 @@ func main() { Namespace: cfg.Namespace, AnnotationFilter: cfg.AnnotationFilter, LabelFilter: labelSelector, + IngressClassNames: cfg.IngressClassNames, FQDNTemplate: cfg.FQDNTemplate, CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation, IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation, diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index eefd2b2bf0..faa4b88cba 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -54,6 +54,7 @@ type Config struct { Namespace string AnnotationFilter string LabelFilter string + IngressClassNames []string FQDNTemplate string CombineFQDNAndAnnotation bool IgnoreHostnameAnnotation bool @@ -216,6 +217,7 @@ var defaultConfig = &Config{ Namespace: "", AnnotationFilter: "", LabelFilter: labels.Everything().String(), + IngressClassNames: nil, FQDNTemplate: "", CombineFQDNAndAnnotation: false, IgnoreHostnameAnnotation: false, @@ -413,6 +415,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 supported by source types CRD, ingress, service and openshift-route").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter) + app.Flag("ingress-class", "Require an ingress to have this class name (defaults to any class; specify multiple times to allow more than one class)").StringsVar(&cfg.IngressClassNames) 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/ingress.go b/source/ingress.go index 76d5429e7f..af7a9dc990 100644 --- a/source/ingress.go +++ b/source/ingress.go @@ -18,6 +18,7 @@ package source import ( "context" + "errors" "fmt" "sort" "strings" @@ -26,6 +27,7 @@ import ( log "github.com/sirupsen/logrus" networkv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" kubeinformers "k8s.io/client-go/informers" netinformers "k8s.io/client-go/informers/networking/v1" "k8s.io/client-go/kubernetes" @@ -43,6 +45,8 @@ const ( // Possible values for the ingress-hostname-source annotation IngressHostnameSourceAnnotationOnlyValue = "annotation-only" IngressHostnameSourceDefinedHostsOnlyValue = "defined-hosts-only" + + IngressClassAnnotationKey = "kubernetes.io/ingress.class" ) // ingressSource is an implementation of Source for Kubernetes ingress objects. @@ -53,6 +57,7 @@ type ingressSource struct { client kubernetes.Interface namespace string annotationFilter string + ingressClassNames []string fqdnTemplate *template.Template combineFQDNAnnotation bool ignoreHostnameAnnotation bool @@ -63,12 +68,27 @@ type ingressSource struct { } // NewIngressSource creates a new ingressSource with the given config. -func NewIngressSource(ctx context.Context, kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool, labelSelector labels.Selector) (Source, error) { +func NewIngressSource(ctx context.Context, kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool, labelSelector labels.Selector, ingressClassNames []string) (Source, error) { tmpl, err := parseTemplate(fqdnTemplate) if err != nil { return nil, err } + // ensure that ingress class is only set in either the ingressClassNames or + // annotationFilter but not both + if ingressClassNames != nil && annotationFilter != "" { + selector, err := getLabelSelector(annotationFilter) + if err != nil { + return nil, err + } + + requirements, _ := selector.Requirements() + for _, requirement := range requirements { + if requirement.Key() == "kubernetes.io/ingress.class" { + return nil, errors.New("--ingress-class is mutually exclusive with the kubernetes.io/ingress.class annotation filter") + } + } + } // Use shared informer to listen for add/update/delete of ingresses in the specified namespace. // Set resync period to 0, to prevent processing when nothing has changed. informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace)) @@ -93,6 +113,7 @@ func NewIngressSource(ctx context.Context, kubeClient kubernetes.Interface, name client: kubeClient, namespace: namespace, annotationFilter: annotationFilter, + ingressClassNames: ingressClassNames, fqdnTemplate: tmpl, combineFQDNAnnotation: combineFqdnAnnotation, ignoreHostnameAnnotation: ignoreHostnameAnnotation, @@ -116,6 +137,11 @@ func (sc *ingressSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e return nil, err } + ingresses, err = sc.filterByIngressClass(ingresses) + if err != nil { + return nil, err + } + endpoints := []*endpoint.Endpoint{} for _, ing := range ingresses { @@ -210,6 +236,50 @@ func (sc *ingressSource) filterByAnnotations(ingresses []*networkv1.Ingress) ([] return filteredList, nil } +// filterByIngressClass filters a list of ingresses based on a required ingress +// class +func (sc *ingressSource) filterByIngressClass(ingresses []*networkv1.Ingress) ([]*networkv1.Ingress, error) { + // if no class filter is specified then there's nothing to do + if len(sc.ingressClassNames) == 0 { + return ingresses, nil + } + + classNameReq, err := labels.NewRequirement(IngressClassAnnotationKey, selection.In, sc.ingressClassNames) + if err != nil { + return nil, err + } + + selector := labels.NewSelector() + selector = selector.Add(*classNameReq) + + filteredList := []*networkv1.Ingress{} + + for _, ingress := range ingresses { + var matched = false + + for _, nameFilter := range sc.ingressClassNames { + if ingress.Spec.IngressClassName != nil && len(*ingress.Spec.IngressClassName) > 0 { + if nameFilter == *ingress.Spec.IngressClassName { + matched = true + } + } else if matchLabelSelector(selector, ingress.Annotations) { + matched = true + } + + if matched { + filteredList = append(filteredList, ingress) + break + } + } + + if !matched { + log.Debugf("Discarding ingress %s/%s because it does not match required ingress classes %v", ingress.Namespace, ingress.Name, sc.ingressClassNames) + } + } + + return filteredList, nil +} + func (sc *ingressSource) setResourceLabel(ingress *networkv1.Ingress, endpoints []*endpoint.Endpoint) { for _, ep := range endpoints { ep.Labels[endpoint.ResourceLabelKey] = fmt.Sprintf("ingress/%s/%s", ingress.Namespace, ingress.Name) diff --git a/source/ingress_test.go b/source/ingress_test.go index cdd23505a7..2be3eddfd0 100644 --- a/source/ingress_test.go +++ b/source/ingress_test.go @@ -65,6 +65,7 @@ func (suite *IngressSuite) SetupTest() { false, false, labels.Everything(), + []string{}, ) suite.NoError(err, "should initialize ingress source") } @@ -101,6 +102,7 @@ func TestNewIngressSource(t *testing.T) { fqdnTemplate string combineFQDNAndAnnotation bool expectError bool + ingressClassNames []string }{ { title: "invalid template", @@ -132,6 +134,17 @@ func TestNewIngressSource(t *testing.T) { expectError: false, annotationFilter: "kubernetes.io/ingress.class=nginx", }, + { + title: "non-empty ingress class name list", + expectError: false, + ingressClassNames: []string{"internal", "external"}, + }, + { + title: "ingress class name and annotation filter jointly specified", + expectError: true, + ingressClassNames: []string{"internal", "external"}, + annotationFilter: "kubernetes.io/ingress.class=nginx", + }, } { ti := ti t.Run(ti.title, func(t *testing.T) { @@ -148,6 +161,7 @@ func TestNewIngressSource(t *testing.T) { false, false, labels.Everything(), + ti.ingressClassNames, ) if ti.expectError { assert.Error(t, err) @@ -374,6 +388,7 @@ func testIngressEndpoints(t *testing.T) { ignoreIngressTLSSpec bool ignoreIngressRulesSpec bool ingressLabelSelector labels.Selector + ingressClassNames []string }{ { title: "no ingress", @@ -1220,6 +1235,116 @@ func testIngressEndpoints(t *testing.T) { }, }, }, + { + title: "ingressClassName filtering", + targetNamespace: "", + ingressClassNames: []string{"public", "dmz"}, + ingressItems: []fakeIngress{ + { + name: "none", + namespace: namespace, + tlsdnsnames: [][]string{{"none.example.org"}}, + ips: []string{"1.0.0.0"}, + }, + { + name: "fake-public", + namespace: namespace, + tlsdnsnames: [][]string{{"example.org"}}, + ips: []string{"1.2.3.4"}, + ingressClassName: "public", // match + }, + { + name: "fake-internal", + namespace: namespace, + tlsdnsnames: [][]string{{"int.example.org"}}, + ips: []string{"2.3.4.5"}, + ingressClassName: "internal", + }, + { + name: "fake-dmz", + namespace: namespace, + tlsdnsnames: [][]string{{"dmz.example.org"}}, + ips: []string{"3.4.5.6"}, + ingressClassName: "dmz", // match + }, + { + name: "annotated-dmz", + namespace: namespace, + tlsdnsnames: [][]string{{"annodmz.example.org"}}, + ips: []string{"4.5.6.7"}, + annotations: map[string]string{ + "kubernetes.io/ingress.class": "dmz", // match + }, + }, + { + name: "fake-internal-annotated-dmz", + namespace: namespace, + tlsdnsnames: [][]string{{"int-annodmz.example.org"}}, + ips: []string{"5.6.7.8"}, + annotations: map[string]string{ + "kubernetes.io/ingress.class": "dmz", // match but ignored (non-empty ingressClassName) + }, + ingressClassName: "internal", + }, + { + name: "fake-dmz-annotated-internal", + namespace: namespace, + tlsdnsnames: [][]string{{"dmz-annoint.example.org"}}, + ips: []string{"6.7.8.9"}, + annotations: map[string]string{ + "kubernetes.io/ingress.class": "internal", + }, + ingressClassName: "dmz", // match + }, + { + name: "empty-annotated-dmz", + namespace: namespace, + tlsdnsnames: [][]string{{"empty-annotdmz.example.org"}}, + ips: []string{"7.8.9.0"}, + annotations: map[string]string{ + "kubernetes.io/ingress.class": "dmz", // match (empty ingressClassName) + }, + ingressClassName: "", + }, + { + name: "empty-annotated-internal", + namespace: namespace, + tlsdnsnames: [][]string{{"empty-annotint.example.org"}}, + ips: []string{"8.9.0.1"}, + annotations: map[string]string{ + "kubernetes.io/ingress.class": "internal", + }, + ingressClassName: "", + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "example.org", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.2.3.4"}, + }, + { + DNSName: "dmz.example.org", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"3.4.5.6"}, + }, + { + DNSName: "annodmz.example.org", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"4.5.6.7"}, + }, + { + DNSName: "dmz-annoint.example.org", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"6.7.8.9"}, + }, + { + DNSName: "empty-annotdmz.example.org", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"7.8.9.0"}, + }, + }, + }, { ingressLabelSelector: labels.SelectorFromSet(labels.Set{"app": "web-external"}), title: "ingress with matching labels", @@ -1283,6 +1408,7 @@ func testIngressEndpoints(t *testing.T) { ti.ignoreIngressTLSSpec, ti.ignoreIngressRulesSpec, ti.ingressLabelSelector, + ti.ingressClassNames, ) // Informer cache has all of the ingresses. Retrieve and validate their endpoints. res, err := source.Endpoints(context.Background()) @@ -1298,14 +1424,15 @@ func testIngressEndpoints(t *testing.T) { // ingress specific helper functions type fakeIngress struct { - dnsnames []string - tlsdnsnames [][]string - ips []string - hostnames []string - namespace string - name string - annotations map[string]string - labels map[string]string + dnsnames []string + tlsdnsnames [][]string + ips []string + hostnames []string + namespace string + name string + annotations map[string]string + labels map[string]string + ingressClassName string } func (ing fakeIngress) Ingress() *networkv1.Ingress { @@ -1317,7 +1444,8 @@ func (ing fakeIngress) Ingress() *networkv1.Ingress { Labels: ing.labels, }, Spec: networkv1.IngressSpec{ - Rules: []networkv1.IngressRule{}, + Rules: []networkv1.IngressRule{}, + IngressClassName: &ing.ingressClassName, }, Status: networkv1.IngressStatus{ LoadBalancer: networkv1.IngressLoadBalancerStatus{ diff --git a/source/store.go b/source/store.go index d22e16ff87..ef4718e3b2 100644 --- a/source/store.go +++ b/source/store.go @@ -46,6 +46,7 @@ type Config struct { Namespace string AnnotationFilter string LabelFilter labels.Selector + IngressClassNames []string FQDNTemplate string CombineFQDNAndAnnotation bool IgnoreHostnameAnnotation bool @@ -222,7 +223,7 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg if err != nil { return nil, err } - return NewIngressSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter) + return NewIngressSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter, cfg.IngressClassNames) case "pod": client, err := p.KubeClient() if err != nil {