allow hostname annotations to be ignored

This commit is contained in:
Anand Patel 2018-10-15 16:50:52 -04:00
parent 2668b9f4ba
commit 3454363d67
12 changed files with 551 additions and 176 deletions

View File

@ -71,6 +71,7 @@ func main() {
AnnotationFilter: cfg.AnnotationFilter, AnnotationFilter: cfg.AnnotationFilter,
FQDNTemplate: cfg.FQDNTemplate, FQDNTemplate: cfg.FQDNTemplate,
CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation, CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation,
IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation,
Compatibility: cfg.Compatibility, Compatibility: cfg.Compatibility,
PublishInternal: cfg.PublishInternal, PublishInternal: cfg.PublishInternal,
PublishHostIP: cfg.PublishHostIP, PublishHostIP: cfg.PublishHostIP,

View File

@ -45,6 +45,7 @@ type Config struct {
AnnotationFilter string AnnotationFilter string
FQDNTemplate string FQDNTemplate string
CombineFQDNAndAnnotation bool CombineFQDNAndAnnotation bool
IgnoreHostnameAnnotation bool
Compatibility string Compatibility string
PublishInternal bool PublishInternal bool
PublishHostIP bool PublishHostIP bool
@ -118,6 +119,7 @@ var defaultConfig = &Config{
AnnotationFilter: "", AnnotationFilter: "",
FQDNTemplate: "", FQDNTemplate: "",
CombineFQDNAndAnnotation: false, CombineFQDNAndAnnotation: false,
IgnoreHostnameAnnotation: false,
Compatibility: "", Compatibility: "",
PublishInternal: false, PublishInternal: false,
PublishHostIP: false, PublishHostIP: false,
@ -225,6 +227,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("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("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("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("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-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("publish-host-ip", "Allow external-dns to publish host-ip for headless services (optional)").BoolVar(&cfg.PublishHostIP)

View File

@ -80,58 +80,59 @@ var (
} }
overriddenConfig = &Config{ overriddenConfig = &Config{
Master: "http://127.0.0.1:8080", Master: "http://127.0.0.1:8080",
KubeConfig: "/some/path", KubeConfig: "/some/path",
RequestTimeout: time.Second * 77, RequestTimeout: time.Second * 77,
IstioIngressGateway: "istio-other/istio-otheringressgateway", IstioIngressGateway: "istio-other/istio-otheringressgateway",
Sources: []string{"service", "ingress", "connector"}, Sources: []string{"service", "ingress", "connector"},
Namespace: "namespace", Namespace: "namespace",
FQDNTemplate: "{{.Name}}.service.example.com", FQDNTemplate: "{{.Name}}.service.example.com",
Compatibility: "mate", IgnoreHostnameAnnotation: true,
Provider: "google", Compatibility: "mate",
GoogleProject: "project", Provider: "google",
DomainFilter: []string{"example.org", "company.com"}, GoogleProject: "project",
ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"}, DomainFilter: []string{"example.org", "company.com"},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"},
AWSZoneType: "private", AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSAssumeRole: "some-other-role", AWSZoneType: "private",
AWSBatchChangeSize: 100, AWSAssumeRole: "some-other-role",
AWSBatchChangeInterval: time.Second * 2, AWSBatchChangeSize: 100,
AWSEvaluateTargetHealth: false, AWSBatchChangeInterval: time.Second * 2,
AzureConfigFile: "azure.json", AWSEvaluateTargetHealth: false,
AzureResourceGroup: "arg", AzureConfigFile: "azure.json",
CloudflareProxied: true, AzureResourceGroup: "arg",
InfobloxGridHost: "127.0.0.1", CloudflareProxied: true,
InfobloxWapiPort: 8443, InfobloxGridHost: "127.0.0.1",
InfobloxWapiUsername: "infoblox", InfobloxWapiPort: 8443,
InfobloxWapiPassword: "infoblox", InfobloxWapiUsername: "infoblox",
InfobloxWapiVersion: "2.6.1", InfobloxWapiPassword: "infoblox",
InfobloxSSLVerify: false, InfobloxWapiVersion: "2.6.1",
OCIConfigFile: "oci.yaml", InfobloxSSLVerify: false,
InMemoryZones: []string{"example.org", "company.com"}, OCIConfigFile: "oci.yaml",
PDNSServer: "http://ns.example.com:8081", InMemoryZones: []string{"example.org", "company.com"},
PDNSAPIKey: "some-secret-key", PDNSServer: "http://ns.example.com:8081",
PDNSTLSEnabled: true, PDNSAPIKey: "some-secret-key",
TLSCA: "/path/to/ca.crt", PDNSTLSEnabled: true,
TLSClientCert: "/path/to/cert.pem", TLSCA: "/path/to/ca.crt",
TLSClientCertKey: "/path/to/key.pem", TLSClientCert: "/path/to/cert.pem",
Policy: "upsert-only", TLSClientCertKey: "/path/to/key.pem",
Registry: "noop", Policy: "upsert-only",
TXTOwnerID: "owner-1", Registry: "noop",
TXTPrefix: "associated-txt-record", TXTOwnerID: "owner-1",
TXTCacheInterval: 12 * time.Hour, TXTPrefix: "associated-txt-record",
Interval: 10 * time.Minute, TXTCacheInterval: 12 * time.Hour,
Once: true, Interval: 10 * time.Minute,
DryRun: true, Once: true,
LogFormat: "json", DryRun: true,
MetricsAddress: "127.0.0.1:9099", LogFormat: "json",
LogLevel: logrus.DebugLevel.String(), MetricsAddress: "127.0.0.1:9099",
ConnectorSourceServer: "localhost:8081", LogLevel: logrus.DebugLevel.String(),
ExoscaleEndpoint: "https://api.foo.ch/dns", ConnectorSourceServer: "localhost:8081",
ExoscaleAPIKey: "1", ExoscaleEndpoint: "https://api.foo.ch/dns",
ExoscaleAPISecret: "2", ExoscaleAPIKey: "1",
CRDSourceAPIVersion: "test.k8s.io/v1alpha1", ExoscaleAPISecret: "2",
CRDSourceKind: "Endpoint", CRDSourceAPIVersion: "test.k8s.io/v1alpha1",
CRDSourceKind: "Endpoint",
} }
) )
@ -163,6 +164,7 @@ func TestParseFlags(t *testing.T) {
"--source=connector", "--source=connector",
"--namespace=namespace", "--namespace=namespace",
"--fqdn-template={{.Name}}.service.example.com", "--fqdn-template={{.Name}}.service.example.com",
"--ignore-hostname-annotation",
"--compatibility=mate", "--compatibility=mate",
"--provider=google", "--provider=google",
"--google-project=project", "--google-project=project",
@ -225,6 +227,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_SOURCE": "service\ningress\nconnector", "EXTERNAL_DNS_SOURCE": "service\ningress\nconnector",
"EXTERNAL_DNS_NAMESPACE": "namespace", "EXTERNAL_DNS_NAMESPACE": "namespace",
"EXTERNAL_DNS_FQDN_TEMPLATE": "{{.Name}}.service.example.com", "EXTERNAL_DNS_FQDN_TEMPLATE": "{{.Name}}.service.example.com",
"EXTERNAL_DNS_IGNORE_HOSTNAME_ANNOTATION": "1",
"EXTERNAL_DNS_COMPATIBILITY": "mate", "EXTERNAL_DNS_COMPATIBILITY": "mate",
"EXTERNAL_DNS_PROVIDER": "google", "EXTERNAL_DNS_PROVIDER": "google",
"EXTERNAL_DNS_GOOGLE_PROJECT": "project", "EXTERNAL_DNS_GOOGLE_PROJECT": "project",

View File

@ -65,5 +65,9 @@ func ValidateConfig(cfg *externaldns.Config) error {
return errors.New("TTL specified for Dyn is negative") 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 return nil
} }

View File

@ -116,3 +116,11 @@ func TestValidateGoodDynConfig(t *testing.T) {
assert.Nil(t, err, "Configuration should be valid, got this error instead", err) 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))
}

View File

@ -38,14 +38,15 @@ import (
// The gateway implementation uses the spec.servers.hosts values for the hostnames. // The gateway implementation uses the spec.servers.hosts values for the hostnames.
// Use targetAnnotationKey to explicitly set Endpoint. // Use targetAnnotationKey to explicitly set Endpoint.
type gatewaySource struct { type gatewaySource struct {
kubeClient kubernetes.Interface kubeClient kubernetes.Interface
istioClient istiomodel.ConfigStore istioClient istiomodel.ConfigStore
istioNamespace string istioNamespace string
istioIngressGatewayName string istioIngressGatewayName string
namespace string namespace string
annotationFilter string annotationFilter string
fqdnTemplate *template.Template fqdnTemplate *template.Template
combineFQDNAnnotation bool combineFQDNAnnotation bool
ignoreHostnameAnnotation bool
} }
// NewIstioGatewaySource creates a new gatewaySource with the given config. // NewIstioGatewaySource creates a new gatewaySource with the given config.
@ -57,6 +58,7 @@ func NewIstioGatewaySource(
annotationFilter string, annotationFilter string,
fqdnTemplate string, fqdnTemplate string,
combineFqdnAnnotation bool, combineFqdnAnnotation bool,
ignoreHostnameAnnotation bool,
) (Source, error) { ) (Source, error) {
var ( var (
tmpl *template.Template tmpl *template.Template
@ -77,14 +79,15 @@ func NewIstioGatewaySource(
} }
return &gatewaySource{ return &gatewaySource{
kubeClient: kubeClient, kubeClient: kubeClient,
istioClient: istioClient, istioClient: istioClient,
istioNamespace: istioNamespace, istioNamespace: istioNamespace,
istioIngressGatewayName: istioIngressGatewayName, istioIngressGatewayName: istioIngressGatewayName,
namespace: namespace, namespace: namespace,
annotationFilter: annotationFilter, annotationFilter: annotationFilter,
fqdnTemplate: tmpl, fqdnTemplate: tmpl,
combineFQDNAnnotation: combineFqdnAnnotation, combineFQDNAnnotation: combineFqdnAnnotation,
ignoreHostnameAnnotation: ignoreHostnameAnnotation,
}, nil }, nil
} }
@ -265,9 +268,12 @@ func (sc *gatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([
} }
} }
hostnameList := getHostnamesFromAnnotations(config.Annotations) // Skip endpoints if we do not want entries from annotations
for _, hostname := range hostnameList { if !sc.ignoreHostnameAnnotation {
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) hostnameList := getHostnamesFromAnnotations(config.Annotations)
for _, hostname := range hostnameList {
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...)
}
} }
return endpoints, nil return endpoints, nil

View File

@ -68,6 +68,7 @@ func (suite *GatewaySuite) SetupTest() {
"", "",
"{{.Name}}", "{{.Name}}",
false, false,
false,
) )
suite.NoError(err, "should initialize gateway source") suite.NoError(err, "should initialize gateway source")
@ -141,6 +142,7 @@ func TestNewIstioGatewaySource(t *testing.T) {
ti.annotationFilter, ti.annotationFilter,
ti.fqdnTemplate, ti.fqdnTemplate,
ti.combineFQDNAndAnnotation, ti.combineFQDNAndAnnotation,
false,
) )
if ti.expectError { if ti.expectError {
assert.Error(t, err) assert.Error(t, err)
@ -273,6 +275,7 @@ func testGatewayEndpoints(t *testing.T) {
expectError bool expectError bool
fqdnTemplate string fqdnTemplate string
combineFQDNAndAnnotation bool combineFQDNAndAnnotation bool
ignoreHostnameAnnotation bool
}{ }{
{ {
title: "no gateway", title: "no gateway",
@ -913,6 +916,51 @@ func testGatewayEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{}, expected: []*endpoint.Endpoint{},
fqdnTemplate: "{{.Name}}.ext-dns.test.com", 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) { t.Run(ti.title, func(t *testing.T) {
configs := make([]istiomodel.Config, 0) configs := make([]istiomodel.Config, 0)
@ -939,6 +987,7 @@ func testGatewayEndpoints(t *testing.T) {
ti.annotationFilter, ti.annotationFilter,
ti.fqdnTemplate, ti.fqdnTemplate,
ti.combineFQDNAndAnnotation, ti.combineFQDNAndAnnotation,
ti.ignoreHostnameAnnotation,
) )
require.NoError(t, err) require.NoError(t, err)
@ -972,6 +1021,7 @@ func newTestGatewaySource(ingress *v1.Service) (*gatewaySource, error) {
"", "",
"{{.Name}}", "{{.Name}}",
false, false,
false,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -38,15 +38,16 @@ import (
// Use targetAnnotationKey to explicitly set Endpoint. (useful if the ingress // Use targetAnnotationKey to explicitly set Endpoint. (useful if the ingress
// controller does not update, or to override with alternative endpoint) // controller does not update, or to override with alternative endpoint)
type ingressSource struct { type ingressSource struct {
client kubernetes.Interface client kubernetes.Interface
namespace string namespace string
annotationFilter string annotationFilter string
fqdnTemplate *template.Template fqdnTemplate *template.Template
combineFQDNAnnotation bool combineFQDNAnnotation bool
ignoreHostnameAnnotation bool
} }
// NewIngressSource creates a new ingressSource with the given config. // 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 ( var (
tmpl *template.Template tmpl *template.Template
err error err error
@ -61,11 +62,12 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilt
} }
return &ingressSource{ return &ingressSource{
client: kubeClient, client: kubeClient,
namespace: namespace, namespace: namespace,
annotationFilter: annotationFilter, annotationFilter: annotationFilter,
fqdnTemplate: tmpl, fqdnTemplate: tmpl,
combineFQDNAnnotation: combineFqdnAnnotation, combineFQDNAnnotation: combineFqdnAnnotation,
ignoreHostnameAnnotation: ignoreHostnameAnnotation,
}, nil }, nil
} }
@ -92,7 +94,7 @@ func (sc *ingressSource) Endpoints() ([]*endpoint.Endpoint, error) {
continue continue
} }
ingEndpoints := endpointsFromIngress(&ing) ingEndpoints := endpointsFromIngress(&ing, sc.ignoreHostnameAnnotation)
// apply template if host is missing on ingress // apply template if host is missing on ingress
if (sc.combineFQDNAnnotation || len(ingEndpoints) == 0) && sc.fqdnTemplate != nil { if (sc.combineFQDNAnnotation || len(ingEndpoints) == 0) && sc.fqdnTemplate != nil {
@ -194,7 +196,7 @@ func (sc *ingressSource) setResourceLabel(ingress v1beta1.Ingress, endpoints []*
} }
// endpointsFromIngress extracts the endpoints from ingress object // 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 var endpoints []*endpoint.Endpoint
ttl, err := getTTLFromAnnotations(ing.Annotations) ttl, err := getTTLFromAnnotations(ing.Annotations)
@ -224,11 +226,13 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
} }
} }
hostnameList := getHostnamesFromAnnotations(ing.Annotations) // Skip endpoints if we do not want entries from annotations
for _, hostname := range hostnameList { if !ignoreHostnameAnnotation {
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) hostnameList := getHostnamesFromAnnotations(ing.Annotations)
for _, hostname := range hostnameList {
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...)
}
} }
return endpoints return endpoints
} }

View File

@ -50,6 +50,7 @@ func (suite *IngressSuite) SetupTest() {
"", "",
"{{.Name}}", "{{.Name}}",
false, false,
false,
) )
suite.NoError(err, "should initialize ingress source") suite.NoError(err, "should initialize ingress source")
@ -123,6 +124,7 @@ func TestNewIngressSource(t *testing.T) {
ti.annotationFilter, ti.annotationFilter,
ti.fqdnTemplate, ti.fqdnTemplate,
ti.combineFQDNAndAnnotation, ti.combineFQDNAndAnnotation,
false,
) )
if ti.expectError { if ti.expectError {
assert.Error(t, err) assert.Error(t, err)
@ -210,7 +212,7 @@ func testEndpointsFromIngress(t *testing.T) {
} { } {
t.Run(ti.title, func(t *testing.T) { t.Run(ti.title, func(t *testing.T) {
realIngress := ti.ingress.Ingress() 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 expectError bool
fqdnTemplate string fqdnTemplate string
combineFQDNAndAnnotation bool combineFQDNAndAnnotation bool
ignoreHostnameAnnotation bool
}{ }{
{ {
title: "no ingress", title: "no ingress",
@ -888,6 +891,38 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{}, expected: []*endpoint.Endpoint{},
fqdnTemplate: "{{.Name}}.ext-dns.test.com", 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) { t.Run(ti.title, func(t *testing.T) {
ingresses := make([]*v1beta1.Ingress, 0) ingresses := make([]*v1beta1.Ingress, 0)
@ -902,6 +937,7 @@ func testIngressEndpoints(t *testing.T) {
ti.annotationFilter, ti.annotationFilter,
ti.fqdnTemplate, ti.fqdnTemplate,
ti.combineFQDNAndAnnotation, ti.combineFQDNAndAnnotation,
ti.ignoreHostnameAnnotation,
) )
for _, ingress := range ingresses { for _, ingress := range ingresses {
_, err := fakeClient.Extensions().Ingresses(ingress.Namespace).Create(ingress) _, err := fakeClient.Extensions().Ingresses(ingress.Namespace).Create(ingress)

View File

@ -48,16 +48,17 @@ type serviceSource struct {
namespace string namespace string
annotationFilter string annotationFilter string
// process Services with legacy annotations // process Services with legacy annotations
compatibility string compatibility string
fqdnTemplate *template.Template fqdnTemplate *template.Template
combineFQDNAnnotation bool combineFQDNAnnotation bool
publishInternal bool ignoreHostnameAnnotation bool
publishHostIP bool publishInternal bool
serviceTypeFilter map[string]struct{} publishHostIP bool
serviceTypeFilter map[string]struct{}
} }
// NewServiceSource creates a new serviceSource with the given config. // 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 ( var (
tmpl *template.Template tmpl *template.Template
err error err error
@ -79,15 +80,16 @@ func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilt
} }
return &serviceSource{ return &serviceSource{
client: kubeClient, client: kubeClient,
namespace: namespace, namespace: namespace,
annotationFilter: annotationFilter, annotationFilter: annotationFilter,
compatibility: compatibility, compatibility: compatibility,
fqdnTemplate: tmpl, fqdnTemplate: tmpl,
combineFQDNAnnotation: combineFqdnAnnotation, combineFQDNAnnotation: combineFqdnAnnotation,
publishInternal: publishInternal, ignoreHostnameAnnotation: ignoreHostnameAnnotation,
publishHostIP: publishHostIP, publishInternal: publishInternal,
serviceTypeFilter: serviceTypes, publishHostIP: publishHostIP,
serviceTypeFilter: serviceTypes,
}, nil }, nil
} }
@ -230,9 +232,12 @@ func (sc *serviceSource) endpointsFromTemplate(svc *v1.Service, nodeTargets endp
func (sc *serviceSource) endpoints(svc *v1.Service, nodeTargets endpoint.Targets) []*endpoint.Endpoint { func (sc *serviceSource) endpoints(svc *v1.Service, nodeTargets endpoint.Targets) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint var endpoints []*endpoint.Endpoint
hostnameList := getHostnamesFromAnnotations(svc.Annotations) // Skip endpoints if we do not want entries from annotations
for _, hostname := range hostnameList { if !sc.ignoreHostnameAnnotation {
endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, nodeTargets)...) hostnameList := getHostnamesFromAnnotations(svc.Annotations)
for _, hostname := range hostnameList {
endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, nodeTargets)...)
}
} }
return endpoints return endpoints

View File

@ -51,6 +51,7 @@ func (suite *ServiceSuite) SetupTest() {
false, false,
false, false,
[]string{}, []string{},
false,
) )
suite.fooWithTargets = &v1.Service{ suite.fooWithTargets = &v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
@ -142,6 +143,7 @@ func testServiceSourceNewServiceSource(t *testing.T) {
false, false,
false, false,
ti.serviceTypesFilter, ti.serviceTypesFilter,
false,
) )
if ti.expectError { if ti.expectError {
@ -165,6 +167,7 @@ func testServiceSourceEndpoints(t *testing.T) {
compatibility string compatibility string
fqdnTemplate string fqdnTemplate string
combineFQDNAndAnnotation bool combineFQDNAndAnnotation bool
ignoreHostnameAnnotation bool
labels map[string]string labels map[string]string
annotations map[string]string annotations map[string]string
clusterIP string clusterIP string
@ -183,6 +186,26 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, 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{},
map[string]string{}, map[string]string{},
"", "",
@ -201,6 +224,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -213,6 +237,27 @@ func testServiceSourceEndpoints(t *testing.T) {
}, },
false, 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", "annotated ClusterIp aren't processed without explicit authorization",
"", "",
@ -223,6 +268,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -243,6 +289,29 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
false, 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{},
map[string]string{}, map[string]string{},
"", "",
@ -264,6 +333,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
true, true,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org., bar.example.org.", hostnameAnnotationKey: "foo.example.org., bar.example.org.",
@ -279,6 +349,30 @@ func testServiceSourceEndpoints(t *testing.T) {
}, },
false, 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", "annotated services with multiple hostnames return an endpoint with target IP",
"", "",
@ -289,6 +383,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org., bar.example.org.", hostnameAnnotationKey: "foo.example.org., bar.example.org.",
@ -312,6 +407,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org, bar.example.org", hostnameAnnotationKey: "foo.example.org, bar.example.org",
@ -335,6 +431,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -357,6 +454,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted
@ -380,6 +478,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
controllerAnnotationKey: controllerAnnotationValue, controllerAnnotationKey: controllerAnnotationValue,
@ -403,6 +502,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"{{.Name}}.ext-dns.test.com", "{{.Name}}.ext-dns.test.com",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
controllerAnnotationKey: "some-other-tool", controllerAnnotationKey: "some-other-tool",
@ -424,6 +524,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -446,6 +547,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -466,6 +568,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -488,6 +591,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -511,6 +615,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -532,6 +637,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -553,6 +659,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -576,6 +683,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -597,6 +705,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -617,6 +726,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -639,6 +749,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
"zalando.org/dnsname": "foo.example.org.", "zalando.org/dnsname": "foo.example.org.",
@ -659,6 +770,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"mate", "mate",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
"zalando.org/dnsname": "foo.example.org.", "zalando.org/dnsname": "foo.example.org.",
@ -681,6 +793,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"molecule", "molecule",
"", "",
false, false,
false,
map[string]string{ map[string]string{
"dns": "route53", "dns": "route53",
}, },
@ -706,6 +819,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"{{.Name}}.bar.example.com", "{{.Name}}.bar.example.com",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{}, map[string]string{},
"", "",
@ -727,6 +841,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"{{.Name}}.bar.example.com", "{{.Name}}.bar.example.com",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -750,6 +865,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"mate", "mate",
"{{.Name}}.bar.example.com", "{{.Name}}.bar.example.com",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
"zalando.org/dnsname": "mate.example.org.", "zalando.org/dnsname": "mate.example.org.",
@ -772,6 +888,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"{{.Calibre}}.bar.example.com", "{{.Calibre}}.bar.example.com",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{}, map[string]string{},
"", "",
@ -790,6 +907,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -812,6 +930,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -835,6 +954,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -858,6 +978,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -881,6 +1002,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -903,6 +1025,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"", "",
"", "",
false, false,
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -960,6 +1083,7 @@ func testServiceSourceEndpoints(t *testing.T) {
false, false,
false, false,
tc.serviceTypesFilter, tc.serviceTypesFilter,
tc.ignoreHostnameAnnotation,
) )
require.NoError(t, err) require.NoError(t, err)
@ -979,20 +1103,21 @@ func testServiceSourceEndpoints(t *testing.T) {
// testServiceSourceEndpoints tests that various services generate the correct endpoints. // testServiceSourceEndpoints tests that various services generate the correct endpoints.
func TestClusterIpServices(t *testing.T) { func TestClusterIpServices(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
title string title string
targetNamespace string targetNamespace string
annotationFilter string annotationFilter string
svcNamespace string svcNamespace string
svcName string svcName string
svcType v1.ServiceType svcType v1.ServiceType
compatibility string compatibility string
fqdnTemplate string fqdnTemplate string
labels map[string]string ignoreHostnameAnnotation bool
annotations map[string]string labels map[string]string
clusterIP string annotations map[string]string
lbs []string clusterIP string
expected []*endpoint.Endpoint lbs []string
expectError bool expected []*endpoint.Endpoint
expectError bool
}{ }{
{ {
"annotated ClusterIp services return an endpoint with Cluster IP", "annotated ClusterIp services return an endpoint with Cluster IP",
@ -1003,6 +1128,7 @@ func TestClusterIpServices(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -1014,6 +1140,25 @@ func TestClusterIpServices(t *testing.T) {
}, },
false, 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", "non-annotated ClusterIp services with set fqdnTemplate return an endpoint with target IP",
"", "",
@ -1023,6 +1168,7 @@ func TestClusterIpServices(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"{{.Name}}.bar.example.com", "{{.Name}}.bar.example.com",
false,
map[string]string{}, map[string]string{},
map[string]string{}, map[string]string{},
"4.5.6.7", "4.5.6.7",
@ -1041,6 +1187,7 @@ func TestClusterIpServices(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{}, map[string]string{},
map[string]string{}, map[string]string{},
v1.ClusterIPNone, v1.ClusterIPNone,
@ -1095,6 +1242,7 @@ func TestClusterIpServices(t *testing.T) {
true, true,
false, false,
[]string{}, []string{},
tc.ignoreHostnameAnnotation,
) )
require.NoError(t, err) require.NoError(t, err)
@ -1114,20 +1262,21 @@ func TestClusterIpServices(t *testing.T) {
// testNodePortServices tests that various services generate the correct endpoints. // testNodePortServices tests that various services generate the correct endpoints.
func TestNodePortServices(t *testing.T) { func TestNodePortServices(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
title string title string
targetNamespace string targetNamespace string
annotationFilter string annotationFilter string
svcNamespace string svcNamespace string
svcName string svcName string
svcType v1.ServiceType svcType v1.ServiceType
compatibility string compatibility string
fqdnTemplate string fqdnTemplate string
labels map[string]string ignoreHostnameAnnotation bool
annotations map[string]string labels map[string]string
lbs []string annotations map[string]string
expected []*endpoint.Endpoint lbs []string
expectError bool expected []*endpoint.Endpoint
nodes []*v1.Node expectError bool
nodes []*v1.Node
}{ }{
{ {
"annotated NodePort services return an endpoint with IP addresses of the cluster's nodes", "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, v1.ServiceTypeNodePort,
"", "",
"", "",
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", 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", "non-annotated NodePort services with set fqdnTemplate return an endpoint with target IP",
"", "",
@ -1179,6 +1368,7 @@ func TestNodePortServices(t *testing.T) {
v1.ServiceTypeNodePort, v1.ServiceTypeNodePort,
"", "",
"{{.Name}}.bar.example.com", "{{.Name}}.bar.example.com",
false,
map[string]string{}, map[string]string{},
map[string]string{}, map[string]string{},
nil, nil,
@ -1218,6 +1408,7 @@ func TestNodePortServices(t *testing.T) {
v1.ServiceTypeNodePort, v1.ServiceTypeNodePort,
"", "",
"", "",
false,
map[string]string{}, map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -1292,6 +1483,7 @@ func TestNodePortServices(t *testing.T) {
true, true,
false, false,
[]string{}, []string{},
tc.ignoreHostnameAnnotation,
) )
require.NoError(t, err) require.NoError(t, err)
@ -1311,24 +1503,25 @@ func TestNodePortServices(t *testing.T) {
// TestHeadlessServices tests that headless services generate the correct endpoints. // TestHeadlessServices tests that headless services generate the correct endpoints.
func TestHeadlessServices(t *testing.T) { func TestHeadlessServices(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
title string title string
targetNamespace string targetNamespace string
svcNamespace string svcNamespace string
svcName string svcName string
svcType v1.ServiceType svcType v1.ServiceType
compatibility string compatibility string
fqdnTemplate string fqdnTemplate string
labels map[string]string ignoreHostnameAnnotation bool
annotations map[string]string labels map[string]string
clusterIP string annotations map[string]string
podIP string clusterIP string
selector map[string]string podIP string
lbs []string selector map[string]string
podnames []string lbs []string
hostnames []string podnames []string
phases []v1.PodPhase hostnames []string
expected []*endpoint.Endpoint phases []v1.PodPhase
expectError bool expected []*endpoint.Endpoint
expectError bool
}{ }{
{ {
"annotated Headless services return endpoints for each selected Pod", "annotated Headless services return endpoints for each selected Pod",
@ -1338,6 +1531,7 @@ func TestHeadlessServices(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{"component": "foo"}, map[string]string{"component": "foo"},
map[string]string{ map[string]string{
hostnameAnnotationKey: "service.example.org", hostnameAnnotationKey: "service.example.org",
@ -1357,6 +1551,31 @@ func TestHeadlessServices(t *testing.T) {
}, },
false, 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,
"1.1.1.1",
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", "annotated Headless services return endpoints with TTL for each selected Pod",
"", "",
@ -1365,6 +1584,7 @@ func TestHeadlessServices(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{"component": "foo"}, map[string]string{"component": "foo"},
map[string]string{ map[string]string{
hostnameAnnotationKey: "service.example.org", hostnameAnnotationKey: "service.example.org",
@ -1393,6 +1613,7 @@ func TestHeadlessServices(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{"component": "foo"}, map[string]string{"component": "foo"},
map[string]string{ map[string]string{
hostnameAnnotationKey: "service.example.org", hostnameAnnotationKey: "service.example.org",
@ -1419,6 +1640,7 @@ func TestHeadlessServices(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{"component": "foo"}, map[string]string{"component": "foo"},
map[string]string{ map[string]string{
hostnameAnnotationKey: "service.example.org", hostnameAnnotationKey: "service.example.org",
@ -1493,6 +1715,7 @@ func TestHeadlessServices(t *testing.T) {
true, true,
false, false,
[]string{}, []string{},
tc.ignoreHostnameAnnotation,
) )
require.NoError(t, err) require.NoError(t, err)
@ -1512,24 +1735,25 @@ func TestHeadlessServices(t *testing.T) {
// TestHeadlessServices tests that headless services generate the correct endpoints. // TestHeadlessServices tests that headless services generate the correct endpoints.
func TestHeadlessServicesHostIP(t *testing.T) { func TestHeadlessServicesHostIP(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
title string title string
targetNamespace string targetNamespace string
svcNamespace string svcNamespace string
svcName string svcName string
svcType v1.ServiceType svcType v1.ServiceType
compatibility string compatibility string
fqdnTemplate string fqdnTemplate string
labels map[string]string ignoreHostnameAnnotation bool
annotations map[string]string labels map[string]string
clusterIP string annotations map[string]string
hostIP string clusterIP string
selector map[string]string hostIP string
lbs []string selector map[string]string
podnames []string lbs []string
hostnames []string podnames []string
phases []v1.PodPhase hostnames []string
expected []*endpoint.Endpoint phases []v1.PodPhase
expectError bool expected []*endpoint.Endpoint
expectError bool
}{ }{
{ {
"annotated Headless services return endpoints for each selected Pod", "annotated Headless services return endpoints for each selected Pod",
@ -1539,6 +1763,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{"component": "foo"}, map[string]string{"component": "foo"},
map[string]string{ map[string]string{
hostnameAnnotationKey: "service.example.org", hostnameAnnotationKey: "service.example.org",
@ -1558,6 +1783,31 @@ func TestHeadlessServicesHostIP(t *testing.T) {
}, },
false, 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,
"1.1.1.1",
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", "annotated Headless services return endpoints with TTL for each selected Pod",
"", "",
@ -1566,6 +1816,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{"component": "foo"}, map[string]string{"component": "foo"},
map[string]string{ map[string]string{
hostnameAnnotationKey: "service.example.org", hostnameAnnotationKey: "service.example.org",
@ -1594,6 +1845,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{"component": "foo"}, map[string]string{"component": "foo"},
map[string]string{ map[string]string{
hostnameAnnotationKey: "service.example.org", hostnameAnnotationKey: "service.example.org",
@ -1620,6 +1872,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
v1.ServiceTypeClusterIP, v1.ServiceTypeClusterIP,
"", "",
"", "",
false,
map[string]string{"component": "foo"}, map[string]string{"component": "foo"},
map[string]string{ map[string]string{
hostnameAnnotationKey: "service.example.org", hostnameAnnotationKey: "service.example.org",
@ -1694,6 +1947,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
true, true,
true, true,
[]string{}, []string{},
tc.ignoreHostnameAnnotation,
) )
require.NoError(t, err) require.NoError(t, err)
@ -1734,7 +1988,7 @@ func BenchmarkServiceEndpoints(b *testing.B) {
_, err := kubernetes.CoreV1().Services(service.Namespace).Create(service) _, err := kubernetes.CoreV1().Services(service.Namespace).Create(service)
require.NoError(b, err) 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) require.NoError(b, err)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {

View File

@ -42,6 +42,7 @@ type Config struct {
AnnotationFilter string AnnotationFilter string
FQDNTemplate string FQDNTemplate string
CombineFQDNAndAnnotation bool CombineFQDNAndAnnotation bool
IgnoreHostnameAnnotation bool
Compatibility string Compatibility string
PublishInternal bool PublishInternal bool
PublishHostIP bool PublishHostIP bool
@ -112,13 +113,13 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
if err != nil { if err != nil {
return nil, err 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": case "ingress":
client, err := p.KubeClient() client, err := p.KubeClient()
if err != nil { if err != nil {
return nil, err 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": case "istio-gateway":
kubernetesClient, err := p.KubeClient() kubernetesClient, err := p.KubeClient()
if err != nil { if err != nil {
@ -128,7 +129,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
if err != nil { if err != nil {
return nil, err 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": case "fake":
return NewFakeSource(cfg.FQDNTemplate) return NewFakeSource(cfg.FQDNTemplate)
case "connector": case "connector":