Improve kubernetes external name service support

Co-authored-by: jbdoumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
robotte 2020-03-10 12:46:05 +01:00 committed by GitHub
parent e511cfe2e4
commit 3b85dc9618
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 876 additions and 48 deletions

View file

@ -355,25 +355,25 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
- b.foo.com
```
| Ref | Attribute | Purpose |
|------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. |
| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching |
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace |
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [9] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
| [10] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
| [11] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
| [12] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
| [13] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
| [14] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
| [15] | `tls.domains` | List of [domains](../routers/index.md#domains) |
| [16] | `domains[n].main` | Defines the main domain name |
| [17] | `domains[n].sans` | List of SANs (alternative domains) |
| Ref | Attribute | Purpose |
|------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. |
| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching |
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace |
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) |
| [9] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
| [10] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
| [11] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
| [12] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
| [13] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
| [14] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
| [15] | `tls.domains` | List of [domains](../routers/index.md#domains) |
| [16] | `domains[n].main` | Defines the main domain name |
| [17] | `domains[n].sans` | List of SANs (alternative domains) |
??? example "Declaring an IngressRoute"
@ -467,6 +467,112 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
- Setting the kubernetes service port to use port 443 (https)
If you do not configure the above, Traefik will assume an http connection.
!!! important "Using Kubernetes ExternalName Service"
Traefik backends creation needs a port to be set, however Kubernetes [ExternalName Service](https://kubernetes.io/fr/docs/concepts/services-networking/service/#externalname) could be defined without any port.
Accordingly, Traefik supports defining a port in two ways:
- only on `IngressRoute` service
- on both sides, you'll be warned if the ports don't match, and the `IngressRoute` service port is used
Thus, in case of two sides port definition, Traefik expects a match between ports.
??? example "Examples"
```yaml tab="IngressRoute"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`)
kind: Rule
services:
- name: external-svc
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
```
```yaml tab="ExternalName Service"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`)
kind: Rule
services:
- name: external-svc
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- port: 80
```
```yaml tab="Both sides"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`)
kind: Rule
services:
- name: external-svc
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- port: 80
```
### Kind: `Middleware`
@ -853,7 +959,7 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube
| [1] | `entryPoints` | List of [entrypoints](../routers/index.md#entrypoints_1) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule_1) corresponding to an underlying router |
| [4] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions |
| [4] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) |
| [5] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) |
| [7] | `services[n].weight` | Defines the weight to apply to the server load balancing |
@ -928,6 +1034,111 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
```
!!! important "Using Kubernetes ExternalName Service"
Traefik backends creation needs a port to be set, however Kubernetes [ExternalName Service](https://kubernetes.io/fr/docs/concepts/services-networking/service/#externalname) could be defined without any port.
Accordingly, Traefik supports defining a port in two ways:
- only on `IngressRouteTCP` service
- on both sides, you'll be warned if the ports don't match, and the `IngressRouteTCP` service port is used
Thus, in case of two sides port definition, Traefik expects a match between ports.
??? example "Examples"
```yaml tab="IngressRouteTCP"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`*`)
kind: Rule
services:
- name: external-svc
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
```
```yaml tab="ExternalName Service"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`*`)
kind: Rule
services:
- name: external-svc
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- port: 80
```
```yaml tab="Both sides"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`*`)
kind: Rule
services:
- name: external-svc
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- port: 80
```
### Kind `IngressRouteUDP`
`IngressRouteUDP` is the CRD implementation of a [Traefik UDP router](../routers/index.md#configuring-udp-routers).

View file

@ -126,3 +126,13 @@ spec:
selector:
app: containous
task: whoamiudp
---
apiVersion: v1
kind: Service
metadata:
name: externalname-svc
namespace: default
spec:
externalName: domain.com
type: ExternalName

View file

@ -13,6 +13,8 @@ spec:
- name: whoamitcp
namespace: default
port: 8080
- name: externalname-svc
port: 9090
tls:
options:
name: mytlsoption

View file

@ -194,18 +194,44 @@
}
},
"tcpServices": {
"default-test3.route-673acf455cb2dab0b43a@kubernetescrd": {
"default-test3.route-673acf455cb2dab0b43a-externalname-svc-9090@kubernetescrd": {
"loadBalancer": {
"terminationDelay": 100,
"servers": [
{
"address": "10.42.0.4:8080"
"address": "domain.com:9090"
}
]
},
"status": "enabled"
},
"default-test3.route-673acf455cb2dab0b43a-whoamitcp-8080@kubernetescrd": {
"loadBalancer": {
"terminationDelay": 100,
"servers": [
{
"address": "10.42.0.3:8080"
},
{
"address": "10.42.0.8:8080"
}
]
},
"status": "enabled"
},
"default-test3.route-673acf455cb2dab0b43a@kubernetescrd": {
"weighted": {
"services": [
{
"name": "default-test3.route-673acf455cb2dab0b43a-whoamitcp-8080",
"weight": 1
},
{
"name": "default-test3.route-673acf455cb2dab0b43a-externalname-svc-9090",
"weight": 1
}
]
},
"status": "enabled",
"usedBy": [
"default-test3.route-673acf455cb2dab0b43a@kubernetescrd"

View file

@ -118,3 +118,44 @@ subsets:
ports:
- name: websecure2
port: 8443
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
---
apiVersion: v1
kind: Service
metadata:
name: external-svc-with-http
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- name: http
protocol: TCP
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: external-svc-with-https
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- name: https
protocol: TCP
port: 443

View file

@ -131,3 +131,40 @@ subsets:
ports:
- name: myapp4
port: 8084
---
apiVersion: v1
kind: Service
metadata:
name: external-svc
namespace: default
spec:
externalName: external.domain
type: ExternalName
---
apiVersion: v1
kind: Service
metadata:
name: external.service.with.port
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- name: http
protocol: TCP
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: external.service.without.port
namespace: default
spec:
externalName: external.domain
type: ExternalName
ports:
- name: http
protocol: TCP

View file

@ -0,0 +1,15 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: external-svc
port: 8000

View file

@ -0,0 +1,15 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: external.service.with.port
port: 80

View file

@ -0,0 +1,14 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: external-svc

View file

@ -0,0 +1,16 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`)
kind: Rule
services:
- name: external-svc
port: 80

View file

@ -0,0 +1,16 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`)
kind: Rule
services:
- name: external-svc-with-http
port: 80

View file

@ -0,0 +1,16 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`)
kind: Rule
services:
- name: external-svc-with-https
port: 443

View file

@ -0,0 +1,15 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: Host(`foo.com`)
kind: Rule
services:
- name: external-svc

View file

@ -5,6 +5,7 @@ import (
"bytes"
"context"
"crypto/sha256"
"errors"
"fmt"
"os"
"sort"
@ -248,6 +249,38 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
return conf
}
func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error) {
if svc == nil {
return nil, errors.New("service is not defined")
}
if port == 0 {
return nil, errors.New("ingressRoute service port not defined")
}
hasValidPort := false
for _, p := range svc.Spec.Ports {
if p.Port == port {
return &p, nil
}
if p.Port != 0 {
hasValidPort = true
}
}
if svc.Spec.Type != corev1.ServiceTypeExternalName {
return nil, fmt.Errorf("service port not found: %d", port)
}
if hasValidPort {
log.WithoutContext().
Warning("The port %d from IngressRoute doesn't match with ports defined in the ExternalName service %s/%s.", port, svc.Namespace, svc.Name)
}
return &corev1.ServicePort{Port: port}, nil
}
func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {
if errorPage == nil {
return nil, nil, nil

View file

@ -294,27 +294,20 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
return nil, fmt.Errorf("kubernetes service not found: %s/%s", namespace, sanitizedName)
}
confPort := svc.Port
var portSpec *corev1.ServicePort
for _, p := range service.Spec.Ports {
if confPort == p.Port {
portSpec = &p
break
}
}
if portSpec == nil {
return nil, errors.New("service port not found")
svcPort, err := getServicePort(service, svc.Port)
if err != nil {
return nil, err
}
var servers []dynamic.Server
if service.Spec.Type == corev1.ServiceTypeExternalName {
protocol, err := parseServiceProtocol(svc.Scheme, portSpec.Name, portSpec.Port)
protocol, err := parseServiceProtocol(svc.Scheme, svcPort.Name, svcPort.Port)
if err != nil {
return nil, err
}
return append(servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, portSpec.Port),
URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, svcPort.Port),
}), nil
}
@ -332,7 +325,7 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
var port int32
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if portSpec.Name == p.Name {
if svcPort.Name == p.Name {
port = p.Port
break
}
@ -342,7 +335,7 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
return nil, fmt.Errorf("cannot define a port for %s/%s", namespace, sanitizedName)
}
protocol, err := parseServiceProtocol(svc.Scheme, portSpec.Name, portSpec.Port)
protocol, err := parseServiceProtocol(svc.Scheme, svcPort.Name, svcPort.Port)
if err != nil {
return nil, err
}

View file

@ -162,22 +162,15 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
return nil, errors.New("service not found")
}
var portSpec *corev1.ServicePort
for _, p := range service.Spec.Ports {
if svc.Port == p.Port {
portSpec = &p
break
}
}
if portSpec == nil {
return nil, errors.New("service port not found")
svcPort, err := getServicePort(service, svc.Port)
if err != nil {
return nil, err
}
var servers []dynamic.TCPServer
if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, dynamic.TCPServer{
Address: fmt.Sprintf("%s:%d", service.Spec.ExternalName, portSpec.Port),
Address: fmt.Sprintf("%s:%d", service.Spec.ExternalName, svcPort.Port),
})
} else {
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
@ -196,7 +189,7 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
var port int32
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if portSpec.Name == p.Name {
if svcPort.Name == p.Name {
port = p.Port
break
}

View file

@ -4,6 +4,8 @@ import (
"context"
"testing"
corev1 "k8s.io/api/core/v1"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/tls"
@ -906,6 +908,106 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
},
},
},
{
desc: "Simple Ingress Route, with externalName service",
paths: []string{"tcp/services.yml", "tcp/with_externalname.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-test.route-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Services: map[string]*dynamic.TCPService{
"default-test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "external.domain:8000",
Port: "",
},
},
},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Ingress Route, externalName service with port",
paths: []string{"tcp/services.yml", "tcp/with_externalname_with_port.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-test.route-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Services: map[string]*dynamic.TCPService{
"default-test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "external.domain:80",
Port: "",
},
},
},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Ingress Route, externalName service without port",
paths: []string{"tcp/services.yml", "tcp/with_externalname_without_ports.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-test.route-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
}
for _, test := range testCases {
@ -2860,6 +2962,134 @@ func TestLoadIngressRoutes(t *testing.T) {
{
desc: "port selected by name (TODO)",
},
{
desc: "Simple Ingress Route, with externalName service",
paths: []string{"services.yml", "with_externalname.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-test-route-6f97418635c7e18853da": {
EntryPoints: []string{"foo"},
Service: "default-test-route-6f97418635c7e18853da",
Rule: "Host(`foo.com`)",
}},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-test-route-6f97418635c7e18853da": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://external.domain:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Ingress Route, externalName service with http",
paths: []string{"services.yml", "with_externalname_with_http.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-test-route-6f97418635c7e18853da": {
EntryPoints: []string{"foo"},
Service: "default-test-route-6f97418635c7e18853da",
Rule: "Host(`foo.com`)",
}},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-test-route-6f97418635c7e18853da": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://external.domain:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Ingress Route, externalName service with https",
paths: []string{"services.yml", "with_externalname_with_https.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-test-route-6f97418635c7e18853da": {
EntryPoints: []string{"foo"},
Service: "default-test-route-6f97418635c7e18853da",
Rule: "Host(`foo.com`)",
}},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-test-route-6f97418635c7e18853da": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "https://external.domain:443",
},
},
PassHostHeader: Bool(true),
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Ingress Route, externalName service without ports",
paths: []string{"services.yml", "with_externalname_without_ports.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
}
for _, test := range testCases {
@ -3255,3 +3485,148 @@ func TestParseServiceProtocol(t *testing.T) {
})
}
}
func TestGetServicePort(t *testing.T) {
testCases := []struct {
desc string
svc *corev1.Service
port int32
expected *corev1.ServicePort
expectError bool
}{
{
desc: "Basic",
expectError: true,
},
{
desc: "Matching ports, with no service type",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Port: 80,
},
},
},
},
port: 80,
expected: &corev1.ServicePort{
Port: 80,
},
},
{
desc: "Matching ports 0",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{},
},
},
},
expectError: true,
},
{
desc: "Matching ports 0 (with external name)",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
Ports: []corev1.ServicePort{
{},
},
},
},
expectError: true,
},
{
desc: "Mismatching, only port(Ingress) defined",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{},
},
port: 80,
expectError: true,
},
{
desc: "Mismatching, only port(Ingress) defined with external name",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
},
},
port: 80,
expected: &corev1.ServicePort{
Port: 80,
},
},
{
desc: "Mismatching, only Service port defined",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Port: 80,
},
},
},
},
expectError: true,
},
{
desc: "Mismatching, only Service port defined with external name",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
Ports: []corev1.ServicePort{
{
Port: 80,
},
},
},
},
expectError: true,
},
{
desc: "Two different ports defined",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Port: 80,
},
},
},
},
port: 443,
expectError: true,
},
{
desc: "Two different ports defined (with external name)",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
Ports: []corev1.ServicePort{
{
Port: 80,
},
},
},
},
port: 443,
expected: &corev1.ServicePort{
Port: 443,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual, err := getServicePort(test.svc, test.port)
if test.expectError {
assert.Error(t, err)
} else {
assert.Equal(t, test.expected, actual)
}
})
}
}