diff --git a/main.go b/main.go index 547bdc0a5d..603a3ad5e2 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,7 @@ func main() { AnnotationFilter: cfg.AnnotationFilter, FQDNTemplate: cfg.FQDNTemplate, CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation, + IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation, Compatibility: cfg.Compatibility, PublishInternal: cfg.PublishInternal, PublishHostIP: cfg.PublishHostIP, diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index a7b82b2246..ecbcd480c4 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -45,6 +45,7 @@ type Config struct { AnnotationFilter string FQDNTemplate string CombineFQDNAndAnnotation bool + IgnoreHostnameAnnotation bool Compatibility string PublishInternal bool PublishHostIP bool @@ -121,6 +122,7 @@ var defaultConfig = &Config{ AnnotationFilter: "", FQDNTemplate: "", CombineFQDNAndAnnotation: false, + IgnoreHostnameAnnotation: false, Compatibility: "", PublishInternal: false, PublishHostIP: false, @@ -231,6 +233,7 @@ func (cfg *Config) ParseFlags(args []string) error { 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). 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) 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) app.Flag("publish-host-ip", "Allow external-dns to publish host-ip for headless services (optional)").BoolVar(&cfg.PublishHostIP) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 143ddb75bc..394e666c58 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -83,61 +83,62 @@ var ( } overriddenConfig = &Config{ - Master: "http://127.0.0.1:8080", - KubeConfig: "/some/path", - RequestTimeout: time.Second * 77, - IstioIngressGateway: "istio-other/istio-otheringressgateway", - Sources: []string{"service", "ingress", "connector"}, - Namespace: "namespace", - FQDNTemplate: "{{.Name}}.service.example.com", - Compatibility: "mate", - Provider: "google", - GoogleProject: "project", - DomainFilter: []string{"example.org", "company.com"}, - ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"}, - AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", - AWSZoneType: "private", - AWSZoneTagFilter: []string{"tag=foo"}, - AWSAssumeRole: "some-other-role", - AWSBatchChangeSize: 100, - AWSBatchChangeInterval: time.Second * 2, - AWSEvaluateTargetHealth: false, - AWSAPIRetries: 13, - AzureConfigFile: "azure.json", - AzureResourceGroup: "arg", - CloudflareProxied: true, - CloudflareZonesPerPage: 20, - InfobloxGridHost: "127.0.0.1", - InfobloxWapiPort: 8443, - InfobloxWapiUsername: "infoblox", - InfobloxWapiPassword: "infoblox", - InfobloxWapiVersion: "2.6.1", - InfobloxSSLVerify: false, - OCIConfigFile: "oci.yaml", - InMemoryZones: []string{"example.org", "company.com"}, - PDNSServer: "http://ns.example.com:8081", - PDNSAPIKey: "some-secret-key", - PDNSTLSEnabled: true, - TLSCA: "/path/to/ca.crt", - TLSClientCert: "/path/to/cert.pem", - TLSClientCertKey: "/path/to/key.pem", - Policy: "upsert-only", - Registry: "noop", - TXTOwnerID: "owner-1", - TXTPrefix: "associated-txt-record", - TXTCacheInterval: 12 * time.Hour, - Interval: 10 * time.Minute, - Once: true, - DryRun: true, - LogFormat: "json", - MetricsAddress: "127.0.0.1:9099", - LogLevel: logrus.DebugLevel.String(), - ConnectorSourceServer: "localhost:8081", - ExoscaleEndpoint: "https://api.foo.ch/dns", - ExoscaleAPIKey: "1", - ExoscaleAPISecret: "2", - CRDSourceAPIVersion: "test.k8s.io/v1alpha1", - CRDSourceKind: "Endpoint", + Master: "http://127.0.0.1:8080", + KubeConfig: "/some/path", + RequestTimeout: time.Second * 77, + IstioIngressGateway: "istio-other/istio-otheringressgateway", + Sources: []string{"service", "ingress", "connector"}, + Namespace: "namespace", + IgnoreHostnameAnnotation: true, + FQDNTemplate: "{{.Name}}.service.example.com", + Compatibility: "mate", + Provider: "google", + GoogleProject: "project", + DomainFilter: []string{"example.org", "company.com"}, + ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"}, + AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", + AWSZoneType: "private", + AWSZoneTagFilter: []string{"tag=foo"}, + AWSAssumeRole: "some-other-role", + AWSBatchChangeSize: 100, + AWSBatchChangeInterval: time.Second * 2, + AWSEvaluateTargetHealth: false, + AWSAPIRetries: 13, + AzureConfigFile: "azure.json", + AzureResourceGroup: "arg", + CloudflareProxied: true, + CloudflareZonesPerPage: 20, + InfobloxGridHost: "127.0.0.1", + InfobloxWapiPort: 8443, + InfobloxWapiUsername: "infoblox", + InfobloxWapiPassword: "infoblox", + InfobloxWapiVersion: "2.6.1", + InfobloxSSLVerify: false, + OCIConfigFile: "oci.yaml", + InMemoryZones: []string{"example.org", "company.com"}, + PDNSServer: "http://ns.example.com:8081", + PDNSAPIKey: "some-secret-key", + PDNSTLSEnabled: true, + TLSCA: "/path/to/ca.crt", + TLSClientCert: "/path/to/cert.pem", + TLSClientCertKey: "/path/to/key.pem", + Policy: "upsert-only", + Registry: "noop", + TXTOwnerID: "owner-1", + TXTPrefix: "associated-txt-record", + TXTCacheInterval: 12 * time.Hour, + Interval: 10 * time.Minute, + Once: true, + DryRun: true, + LogFormat: "json", + MetricsAddress: "127.0.0.1:9099", + LogLevel: logrus.DebugLevel.String(), + ConnectorSourceServer: "localhost:8081", + ExoscaleEndpoint: "https://api.foo.ch/dns", + ExoscaleAPIKey: "1", + ExoscaleAPISecret: "2", + CRDSourceAPIVersion: "test.k8s.io/v1alpha1", + CRDSourceKind: "Endpoint", } ) @@ -169,6 +170,7 @@ func TestParseFlags(t *testing.T) { "--source=connector", "--namespace=namespace", "--fqdn-template={{.Name}}.service.example.com", + "--ignore-hostname-annotation", "--compatibility=mate", "--provider=google", "--google-project=project", @@ -234,6 +236,7 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_SOURCE": "service\ningress\nconnector", "EXTERNAL_DNS_NAMESPACE": "namespace", "EXTERNAL_DNS_FQDN_TEMPLATE": "{{.Name}}.service.example.com", + "EXTERNAL_DNS_IGNORE_HOSTNAME_ANNOTATION": "1", "EXTERNAL_DNS_COMPATIBILITY": "mate", "EXTERNAL_DNS_PROVIDER": "google", "EXTERNAL_DNS_GOOGLE_PROJECT": "project", diff --git a/pkg/apis/externaldns/validation/validation.go b/pkg/apis/externaldns/validation/validation.go index cdbe6ef4e4..174550cb47 100644 --- a/pkg/apis/externaldns/validation/validation.go +++ b/pkg/apis/externaldns/validation/validation.go @@ -65,5 +65,9 @@ func ValidateConfig(cfg *externaldns.Config) error { return errors.New("TTL specified for Dyn is negative") } } + + if cfg.IgnoreHostnameAnnotation && cfg.FQDNTemplate == "" { + return errors.New("FQDN Template must be set if ignoring annotations") + } return nil } diff --git a/pkg/apis/externaldns/validation/validation_test.go b/pkg/apis/externaldns/validation/validation_test.go index c50073ae4e..da3d9e300c 100644 --- a/pkg/apis/externaldns/validation/validation_test.go +++ b/pkg/apis/externaldns/validation/validation_test.go @@ -116,3 +116,11 @@ func TestValidateGoodDynConfig(t *testing.T) { assert.Nil(t, err, "Configuration should be valid, got this error instead", err) } } + +func TestValidateBadIgnoreHostnameAnnotationsConfig(t *testing.T) { + cfg := externaldns.NewConfig() + cfg.IgnoreHostnameAnnotation = true + cfg.FQDNTemplate = "" + + assert.Error(t, ValidateConfig(cfg)) +} diff --git a/source/gateway.go b/source/gateway.go index 30aa53b10e..9d34c16ca8 100644 --- a/source/gateway.go +++ b/source/gateway.go @@ -38,14 +38,15 @@ import ( // The gateway implementation uses the spec.servers.hosts values for the hostnames. // Use targetAnnotationKey to explicitly set Endpoint. type gatewaySource struct { - kubeClient kubernetes.Interface - istioClient istiomodel.ConfigStore - istioNamespace string - istioIngressGatewayName string - namespace string - annotationFilter string - fqdnTemplate *template.Template - combineFQDNAnnotation bool + kubeClient kubernetes.Interface + istioClient istiomodel.ConfigStore + istioNamespace string + istioIngressGatewayName string + namespace string + annotationFilter string + fqdnTemplate *template.Template + combineFQDNAnnotation bool + ignoreHostnameAnnotation bool } // NewIstioGatewaySource creates a new gatewaySource with the given config. @@ -57,6 +58,7 @@ func NewIstioGatewaySource( annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, + ignoreHostnameAnnotation bool, ) (Source, error) { var ( tmpl *template.Template @@ -77,14 +79,15 @@ func NewIstioGatewaySource( } return &gatewaySource{ - kubeClient: kubeClient, - istioClient: istioClient, - istioNamespace: istioNamespace, - istioIngressGatewayName: istioIngressGatewayName, - namespace: namespace, - annotationFilter: annotationFilter, - fqdnTemplate: tmpl, - combineFQDNAnnotation: combineFqdnAnnotation, + kubeClient: kubeClient, + istioClient: istioClient, + istioNamespace: istioNamespace, + istioIngressGatewayName: istioIngressGatewayName, + namespace: namespace, + annotationFilter: annotationFilter, + fqdnTemplate: tmpl, + combineFQDNAnnotation: combineFqdnAnnotation, + ignoreHostnameAnnotation: ignoreHostnameAnnotation, }, nil } @@ -269,9 +272,12 @@ func (sc *gatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([ } } - hostnameList := getHostnamesFromAnnotations(config.Annotations) - for _, hostname := range hostnameList { - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) + // Skip endpoints if we do not want entries from annotations + if !sc.ignoreHostnameAnnotation { + hostnameList := getHostnamesFromAnnotations(config.Annotations) + for _, hostname := range hostnameList { + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) + } } return endpoints, nil diff --git a/source/gateway_test.go b/source/gateway_test.go index 1456942938..c12a8389b1 100644 --- a/source/gateway_test.go +++ b/source/gateway_test.go @@ -68,6 +68,7 @@ func (suite *GatewaySuite) SetupTest() { "", "{{.Name}}", false, + false, ) suite.NoError(err, "should initialize gateway source") @@ -141,6 +142,7 @@ func TestNewIstioGatewaySource(t *testing.T) { ti.annotationFilter, ti.fqdnTemplate, ti.combineFQDNAndAnnotation, + false, ) if ti.expectError { assert.Error(t, err) @@ -273,6 +275,7 @@ func testGatewayEndpoints(t *testing.T) { expectError bool fqdnTemplate string combineFQDNAndAnnotation bool + ignoreHostnameAnnotation bool }{ { title: "no gateway", @@ -913,6 +916,51 @@ func testGatewayEndpoints(t *testing.T) { expected: []*endpoint.Endpoint{}, fqdnTemplate: "{{.Name}}.ext-dns.test.com", }, + { + title: "ignore hostname annotations", + ignoreHostnameAnnotation: true, + targetNamespace: "", + ingressGateway: fakeIngressGateway{ + ips: []string{"8.8.8.8"}, + hostnames: []string{"lb.com"}, + }, + configItems: []fakeGatewayConfig{ + { + name: "fake1", + namespace: namespace, + annotations: map[string]string{ + hostnameAnnotationKey: "ignore.me", + }, + dnsnames: [][]string{{"example.org"}}, + }, + { + name: "fake2", + namespace: namespace, + annotations: map[string]string{ + hostnameAnnotationKey: "ignore.me.too", + }, + dnsnames: [][]string{{"new.org"}}, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "example.org", + Targets: endpoint.Targets{"8.8.8.8"}, + }, + { + DNSName: "example.org", + Targets: endpoint.Targets{"lb.com"}, + }, + { + DNSName: "new.org", + Targets: endpoint.Targets{"8.8.8.8"}, + }, + { + DNSName: "new.org", + Targets: endpoint.Targets{"lb.com"}, + }, + }, + }, } { t.Run(ti.title, func(t *testing.T) { configs := make([]istiomodel.Config, 0) @@ -939,6 +987,7 @@ func testGatewayEndpoints(t *testing.T) { ti.annotationFilter, ti.fqdnTemplate, ti.combineFQDNAndAnnotation, + ti.ignoreHostnameAnnotation, ) require.NoError(t, err) @@ -972,6 +1021,7 @@ func newTestGatewaySource(ingress *v1.Service) (*gatewaySource, error) { "", "{{.Name}}", false, + false, ) if err != nil { return nil, err diff --git a/source/ingress.go b/source/ingress.go index 056b8dd6fb..f522b52424 100644 --- a/source/ingress.go +++ b/source/ingress.go @@ -38,15 +38,16 @@ 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 - annotationFilter string - fqdnTemplate *template.Template - combineFQDNAnnotation bool + client kubernetes.Interface + namespace string + annotationFilter string + fqdnTemplate *template.Template + combineFQDNAnnotation bool + ignoreHostnameAnnotation bool } // NewIngressSource creates a new ingressSource with the given config. -func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool) (Source, error) { +func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool) (Source, error) { var ( tmpl *template.Template err error @@ -61,11 +62,12 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilt } return &ingressSource{ - client: kubeClient, - namespace: namespace, - annotationFilter: annotationFilter, - fqdnTemplate: tmpl, - combineFQDNAnnotation: combineFqdnAnnotation, + client: kubeClient, + namespace: namespace, + annotationFilter: annotationFilter, + fqdnTemplate: tmpl, + combineFQDNAnnotation: combineFqdnAnnotation, + ignoreHostnameAnnotation: ignoreHostnameAnnotation, }, nil } @@ -92,7 +94,7 @@ func (sc *ingressSource) Endpoints() ([]*endpoint.Endpoint, error) { continue } - ingEndpoints := endpointsFromIngress(&ing) + ingEndpoints := endpointsFromIngress(&ing, sc.ignoreHostnameAnnotation) // apply template if host is missing on ingress if (sc.combineFQDNAnnotation || len(ingEndpoints) == 0) && sc.fqdnTemplate != nil { @@ -196,7 +198,7 @@ func (sc *ingressSource) setResourceLabel(ingress v1beta1.Ingress, endpoints []* } // endpointsFromIngress extracts the endpoints from ingress object -func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint { +func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool) []*endpoint.Endpoint { var endpoints []*endpoint.Endpoint ttl, err := getTTLFromAnnotations(ing.Annotations) @@ -228,9 +230,12 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint { } } - hostnameList := getHostnamesFromAnnotations(ing.Annotations) - for _, hostname := range hostnameList { - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) + // Skip endpoints if we do not want entries from annotations + if !ignoreHostnameAnnotation { + hostnameList := getHostnamesFromAnnotations(ing.Annotations) + for _, hostname := range hostnameList { + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) + } } return endpoints } diff --git a/source/ingress_test.go b/source/ingress_test.go index 7d0182653d..2f29f80200 100644 --- a/source/ingress_test.go +++ b/source/ingress_test.go @@ -50,6 +50,7 @@ func (suite *IngressSuite) SetupTest() { "", "{{.Name}}", false, + false, ) suite.NoError(err, "should initialize ingress source") @@ -123,6 +124,7 @@ func TestNewIngressSource(t *testing.T) { ti.annotationFilter, ti.fqdnTemplate, ti.combineFQDNAndAnnotation, + false, ) if ti.expectError { assert.Error(t, err) @@ -210,7 +212,7 @@ func testEndpointsFromIngress(t *testing.T) { } { t.Run(ti.title, func(t *testing.T) { realIngress := ti.ingress.Ingress() - validateEndpoints(t, endpointsFromIngress(realIngress), ti.expected) + validateEndpoints(t, endpointsFromIngress(realIngress, false), ti.expected) }) } } @@ -226,6 +228,7 @@ func testIngressEndpoints(t *testing.T) { expectError bool fqdnTemplate string combineFQDNAndAnnotation bool + ignoreHostnameAnnotation bool }{ { title: "no ingress", @@ -934,6 +937,38 @@ func testIngressEndpoints(t *testing.T) { expected: []*endpoint.Endpoint{}, fqdnTemplate: "{{.Name}}.ext-dns.test.com", }, + { + title: "ignore hostname annotation", + targetNamespace: "", + ignoreHostnameAnnotation: true, + ingressItems: []fakeIngress{ + { + name: "fake1", + namespace: namespace, + dnsnames: []string{"example.org"}, + ips: []string{"8.8.8.8"}, + }, + { + name: "fake2", + namespace: namespace, + annotations: map[string]string{ + hostnameAnnotationKey: "dns-through-hostname.com", + }, + dnsnames: []string{"new.org"}, + hostnames: []string{"lb.com"}, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "example.org", + Targets: endpoint.Targets{"8.8.8.8"}, + }, + { + DNSName: "new.org", + Targets: endpoint.Targets{"lb.com"}, + }, + }, + }, } { t.Run(ti.title, func(t *testing.T) { ingresses := make([]*v1beta1.Ingress, 0) @@ -948,6 +983,7 @@ func testIngressEndpoints(t *testing.T) { ti.annotationFilter, ti.fqdnTemplate, ti.combineFQDNAndAnnotation, + ti.ignoreHostnameAnnotation, ) for _, ingress := range ingresses { _, err := fakeClient.Extensions().Ingresses(ingress.Namespace).Create(ingress) diff --git a/source/service.go b/source/service.go index e88e31d8e5..67b12c69d9 100644 --- a/source/service.go +++ b/source/service.go @@ -25,7 +25,7 @@ import ( log "github.com/sirupsen/logrus" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -48,16 +48,17 @@ type serviceSource struct { namespace string annotationFilter string // process Services with legacy annotations - compatibility string - fqdnTemplate *template.Template - combineFQDNAnnotation bool - publishInternal bool - publishHostIP bool - serviceTypeFilter map[string]struct{} + compatibility string + fqdnTemplate *template.Template + combineFQDNAnnotation bool + ignoreHostnameAnnotation 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, serviceTypeFilter []string) (Source, error) { +func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool) (Source, error) { var ( tmpl *template.Template err error @@ -79,15 +80,16 @@ func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilt } return &serviceSource{ - client: kubeClient, - namespace: namespace, - annotationFilter: annotationFilter, - compatibility: compatibility, - fqdnTemplate: tmpl, - combineFQDNAnnotation: combineFqdnAnnotation, - publishInternal: publishInternal, - publishHostIP: publishHostIP, - serviceTypeFilter: serviceTypes, + client: kubeClient, + namespace: namespace, + annotationFilter: annotationFilter, + compatibility: compatibility, + fqdnTemplate: tmpl, + combineFQDNAnnotation: combineFqdnAnnotation, + ignoreHostnameAnnotation: ignoreHostnameAnnotation, + publishInternal: publishInternal, + publishHostIP: publishHostIP, + serviceTypeFilter: serviceTypes, }, nil } @@ -237,13 +239,14 @@ func (sc *serviceSource) endpointsFromTemplate(svc *v1.Service, nodeTargets endp // endpointsFromService extracts the endpoints from a service object func (sc *serviceSource) endpoints(svc *v1.Service, nodeTargets endpoint.Targets) []*endpoint.Endpoint { var endpoints []*endpoint.Endpoint - - providerSpecific := getProviderSpecificAnnotations(svc.Annotations) - hostnameList := getHostnamesFromAnnotations(svc.Annotations) - for _, hostname := range hostnameList { - endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, nodeTargets, providerSpecific)...) + // Skip endpoints if we do not want entries from annotations + if !sc.ignoreHostnameAnnotation { + providerSpecific := getProviderSpecificAnnotations(svc.Annotations) + hostnameList := getHostnamesFromAnnotations(svc.Annotations) + for _, hostname := range hostnameList { + endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, nodeTargets, providerSpecific)...) + } } - return endpoints } diff --git a/source/service_test.go b/source/service_test.go index 01fef8b931..acd6a2b96d 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -20,7 +20,7 @@ import ( "net" "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" @@ -51,6 +51,7 @@ func (suite *ServiceSuite) SetupTest() { false, false, []string{}, + false, ) suite.fooWithTargets = &v1.Service{ Spec: v1.ServiceSpec{ @@ -142,6 +143,7 @@ func testServiceSourceNewServiceSource(t *testing.T) { false, false, ti.serviceTypesFilter, + false, ) if ti.expectError { @@ -165,6 +167,7 @@ func testServiceSourceEndpoints(t *testing.T) { compatibility string fqdnTemplate string combineFQDNAndAnnotation bool + ignoreHostnameAnnotation bool labels map[string]string annotations map[string]string clusterIP string @@ -183,6 +186,26 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, + map[string]string{}, + map[string]string{}, + "", + []string{"1.2.3.4"}, + []string{}, + []*endpoint.Endpoint{}, + false, + }, + { + "no annotated services return no endpoints when ignoreing annotations", + "", + "", + "testing", + "foo", + v1.ServiceTypeLoadBalancer, + "", + "", + false, + true, map[string]string{}, map[string]string{}, "", @@ -201,6 +224,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -213,6 +237,27 @@ func testServiceSourceEndpoints(t *testing.T) { }, false, }, + { + "hostname annotation on services is ignored", + "", + "", + "testing", + "foo", + v1.ServiceTypeLoadBalancer, + "", + "", + false, + true, + map[string]string{}, + map[string]string{ + hostnameAnnotationKey: "foo.example.org.", + }, + "", + []string{"1.2.3.4"}, + []string{}, + []*endpoint.Endpoint{}, + false, + }, { "annotated ClusterIp aren't processed without explicit authorization", "", @@ -223,6 +268,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -243,6 +289,29 @@ func testServiceSourceEndpoints(t *testing.T) { "", "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", false, + false, + map[string]string{}, + 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"}}, + }, + false, + }, + { + "FQDN template with multiple hostnames return an endpoint with target IP when ignoreing annotations", + "", + "", + "testing", + "foo", + v1.ServiceTypeLoadBalancer, + "", + "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + false, + true, map[string]string{}, map[string]string{}, "", @@ -264,6 +333,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", true, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org., bar.example.org.", @@ -279,6 +349,30 @@ func testServiceSourceEndpoints(t *testing.T) { }, false, }, + { + "FQDN template and annotation both with multiple hostnames while ignoring annotations will only return FQDN endpoints", + "", + "", + "testing", + "foo", + v1.ServiceTypeLoadBalancer, + "", + "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + true, + true, + map[string]string{}, + map[string]string{ + hostnameAnnotationKey: "foo.example.org., bar.example.org.", + }, + "", + []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"}}, + }, + false, + }, { "annotated services with multiple hostnames return an endpoint with target IP", "", @@ -289,6 +383,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org., bar.example.org.", @@ -312,6 +407,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org, bar.example.org", @@ -335,6 +431,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -357,6 +454,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted @@ -380,6 +478,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ controllerAnnotationKey: controllerAnnotationValue, @@ -403,6 +502,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "{{.Name}}.ext-dns.test.com", false, + false, map[string]string{}, map[string]string{ controllerAnnotationKey: "some-other-tool", @@ -424,6 +524,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -446,6 +547,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -466,6 +568,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -488,6 +591,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -511,6 +615,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -532,6 +637,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -553,6 +659,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -576,6 +683,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -597,6 +705,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -617,6 +726,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -639,6 +749,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ "zalando.org/dnsname": "foo.example.org.", @@ -659,6 +770,7 @@ func testServiceSourceEndpoints(t *testing.T) { "mate", "", false, + false, map[string]string{}, map[string]string{ "zalando.org/dnsname": "foo.example.org.", @@ -681,6 +793,7 @@ func testServiceSourceEndpoints(t *testing.T) { "molecule", "", false, + false, map[string]string{ "dns": "route53", }, @@ -706,6 +819,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "{{.Name}}.bar.example.com", false, + false, map[string]string{}, map[string]string{}, "", @@ -727,6 +841,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "{{.Name}}.bar.example.com", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -750,6 +865,7 @@ func testServiceSourceEndpoints(t *testing.T) { "mate", "{{.Name}}.bar.example.com", false, + false, map[string]string{}, map[string]string{ "zalando.org/dnsname": "mate.example.org.", @@ -772,6 +888,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "{{.Calibre}}.bar.example.com", false, + false, map[string]string{}, map[string]string{}, "", @@ -790,6 +907,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -812,6 +930,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -835,6 +954,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -858,6 +978,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -881,6 +1002,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -903,6 +1025,7 @@ func testServiceSourceEndpoints(t *testing.T) { "", "", false, + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -960,6 +1083,7 @@ func testServiceSourceEndpoints(t *testing.T) { false, false, tc.serviceTypesFilter, + tc.ignoreHostnameAnnotation, ) require.NoError(t, err) @@ -979,20 +1103,21 @@ func testServiceSourceEndpoints(t *testing.T) { // testServiceSourceEndpoints tests that various services generate the correct endpoints. func TestClusterIpServices(t *testing.T) { for _, tc := range []struct { - title string - targetNamespace string - annotationFilter string - svcNamespace string - svcName string - svcType v1.ServiceType - compatibility string - fqdnTemplate string - labels map[string]string - annotations map[string]string - clusterIP string - lbs []string - expected []*endpoint.Endpoint - expectError bool + title string + targetNamespace string + annotationFilter string + svcNamespace string + svcName string + svcType v1.ServiceType + compatibility string + fqdnTemplate string + ignoreHostnameAnnotation bool + labels map[string]string + annotations map[string]string + clusterIP string + lbs []string + expected []*endpoint.Endpoint + expectError bool }{ { "annotated ClusterIp services return an endpoint with Cluster IP", @@ -1003,6 +1128,7 @@ func TestClusterIpServices(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -1014,6 +1140,25 @@ func TestClusterIpServices(t *testing.T) { }, false, }, + { + "hostname annotated ClusterIp services are ignored", + "", + "", + "testing", + "foo", + v1.ServiceTypeClusterIP, + "", + "", + true, + map[string]string{}, + map[string]string{ + hostnameAnnotationKey: "foo.example.org.", + }, + "1.2.3.4", + []string{}, + []*endpoint.Endpoint{}, + false, + }, { "non-annotated ClusterIp services with set fqdnTemplate return an endpoint with target IP", "", @@ -1023,6 +1168,7 @@ func TestClusterIpServices(t *testing.T) { v1.ServiceTypeClusterIP, "", "{{.Name}}.bar.example.com", + false, map[string]string{}, map[string]string{}, "4.5.6.7", @@ -1041,6 +1187,7 @@ func TestClusterIpServices(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{}, map[string]string{}, v1.ClusterIPNone, @@ -1095,6 +1242,7 @@ func TestClusterIpServices(t *testing.T) { true, false, []string{}, + tc.ignoreHostnameAnnotation, ) require.NoError(t, err) @@ -1114,20 +1262,21 @@ func TestClusterIpServices(t *testing.T) { // testNodePortServices tests that various services generate the correct endpoints. func TestNodePortServices(t *testing.T) { for _, tc := range []struct { - title string - targetNamespace string - annotationFilter string - svcNamespace string - svcName string - svcType v1.ServiceType - compatibility string - fqdnTemplate string - labels map[string]string - annotations map[string]string - lbs []string - expected []*endpoint.Endpoint - expectError bool - nodes []*v1.Node + title string + targetNamespace string + annotationFilter string + svcNamespace string + svcName string + svcType v1.ServiceType + compatibility string + fqdnTemplate string + ignoreHostnameAnnotation bool + labels map[string]string + annotations map[string]string + lbs []string + expected []*endpoint.Endpoint + expectError bool + nodes []*v1.Node }{ { "annotated NodePort services return an endpoint with IP addresses of the cluster's nodes", @@ -1138,6 +1287,7 @@ func TestNodePortServices(t *testing.T) { v1.ServiceTypeNodePort, "", "", + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -1170,6 +1320,45 @@ func TestNodePortServices(t *testing.T) { }, }}, }, + { + "hostname annotated NodePort services are ignored", + "", + "", + "testing", + "foo", + v1.ServiceTypeNodePort, + "", + "", + true, + map[string]string{}, + map[string]string{ + hostnameAnnotationKey: "foo.example.org.", + }, + nil, + []*endpoint.Endpoint{}, + false, + []*v1.Node{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, + {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + }, + }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, + {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + }, + }, + }}, + }, { "non-annotated NodePort services with set fqdnTemplate return an endpoint with target IP", "", @@ -1179,6 +1368,7 @@ func TestNodePortServices(t *testing.T) { v1.ServiceTypeNodePort, "", "{{.Name}}.bar.example.com", + false, map[string]string{}, map[string]string{}, nil, @@ -1218,6 +1408,7 @@ func TestNodePortServices(t *testing.T) { v1.ServiceTypeNodePort, "", "", + false, map[string]string{}, map[string]string{ hostnameAnnotationKey: "foo.example.org.", @@ -1292,6 +1483,7 @@ func TestNodePortServices(t *testing.T) { true, false, []string{}, + tc.ignoreHostnameAnnotation, ) require.NoError(t, err) @@ -1311,24 +1503,25 @@ func TestNodePortServices(t *testing.T) { // TestHeadlessServices tests that headless services generate the correct endpoints. func TestHeadlessServices(t *testing.T) { for _, tc := range []struct { - title string - targetNamespace string - svcNamespace string - svcName string - svcType v1.ServiceType - compatibility string - fqdnTemplate string - labels map[string]string - annotations map[string]string - clusterIP string - podIPs []string - selector map[string]string - lbs []string - podnames []string - hostnames []string - phases []v1.PodPhase - expected []*endpoint.Endpoint - expectError bool + title string + targetNamespace string + svcNamespace string + svcName string + svcType v1.ServiceType + compatibility string + fqdnTemplate string + ignoreHostnameAnnotation bool + labels map[string]string + annotations map[string]string + clusterIP string + podIPs []string + selector map[string]string + lbs []string + podnames []string + hostnames []string + phases []v1.PodPhase + expected []*endpoint.Endpoint + expectError bool }{ { "annotated Headless services return endpoints for each selected Pod", @@ -1338,6 +1531,7 @@ func TestHeadlessServices(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{"component": "foo"}, map[string]string{ hostnameAnnotationKey: "service.example.org", @@ -1357,6 +1551,31 @@ func TestHeadlessServices(t *testing.T) { }, false, }, + { + "hostname annotated Headless services are ignored", + "", + "testing", + "foo", + v1.ServiceTypeClusterIP, + "", + "", + true, + map[string]string{"component": "foo"}, + map[string]string{ + hostnameAnnotationKey: "service.example.org", + }, + v1.ClusterIPNone, + []string{"1.1.1.1", "1.1.1.2"}, + map[string]string{ + "component": "foo", + }, + []string{}, + []string{"foo-0", "foo-1"}, + []string{"foo-0", "foo-1"}, + []v1.PodPhase{v1.PodRunning, v1.PodRunning}, + []*endpoint.Endpoint{}, + false, + }, { "annotated Headless services return endpoints with TTL for each selected Pod", "", @@ -1365,6 +1584,7 @@ func TestHeadlessServices(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{"component": "foo"}, map[string]string{ hostnameAnnotationKey: "service.example.org", @@ -1393,6 +1613,7 @@ func TestHeadlessServices(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{"component": "foo"}, map[string]string{ hostnameAnnotationKey: "service.example.org", @@ -1419,6 +1640,7 @@ func TestHeadlessServices(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{"component": "foo"}, map[string]string{ hostnameAnnotationKey: "service.example.org", @@ -1492,6 +1714,7 @@ func TestHeadlessServices(t *testing.T) { true, false, []string{}, + tc.ignoreHostnameAnnotation, ) require.NoError(t, err) @@ -1511,24 +1734,25 @@ func TestHeadlessServices(t *testing.T) { // TestHeadlessServices tests that headless services generate the correct endpoints. func TestHeadlessServicesHostIP(t *testing.T) { for _, tc := range []struct { - title string - targetNamespace string - svcNamespace string - svcName string - svcType v1.ServiceType - compatibility string - fqdnTemplate string - labels map[string]string - annotations map[string]string - clusterIP string - hostIPs []string - selector map[string]string - lbs []string - podnames []string - hostnames []string - phases []v1.PodPhase - expected []*endpoint.Endpoint - expectError bool + title string + targetNamespace string + svcNamespace string + svcName string + svcType v1.ServiceType + compatibility string + fqdnTemplate string + ignoreHostnameAnnotation bool + labels map[string]string + annotations map[string]string + clusterIP string + hostIPs []string + selector map[string]string + lbs []string + podnames []string + hostnames []string + phases []v1.PodPhase + expected []*endpoint.Endpoint + expectError bool }{ { "annotated Headless services return endpoints for each selected Pod", @@ -1538,6 +1762,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{"component": "foo"}, map[string]string{ hostnameAnnotationKey: "service.example.org", @@ -1557,6 +1782,31 @@ func TestHeadlessServicesHostIP(t *testing.T) { }, false, }, + { + "hostname annotated Headless services are ignored", + "", + "testing", + "foo", + v1.ServiceTypeClusterIP, + "", + "", + true, + map[string]string{"component": "foo"}, + map[string]string{ + hostnameAnnotationKey: "service.example.org", + }, + v1.ClusterIPNone, + []string{"1.1.1.1", "1.1.1.2"}, + map[string]string{ + "component": "foo", + }, + []string{}, + []string{"foo-0", "foo-1"}, + []string{"foo-0", "foo-1"}, + []v1.PodPhase{v1.PodRunning, v1.PodRunning}, + []*endpoint.Endpoint{}, + false, + }, { "annotated Headless services return endpoints with TTL for each selected Pod", "", @@ -1565,6 +1815,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{"component": "foo"}, map[string]string{ hostnameAnnotationKey: "service.example.org", @@ -1593,6 +1844,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{"component": "foo"}, map[string]string{ hostnameAnnotationKey: "service.example.org", @@ -1619,6 +1871,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { v1.ServiceTypeClusterIP, "", "", + false, map[string]string{"component": "foo"}, map[string]string{ hostnameAnnotationKey: "service.example.org", @@ -1692,6 +1945,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { true, true, []string{}, + tc.ignoreHostnameAnnotation, ) require.NoError(t, err) @@ -1732,7 +1986,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, []string{}) + client, err := NewServiceSource(kubernetes, v1.NamespaceAll, "", "", false, "", false, false, []string{}, false) require.NoError(b, err) for i := 0; i < b.N; i++ { diff --git a/source/store.go b/source/store.go index 10ab64eb73..01d3ef76f4 100644 --- a/source/store.go +++ b/source/store.go @@ -42,6 +42,7 @@ type Config struct { AnnotationFilter string FQDNTemplate string CombineFQDNAndAnnotation bool + IgnoreHostnameAnnotation bool Compatibility string PublishInternal bool PublishHostIP bool @@ -112,13 +113,13 @@ 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, cfg.ServiceTypeFilter) + return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation) case "ingress": client, err := p.KubeClient() if err != nil { return nil, err } - return NewIngressSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation) + return NewIngressSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation) case "istio-gateway": kubernetesClient, err := p.KubeClient() if err != nil { @@ -128,7 +129,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err if err != nil { return nil, err } - return NewIstioGatewaySource(kubernetesClient, istioClient, cfg.IstioIngressGateway, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation) + return NewIstioGatewaySource(kubernetesClient, istioClient, cfg.IstioIngressGateway, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation) case "fake": return NewFakeSource(cfg.FQDNTemplate) case "connector":