Support for all services kinds (and sticky) in CRD

Co-authored-by: Jean-Baptiste Doumenjou <jb.doumenjou@gmail.com>
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
mpl 2019-11-14 19:28:04 +01:00 committed by Traefiker Bot
parent 424e2a9439
commit f30a52c2dc
42 changed files with 3344 additions and 354 deletions

View file

@ -56,6 +56,94 @@ spec:
singular: ingressroutetcp singular: ingressroutetcp
scope: Namespaced scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: traefikservices.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TraefikService
plural: traefikservices
singular: traefikservice
scope: Namespaced
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr2
namespace: default
spec:
weighted:
services:
- name: s1
weight: 1
port: 80
# Optional, as it is the default value
kind: Service
- name: s3
weight: 1
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: default
spec:
weighted:
services:
- name: wrr2
kind: TraefikService
weight: 1
- name: s3
weight: 1
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror1
namespace: default
spec:
mirroring:
name: s1
port: 80
mirrors:
- name: s3
percent: 20
port: 80
- name: mirror2
kind: TraefikService
percent: 20
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror2
namespace: default
spec:
mirroring:
name: wrr2
kind: TraefikService
mirrors:
- name: s2
# Optional, as it is the default value
kind: Service
percent: 20
port: 80
--- ---
apiVersion: traefik.containo.us/v1alpha1 apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute kind: IngressRoute
@ -100,9 +188,19 @@ spec:
- match: PathPrefix(`/misc`) - match: PathPrefix(`/misc`)
services: services:
- name: s3 - name: s3
# Optional, as it is the default value
kind: Service
port: 8443 port: 8443
# scheme allow to override the scheme for the service. (ex: https or h2c) # scheme allow to override the scheme for the service. (ex: https or h2c)
scheme: https scheme: https
- match: PathPrefix(`/lb`)
services:
- name: wrr1
kind: TraefikService
- match: PathPrefix(`/mirrored`)
services:
- name: mirror1
kind: TraefikService
# use an empty tls object for TLS with Let's Encrypt # use an empty tls object for TLS with Let's Encrypt
tls: tls:
secretName: supersecret secretName: supersecret

View file

@ -0,0 +1,13 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: traefikservices.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TraefikService
plural: traefikservices
singular: traefikservice
scope: Namespaced

View file

@ -116,6 +116,84 @@ spec:
More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md). More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md).
### Services
If one needs a setup more sophisticated than a load-balancer of servers (which is a Kubernetes Service kind behind the scenes),
one can define and use additional service objects specific to Traefik, based on the `TraefikService` kind defined in the CRD below.
```yaml
--8<-- "content/routing/providers/crd_traefikservice.yml"
```
Once the `TraefikService` kind has been registered with the Kubernetes cluster, it can then be used in `IngressRoute` definitions
(as well as recursively in other Traefik Services), such as below.
Note how the `name` field in the IngressRoute definition now refers to a TraefikService instead of a (Kubernetes) Service.
The reason this is allowed, and why a `name` can refer either to a TraefikService or a Service,
is because the `kind` field is used to break the ambiguity. The allowed values for this field are `TraefikService`, or `Service`
(which is the default value).
```yaml
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: default
spec:
weighted:
services:
- name: s2
kind: Service
port: 80
weight: 1
- name: s3
weight: 1
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror1
namespace: default
spec:
mirroring:
name: wrr1
kind: TraefikService
mirrors:
- name: s1
percent: 20
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroutebar
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`bar.com`) && PathPrefix(`/foo`)
kind: Rule
services:
- name: mirror1
namespace: default
kind: TraefikService
```
!!! important "References and namespaces"
If the optional `namespace` attribute is not set, the configuration will be applied with the namespace of the current resource.
Additionally, when the definition of the `TraefikService` is from another provider,
the cross-provider syntax (service@provider) should be used to refer to the `TraefikService`, just as in the middleware case.
Specifying a namespace attribute in this case would not make any sense, and will be ignored (except if the provider is `kubernetescrd`).
### TLS Option ### TLS Option
Additionally, to allow for the use of TLS options in an IngressRoute, we defined the CRD below for the TLSOption kind. Additionally, to allow for the use of TLS options in an IngressRoute, we defined the CRD below for the TLSOption kind.

View file

@ -57,6 +57,21 @@ spec:
singular: tlsoption singular: tlsoption
scope: Namespaced scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: traefikservices.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TraefikService
plural: traefikservices
singular: traefikservice
scope: Namespaced
--- ---
kind: ClusterRole kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1 apiVersion: rbac.authorization.k8s.io/v1beta1
@ -120,6 +135,14 @@ rules:
- get - get
- list - list
- watch - watch
- apiGroups:
- traefik.containo.us
resources:
- traefikservices
verbs:
- get
- list
- watch
--- ---
kind: ClusterRoleBinding kind: ClusterRoleBinding

View file

@ -56,3 +56,18 @@ spec:
plural: tlsoptions plural: tlsoptions
singular: tlsoption singular: tlsoption
scope: Namespaced scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: traefikservices.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TraefikService
plural: traefikservices
singular: traefikservice
scope: Namespaced

View file

@ -0,0 +1,62 @@
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror1
namespace: default
spec:
mirroring:
name: whoami
port: 80
mirrors:
- name: whoami
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: default
spec:
weighted:
services:
- name: mirror1
kind: TraefikService
- name: whoami
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test3.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/wrr1`)
kind: Rule
services:
- name: wrr1
kind: TraefikService
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: api.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: PathPrefix(`/api`)
kind: Rule
services:
- name: api@internal
kind: TraefikService

View file

@ -6,7 +6,6 @@
level = "DEBUG" level = "DEBUG"
[api] [api]
insecure = true
[entryPoints] [entryPoints]
[entryPoints.footcp] [entryPoints.footcp]

View file

@ -2,12 +2,12 @@
checkNewVersion = false checkNewVersion = false
sendAnonymousUsage = false sendAnonymousUsage = false
[api]
insecure = true
[log] [log]
level = "DEBUG" level = "DEBUG"
[api]
insecure = true
[entryPoints] [entryPoints]
[entryPoints.web] [entryPoints.web]
address = ":8000" address = ":8000"

View file

@ -71,7 +71,7 @@ func (s *K8sSuite) TestIngressConfiguration(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
testConfiguration(c, "testdata/rawdata-ingress.json") testConfiguration(c, "testdata/rawdata-ingress.json", "8080")
} }
func (s *K8sSuite) TestCRDConfiguration(c *check.C) { func (s *K8sSuite) TestCRDConfiguration(c *check.C) {
@ -82,11 +82,11 @@ func (s *K8sSuite) TestCRDConfiguration(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
testConfiguration(c, "testdata/rawdata-crd.json") testConfiguration(c, "testdata/rawdata-crd.json", "8000")
} }
func testConfiguration(c *check.C, path string) { func testConfiguration(c *check.C, path, apiPort string) {
err := try.GetRequest("http://127.0.0.1:8080/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`)) err := try.GetRequest("http://127.0.0.1:"+apiPort+"/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
expectedJSON := filepath.FromSlash(path) expectedJSON := filepath.FromSlash(path)
@ -99,7 +99,7 @@ func testConfiguration(c *check.C, path string) {
} }
var buf bytes.Buffer var buf bytes.Buffer
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 40*time.Second, try.StatusCodeIs(http.StatusOK), matchesConfig(expectedJSON, &buf)) err = try.GetRequest("http://127.0.0.1:"+apiPort+"/api/rawdata", 40*time.Second, try.StatusCodeIs(http.StatusOK), matchesConfig(expectedJSON, &buf))
if !*updateExpected { if !*updateExpected {
if err != nil { if err != nil {

View file

@ -1,38 +1,21 @@
{ {
"routers": { "routers": {
"api@internal": { "default-api-route-29f28a463fb5d5ba16d2@kubernetescrd": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 9223372036854775806,
"status": "enabled",
"using": [
"traefik"
]
},
"dashboard@internal": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 9223372036854775805,
"status": "enabled",
"using": [
"traefik"
]
},
"default-test.route-6b204d94623b3df4370c@kubernetescrd": {
"entryPoints": [ "entryPoints": [
"web" "web"
], ],
"service": "default-test.route-6b204d94623b3df4370c", "service": "api@internal",
"rule": "PathPrefix(`/api`)",
"status": "enabled",
"using": [
"web"
]
},
"default-test-route-6b204d94623b3df4370c@kubernetescrd": {
"entryPoints": [
"web"
],
"service": "default-test-route-6b204d94623b3df4370c",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/bar`)", "rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/bar`)",
"priority": 12, "priority": 12,
"tls": { "tls": {
@ -43,45 +26,33 @@
"web" "web"
] ]
}, },
"default-test2.route-23c7f4c450289ee29016@kubernetescrd": { "default-test2-route-23c7f4c450289ee29016@kubernetescrd": {
"entryPoints": [ "entryPoints": [
"web" "web"
], ],
"middlewares": [ "middlewares": [
"default-mychain@kubernetescrd" "default-mychain@kubernetescrd"
], ],
"service": "default-test2.route-23c7f4c450289ee29016", "service": "default-test2-route-23c7f4c450289ee29016",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/tobestripped`)", "rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/tobestripped`)",
"status": "enabled", "status": "enabled",
"using": [ "using": [
"web" "web"
] ]
},
"default-test3-route-7d0ac22d3d8db4b82618@kubernetescrd": {
"entryPoints": [
"web"
],
"service": "default-wrr1",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/wrr1`)",
"status": "enabled",
"using": [
"web"
]
} }
}, },
"middlewares": { "middlewares": {
"dashboard_redirect@internal": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"dashboard_stripprefix@internal": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"default-mychain@kubernetescrd": { "default-mychain@kubernetescrd": {
"chain": { "chain": {
"middlewares": [ "middlewares": [
@ -90,7 +61,7 @@
}, },
"status": "enabled", "status": "enabled",
"usedBy": [ "usedBy": [
"default-test2.route-23c7f4c450289ee29016@kubernetescrd" "default-test2-route-23c7f4c450289ee29016@kubernetescrd"
] ]
}, },
"default-stripprefix@kubernetescrd": { "default-stripprefix@kubernetescrd": {
@ -106,56 +77,100 @@
"api@internal": { "api@internal": {
"status": "enabled", "status": "enabled",
"usedBy": [ "usedBy": [
"api@internal" "default-api-route-29f28a463fb5d5ba16d2@kubernetescrd"
] ]
}, },
"dashboard@internal": { "dashboard@internal": {
"status": "enabled"
},
"default-mirror1@kubernetescrd": {
"mirroring": {
"service": "default-whoami-80",
"mirrors": [
{
"name": "default-whoami-80"
}
]
},
"status": "enabled"
},
"default-test-route-6b204d94623b3df4370c@kubernetescrd": {
"loadBalancer": {
"servers": [
{
"url": "http://10.42.0.3:80"
},
{
"url": "http://10.42.0.5:80"
}
],
"passHostHeader": true
},
"status": "enabled", "status": "enabled",
"usedBy": [ "usedBy": [
"dashboard@internal" "default-test-route-6b204d94623b3df4370c@kubernetescrd"
],
"serverStatus": {
"http://10.42.0.3:80": "UP",
"http://10.42.0.5:80": "UP"
}
},
"default-test2-route-23c7f4c450289ee29016@kubernetescrd": {
"loadBalancer": {
"servers": [
{
"url": "http://10.42.0.3:80"
},
{
"url": "http://10.42.0.5:80"
}
],
"passHostHeader": true
},
"status": "enabled",
"usedBy": [
"default-test2-route-23c7f4c450289ee29016@kubernetescrd"
],
"serverStatus": {
"http://10.42.0.3:80": "UP",
"http://10.42.0.5:80": "UP"
}
},
"default-whoami-80@kubernetescrd": {
"loadBalancer": {
"servers": [
{
"url": "http://10.42.0.3:80"
},
{
"url": "http://10.42.0.5:80"
}
],
"passHostHeader": true
},
"status": "enabled",
"serverStatus": {
"http://10.42.0.3:80": "UP",
"http://10.42.0.5:80": "UP"
}
},
"default-wrr1@kubernetescrd": {
"weighted": {
"services": [
{
"name": "default-mirror1",
"weight": 1
},
{
"name": "default-whoami-80",
"weight": 1
}
]
},
"status": "enabled",
"usedBy": [
"default-test3-route-7d0ac22d3d8db4b82618@kubernetescrd"
] ]
},
"default-test.route-6b204d94623b3df4370c@kubernetescrd": {
"loadBalancer": {
"servers": [
{
"url": "http://10.42.0.3:80"
},
{
"url": "http://10.42.0.6:80"
}
],
"passHostHeader": true
},
"status": "enabled",
"usedBy": [
"default-test.route-6b204d94623b3df4370c@kubernetescrd"
],
"serverStatus": {
"http://10.42.0.3:80": "UP",
"http://10.42.0.6:80": "UP"
}
},
"default-test2.route-23c7f4c450289ee29016@kubernetescrd": {
"loadBalancer": {
"servers": [
{
"url": "http://10.42.0.3:80"
},
{
"url": "http://10.42.0.6:80"
}
],
"passHostHeader": true
},
"status": "enabled",
"usedBy": [
"default-test2.route-23c7f4c450289ee29016@kubernetescrd"
],
"serverStatus": {
"http://10.42.0.3:80": "UP",
"http://10.42.0.6:80": "UP"
}
} }
}, },
"tcpRouters": { "tcpRouters": {
@ -181,10 +196,10 @@
"terminationDelay": 100, "terminationDelay": 100,
"servers": [ "servers": [
{ {
"address": "10.42.0.2:8080" "address": "10.42.0.4:8080"
}, },
{ {
"address": "10.42.0.5:8080" "address": "10.42.0.6:8080"
} }
] ]
}, },

View file

@ -99,10 +99,10 @@
"loadBalancer": { "loadBalancer": {
"servers": [ "servers": [
{ {
"url": "http://10.42.0.2:80" "url": "http://10.42.0.4:80"
}, },
{ {
"url": "http://10.42.0.3:80" "url": "http://10.42.0.6:80"
} }
], ],
"passHostHeader": true "passHostHeader": true
@ -114,8 +114,8 @@
"whoami-test-whoami@kubernetes" "whoami-test-whoami@kubernetes"
], ],
"serverStatus": { "serverStatus": {
"http://10.42.0.2:80": "UP", "http://10.42.0.4:80": "UP",
"http://10.42.0.3:80": "UP" "http://10.42.0.6:80": "UP"
} }
} }
} }

View file

@ -49,6 +49,8 @@ type Client interface {
GetIngressRoutes() []*v1alpha1.IngressRoute GetIngressRoutes() []*v1alpha1.IngressRoute
GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP
GetMiddlewares() []*v1alpha1.Middleware GetMiddlewares() []*v1alpha1.Middleware
GetTraefikService(namespace, name string) (*v1alpha1.TraefikService, bool, error)
GetTraefikServices() []*v1alpha1.TraefikService
GetTLSOptions() []*v1alpha1.TLSOption GetTLSOptions() []*v1alpha1.TLSOption
GetIngresses() []*extensionsv1beta1.Ingress GetIngresses() []*extensionsv1beta1.Ingress
@ -100,7 +102,7 @@ func newClientImpl(csKube *kubernetes.Clientset, csCrd *versioned.Clientset) *cl
func newInClusterClient(endpoint string) (*clientWrapper, error) { func newInClusterClient(endpoint string) (*clientWrapper, error) {
config, err := rest.InClusterConfig() config, err := rest.InClusterConfig()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create in-cluster configuration: %s", err) return nil, fmt.Errorf("failed to create in-cluster configuration: %v", err)
} }
if endpoint != "" { if endpoint != "" {
@ -134,7 +136,7 @@ func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrappe
if caFilePath != "" { if caFilePath != "" {
caData, err := ioutil.ReadFile(caFilePath) caData, err := ioutil.ReadFile(caFilePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read CA file %s: %s", caFilePath, err) return nil, fmt.Errorf("failed to read CA file %s: %v", caFilePath, err)
} }
config.TLSClientConfig = rest.TLSClientConfig{CAData: caData} config.TLSClientConfig = rest.TLSClientConfig{CAData: caData}
@ -160,6 +162,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
factoryCrd.Traefik().V1alpha1().Middlewares().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().Middlewares().Informer().AddEventHandler(eventHandler)
factoryCrd.Traefik().V1alpha1().IngressRouteTCPs().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().IngressRouteTCPs().Informer().AddEventHandler(eventHandler)
factoryCrd.Traefik().V1alpha1().TLSOptions().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().TLSOptions().Informer().AddEventHandler(eventHandler)
factoryCrd.Traefik().V1alpha1().TraefikServices().Informer().AddEventHandler(eventHandler)
factoryKube := informers.NewFilteredSharedInformerFactory(c.csKube, resyncPeriod, ns, nil) factoryKube := informers.NewFilteredSharedInformerFactory(c.csKube, resyncPeriod, ns, nil)
factoryKube.Extensions().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler) factoryKube.Extensions().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler)
@ -207,7 +210,7 @@ func (c *clientWrapper) GetIngressRoutes() []*v1alpha1.IngressRoute {
for ns, factory := range c.factoriesCrd { for ns, factory := range c.factoriesCrd {
ings, err := factory.Traefik().V1alpha1().IngressRoutes().Lister().List(c.labelSelector) ings, err := factory.Traefik().V1alpha1().IngressRoutes().Lister().List(c.labelSelector)
if err != nil { if err != nil {
log.Errorf("Failed to list ingresses in namespace %s: %s", ns, err) log.Errorf("Failed to list ingress routes in namespace %s: %v", ns, err)
} }
result = append(result, ings...) result = append(result, ings...)
} }
@ -221,7 +224,7 @@ func (c *clientWrapper) GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP {
for ns, factory := range c.factoriesCrd { for ns, factory := range c.factoriesCrd {
ings, err := factory.Traefik().V1alpha1().IngressRouteTCPs().Lister().List(c.labelSelector) ings, err := factory.Traefik().V1alpha1().IngressRouteTCPs().Lister().List(c.labelSelector)
if err != nil { if err != nil {
log.Errorf("Failed to list tcp ingresses in namespace %s: %s", ns, err) log.Errorf("Failed to list tcp ingress routes in namespace %s: %v", ns, err)
} }
result = append(result, ings...) result = append(result, ings...)
} }
@ -235,7 +238,7 @@ func (c *clientWrapper) GetMiddlewares() []*v1alpha1.Middleware {
for ns, factory := range c.factoriesCrd { for ns, factory := range c.factoriesCrd {
middlewares, err := factory.Traefik().V1alpha1().Middlewares().Lister().List(c.labelSelector) middlewares, err := factory.Traefik().V1alpha1().Middlewares().Lister().List(c.labelSelector)
if err != nil { if err != nil {
log.Errorf("Failed to list middlewares in namespace %s: %s", ns, err) log.Errorf("Failed to list middlewares in namespace %s: %v", ns, err)
} }
result = append(result, middlewares...) result = append(result, middlewares...)
} }
@ -243,6 +246,32 @@ func (c *clientWrapper) GetMiddlewares() []*v1alpha1.Middleware {
return result return result
} }
// GetTraefikService returns the named service from the given namespace.
func (c *clientWrapper) GetTraefikService(namespace, name string) (*v1alpha1.TraefikService, bool, error) {
if !c.isWatchedNamespace(namespace) {
return nil, false, fmt.Errorf("failed to get service %s/%s: namespace is not within watched namespaces", namespace, name)
}
service, err := c.factoriesCrd[c.lookupNamespace(namespace)].Traefik().V1alpha1().TraefikServices().Lister().TraefikServices(namespace).Get(name)
exist, err := translateNotFoundError(err)
return service, exist, err
}
func (c *clientWrapper) GetTraefikServices() []*v1alpha1.TraefikService {
var result []*v1alpha1.TraefikService
for ns, factory := range c.factoriesCrd {
ings, err := factory.Traefik().V1alpha1().TraefikServices().Lister().List(c.labelSelector)
if err != nil {
log.Errorf("Failed to list Traefik services in namespace %s: %v", ns, err)
}
result = append(result, ings...)
}
return result
}
// GetTLSOptions // GetTLSOptions
func (c *clientWrapper) GetTLSOptions() []*v1alpha1.TLSOption { func (c *clientWrapper) GetTLSOptions() []*v1alpha1.TLSOption {
var result []*v1alpha1.TLSOption var result []*v1alpha1.TLSOption
@ -250,7 +279,7 @@ func (c *clientWrapper) GetTLSOptions() []*v1alpha1.TLSOption {
for ns, factory := range c.factoriesCrd { for ns, factory := range c.factoriesCrd {
options, err := factory.Traefik().V1alpha1().TLSOptions().Lister().List(c.labelSelector) options, err := factory.Traefik().V1alpha1().TLSOptions().Lister().List(c.labelSelector)
if err != nil { if err != nil {
log.Errorf("Failed to list tls options in namespace %s: %s", ns, err) log.Errorf("Failed to list tls options in namespace %s: %v", ns, err)
} }
result = append(result, options...) result = append(result, options...)
} }
@ -264,7 +293,7 @@ func (c *clientWrapper) GetIngresses() []*extensionsv1beta1.Ingress {
for ns, factory := range c.factoriesKube { for ns, factory := range c.factoriesKube {
ings, err := factory.Extensions().V1beta1().Ingresses().Lister().List(c.labelSelector) ings, err := factory.Extensions().V1beta1().Ingresses().Lister().List(c.labelSelector)
if err != nil { if err != nil {
log.Errorf("Failed to list ingresses in namespace %s: %s", ns, err) log.Errorf("Failed to list ingresses in namespace %s: %v", ns, err)
} }
result = append(result, ings...) result = append(result, ings...)
} }

View file

@ -38,6 +38,7 @@ type clientMock struct {
ingressRouteTCPs []*v1alpha1.IngressRouteTCP ingressRouteTCPs []*v1alpha1.IngressRouteTCP
middlewares []*v1alpha1.Middleware middlewares []*v1alpha1.Middleware
tlsOptions []*v1alpha1.TLSOption tlsOptions []*v1alpha1.TLSOption
traefikServices []*v1alpha1.TraefikService
watchChan chan interface{} watchChan chan interface{}
} }
@ -64,6 +65,8 @@ func newClientMock(paths ...string) clientMock {
c.ingressRouteTCPs = append(c.ingressRouteTCPs, o) c.ingressRouteTCPs = append(c.ingressRouteTCPs, o)
case *v1alpha1.Middleware: case *v1alpha1.Middleware:
c.middlewares = append(c.middlewares, o) c.middlewares = append(c.middlewares, o)
case *v1alpha1.TraefikService:
c.traefikServices = append(c.traefikServices, o)
case *v1alpha1.TLSOption: case *v1alpha1.TLSOption:
c.tlsOptions = append(c.tlsOptions, o) c.tlsOptions = append(c.tlsOptions, o)
case *v1beta12.Ingress: case *v1beta12.Ingress:
@ -91,6 +94,20 @@ func (c clientMock) GetMiddlewares() []*v1alpha1.Middleware {
return c.middlewares return c.middlewares
} }
func (c clientMock) GetTraefikService(namespace, name string) (*v1alpha1.TraefikService, bool, error) {
for _, svc := range c.traefikServices {
if svc.Namespace == namespace && svc.Name == name {
return svc, true, nil
}
}
return nil, false, nil
}
func (c clientMock) GetTraefikServices() []*v1alpha1.TraefikService {
return c.traefikServices
}
func (c clientMock) GetTLSOptions() []*v1alpha1.TLSOption { func (c clientMock) GetTLSOptions() []*v1alpha1.TLSOption {
return c.tlsOptions return c.tlsOptions
} }

View file

@ -0,0 +1,96 @@
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami4
namespace: default
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami4
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami4
------
kind: Endpoints
apiVersion: v1
metadata:
name: whoami5
namespace: default
subsets:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami5
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami5
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror1
namespace: default
spec:
mirroring:
name: whoami5
kind: Service
port: 8080
mirrors:
- name: whoami4
kind: Service
percent: 50
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/foo`)
kind: Rule
priority: 12
services:
- name: mirror1
kind: TraefikService

View file

@ -0,0 +1,122 @@
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami4
namespace: default
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami4
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami4
------
kind: Endpoints
apiVersion: v1
metadata:
name: whoami5
namespace: default
subsets:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami5
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami5
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr2
namespace: default
spec:
weighted:
services:
- name: whoami5
weight: 1
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: default
spec:
weighted:
services:
- name: whoami4
weight: 1
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror1
namespace: default
spec:
mirroring:
name: wrr1
kind: TraefikService
mirrors:
- name: wrr2
kind: TraefikService
percent: 30
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/foo`)
kind: Rule
priority: 12
services:
- name: mirror1
kind: TraefikService

View file

@ -0,0 +1,271 @@
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami6
namespace: baz
subsets:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
- name: web
port: 8080
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami5
namespace: foo
subsets:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami4
namespace: foo
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami6
namespace: baz
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami6
---
apiVersion: v1
kind: Service
metadata:
name: whoami5
namespace: foo
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami5
---
apiVersion: v1
kind: Service
metadata:
name: whoami4
namespace: foo
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami4
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror1
namespace: foo
spec:
mirroring:
name: whoami5
port: 8080
namespace: foo
mirrors:
- name: whoami4
port: 8080
- name: whoami6
port: 8080
namespace: baz
- name: mirrored
kind: TraefikService
namespace: bar
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr2
namespace: bar
spec:
weighted:
services:
- name: whoami5
namespace: foo
weight: 1
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror2
namespace: foo
spec:
mirroring:
name: whoami5
port: 8080
mirrors:
- name: whoami4
port: 8080
- name: whoami6
port: 8080
namespace: baz
- name: mirrored
kind: TraefikService
namespace: bar
- name: wrr1
kind: TraefikService
namespace: foo
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror3
namespace: foo
spec:
mirroring:
name: wrr1
kind: TraefikService
namespace: foo
mirrors:
- name: whoami4
port: 8080
- name: whoami6
port: 8080
namespace: baz
- name: mirrored
kind: TraefikService
namespace: bar
- name: wrr1
kind: TraefikService
namespace: foo
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror4
namespace: foo
spec:
mirroring:
name: wrr1
kind: TraefikService
mirrors:
- name: whoami4
port: 8080
- name: whoami6
port: 8080
namespace: baz
- name: mirrored
kind: TraefikService
namespace: bar
- name: wrr1
kind: TraefikService
namespace: foo
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirrored
namespace: bar
spec:
mirroring:
name: whoami6
port: 8080
namespace: baz
mirrors:
- name: whoami4
percent: 50
port: 8080
namespace: foo
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: foo
spec:
weighted:
services:
- name: whoami4
port: 8080
- name: whoami6
port: 8080
namespace: baz
- name: mirror1
kind: TraefikService
- name: wrr2
kind: TraefikService
namespace: bar
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/foo`)
kind: Rule
priority: 12
services:
- name: whoami6
port: 8080
namespace: baz
- name: wrr1
kind: TraefikService
namespace: foo
- name: mirror2
kind: TraefikService
namespace: foo
- name: mirror3
kind: TraefikService
namespace: foo
- name: mirror4
kind: TraefikService
namespace: foo

View file

@ -0,0 +1,63 @@
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami5
namespace: default
subsets:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami5
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami5
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: default
spec:
weighted:
services:
- name: whoami5
kind: Service
weight: 1
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/foo`)
kind: Rule
priority: 12
services:
- name: wrr1
kind: TraefikService

View file

@ -0,0 +1,174 @@
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami4
namespace: default
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 80
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami5
namespace: default
subsets:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami6
namespace: default
subsets:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
- name: web
port: 80
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami7
namespace: default
subsets:
- addresses:
- ip: 10.10.0.7
- ip: 10.10.0.8
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami4
namespace: default
spec:
ports:
- name: web
port: 80
selector:
app: containous
task: whoami4
---
apiVersion: v1
kind: Service
metadata:
name: whoami5
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami5
---
apiVersion: v1
kind: Service
metadata:
name: whoami6
namespace: default
spec:
ports:
- name: web
port: 80
selector:
app: containous
task: whoami6
---
apiVersion: v1
kind: Service
metadata:
name: whoami7
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami7
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: default
spec:
weighted:
services:
- name: whoami4
port: 80
weight: 1
- name: whoami5
port: 8080
weight: 1
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr2
namespace: default
spec:
weighted:
services:
- name: whoami6
port: 80
weight: 1
- name: whoami7
port: 8080
weight: 1
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/foo`)
kind: Rule
priority: 12
services:
- name: wrr1
kind: TraefikService
- name: wrr2
kind: TraefikService

View file

@ -0,0 +1,79 @@
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami5
namespace: default
subsets:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr2
namespace: default
spec:
weighted:
services:
- name: whoami5
weight: 1
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami5
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami5
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: default
spec:
weighted:
services:
- name: wrr2
kind: TraefikService
weight: 1
- name: whoami5
weight: 1
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/foo`)
kind: Rule
priority: 12
services:
- name: wrr1
kind: TraefikService

View file

@ -0,0 +1,128 @@
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami5
namespace: default
subsets:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami4
namespace: default
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami5
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami5
---
apiVersion: v1
kind: Service
metadata:
name: whoami4
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami4
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr2
namespace: default
spec:
weighted:
services:
- name: whoami5
weight: 1
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: mirror1
namespace: default
spec:
mirroring:
name: whoami5
port: 8080
mirrors:
- name: whoami4
percent: 50
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: default
spec:
weighted:
services:
- name: wrr2
kind: TraefikService
weight: 1
- name: whoami5
weight: 1
port: 8080
- name: mirror1
kind: TraefikService
weight: 1
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/foo`)
kind: Rule
priority: 12
services:
- name: wrr1
kind: TraefikService

View file

@ -0,0 +1,44 @@
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoami5
namespace: default
subsets:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: web
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoami5
namespace: default
spec:
ports:
- name: web
port: 8080
selector:
app: containous
task: whoami5
---
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: wrr1
namespace: default
spec:
weighted:
services:
- name: whoami5
kind: Service
weight: 1
port: 8080

View file

@ -52,6 +52,10 @@ func (c *FakeTraefikV1alpha1) TLSOptions(namespace string) v1alpha1.TLSOptionInt
return &FakeTLSOptions{c, namespace} return &FakeTLSOptions{c, namespace}
} }
func (c *FakeTraefikV1alpha1) TraefikServices(namespace string) v1alpha1.TraefikServiceInterface {
return &FakeTraefikServices{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate // RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation. // with API server by this client implementation.
func (c *FakeTraefikV1alpha1) RESTClient() rest.Interface { func (c *FakeTraefikV1alpha1) RESTClient() rest.Interface {

View file

@ -0,0 +1,136 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2019 Containous SAS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeTraefikServices implements TraefikServiceInterface
type FakeTraefikServices struct {
Fake *FakeTraefikV1alpha1
ns string
}
var traefikservicesResource = schema.GroupVersionResource{Group: "traefik.containo.us", Version: "v1alpha1", Resource: "traefikservices"}
var traefikservicesKind = schema.GroupVersionKind{Group: "traefik.containo.us", Version: "v1alpha1", Kind: "TraefikService"}
// Get takes name of the traefikService, and returns the corresponding traefikService object, and an error if there is any.
func (c *FakeTraefikServices) Get(name string, options v1.GetOptions) (result *v1alpha1.TraefikService, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(traefikservicesResource, c.ns, name), &v1alpha1.TraefikService{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.TraefikService), err
}
// List takes label and field selectors, and returns the list of TraefikServices that match those selectors.
func (c *FakeTraefikServices) List(opts v1.ListOptions) (result *v1alpha1.TraefikServiceList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(traefikservicesResource, traefikservicesKind, c.ns, opts), &v1alpha1.TraefikServiceList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.TraefikServiceList{ListMeta: obj.(*v1alpha1.TraefikServiceList).ListMeta}
for _, item := range obj.(*v1alpha1.TraefikServiceList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested traefikServices.
func (c *FakeTraefikServices) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(traefikservicesResource, c.ns, opts))
}
// Create takes the representation of a traefikService and creates it. Returns the server's representation of the traefikService, and an error, if there is any.
func (c *FakeTraefikServices) Create(traefikService *v1alpha1.TraefikService) (result *v1alpha1.TraefikService, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(traefikservicesResource, c.ns, traefikService), &v1alpha1.TraefikService{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.TraefikService), err
}
// Update takes the representation of a traefikService and updates it. Returns the server's representation of the traefikService, and an error, if there is any.
func (c *FakeTraefikServices) Update(traefikService *v1alpha1.TraefikService) (result *v1alpha1.TraefikService, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(traefikservicesResource, c.ns, traefikService), &v1alpha1.TraefikService{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.TraefikService), err
}
// Delete takes name of the traefikService and deletes it. Returns an error if one occurs.
func (c *FakeTraefikServices) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(traefikservicesResource, c.ns, name), &v1alpha1.TraefikService{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeTraefikServices) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(traefikservicesResource, c.ns, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha1.TraefikServiceList{})
return err
}
// Patch applies the patch and returns the patched traefikService.
func (c *FakeTraefikServices) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TraefikService, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(traefikservicesResource, c.ns, name, pt, data, subresources...), &v1alpha1.TraefikService{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.TraefikService), err
}

View file

@ -33,3 +33,5 @@ type IngressRouteTCPExpansion interface{}
type MiddlewareExpansion interface{} type MiddlewareExpansion interface{}
type TLSOptionExpansion interface{} type TLSOptionExpansion interface{}
type TraefikServiceExpansion interface{}

View file

@ -38,6 +38,7 @@ type TraefikV1alpha1Interface interface {
IngressRouteTCPsGetter IngressRouteTCPsGetter
MiddlewaresGetter MiddlewaresGetter
TLSOptionsGetter TLSOptionsGetter
TraefikServicesGetter
} }
// TraefikV1alpha1Client is used to interact with features provided by the traefik.containo.us group. // TraefikV1alpha1Client is used to interact with features provided by the traefik.containo.us group.
@ -61,6 +62,10 @@ func (c *TraefikV1alpha1Client) TLSOptions(namespace string) TLSOptionInterface
return newTLSOptions(c, namespace) return newTLSOptions(c, namespace)
} }
func (c *TraefikV1alpha1Client) TraefikServices(namespace string) TraefikServiceInterface {
return newTraefikServices(c, namespace)
}
// NewForConfig creates a new TraefikV1alpha1Client for the given config. // NewForConfig creates a new TraefikV1alpha1Client for the given config.
func NewForConfig(c *rest.Config) (*TraefikV1alpha1Client, error) { func NewForConfig(c *rest.Config) (*TraefikV1alpha1Client, error) {
config := *c config := *c

View file

@ -0,0 +1,182 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2019 Containous SAS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"time"
scheme "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme"
v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// TraefikServicesGetter has a method to return a TraefikServiceInterface.
// A group's client should implement this interface.
type TraefikServicesGetter interface {
TraefikServices(namespace string) TraefikServiceInterface
}
// TraefikServiceInterface has methods to work with TraefikService resources.
type TraefikServiceInterface interface {
Create(*v1alpha1.TraefikService) (*v1alpha1.TraefikService, error)
Update(*v1alpha1.TraefikService) (*v1alpha1.TraefikService, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha1.TraefikService, error)
List(opts v1.ListOptions) (*v1alpha1.TraefikServiceList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TraefikService, err error)
TraefikServiceExpansion
}
// traefikServices implements TraefikServiceInterface
type traefikServices struct {
client rest.Interface
ns string
}
// newTraefikServices returns a TraefikServices
func newTraefikServices(c *TraefikV1alpha1Client, namespace string) *traefikServices {
return &traefikServices{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the traefikService, and returns the corresponding traefikService object, and an error if there is any.
func (c *traefikServices) Get(name string, options v1.GetOptions) (result *v1alpha1.TraefikService, err error) {
result = &v1alpha1.TraefikService{}
err = c.client.Get().
Namespace(c.ns).
Resource("traefikservices").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of TraefikServices that match those selectors.
func (c *traefikServices) List(opts v1.ListOptions) (result *v1alpha1.TraefikServiceList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.TraefikServiceList{}
err = c.client.Get().
Namespace(c.ns).
Resource("traefikservices").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested traefikServices.
func (c *traefikServices) Watch(opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("traefikservices").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch()
}
// Create takes the representation of a traefikService and creates it. Returns the server's representation of the traefikService, and an error, if there is any.
func (c *traefikServices) Create(traefikService *v1alpha1.TraefikService) (result *v1alpha1.TraefikService, err error) {
result = &v1alpha1.TraefikService{}
err = c.client.Post().
Namespace(c.ns).
Resource("traefikservices").
Body(traefikService).
Do().
Into(result)
return
}
// Update takes the representation of a traefikService and updates it. Returns the server's representation of the traefikService, and an error, if there is any.
func (c *traefikServices) Update(traefikService *v1alpha1.TraefikService) (result *v1alpha1.TraefikService, err error) {
result = &v1alpha1.TraefikService{}
err = c.client.Put().
Namespace(c.ns).
Resource("traefikservices").
Name(traefikService.Name).
Body(traefikService).
Do().
Into(result)
return
}
// Delete takes name of the traefikService and deletes it. Returns an error if one occurs.
func (c *traefikServices) Delete(name string, options *v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("traefikservices").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *traefikServices) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
var timeout time.Duration
if listOptions.TimeoutSeconds != nil {
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("traefikservices").
VersionedParams(&listOptions, scheme.ParameterCodec).
Timeout(timeout).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched traefikService.
func (c *traefikServices) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TraefikService, err error) {
result = &v1alpha1.TraefikService{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("traefikservices").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View file

@ -69,6 +69,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().Middlewares().Informer()}, nil return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().Middlewares().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("tlsoptions"): case v1alpha1.SchemeGroupVersion.WithResource("tlsoptions"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().TLSOptions().Informer()}, nil return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().TLSOptions().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("traefikservices"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().TraefikServices().Informer()}, nil
} }

View file

@ -40,6 +40,8 @@ type Interface interface {
Middlewares() MiddlewareInformer Middlewares() MiddlewareInformer
// TLSOptions returns a TLSOptionInformer. // TLSOptions returns a TLSOptionInformer.
TLSOptions() TLSOptionInformer TLSOptions() TLSOptionInformer
// TraefikServices returns a TraefikServiceInformer.
TraefikServices() TraefikServiceInformer
} }
type version struct { type version struct {
@ -72,3 +74,8 @@ func (v *version) Middlewares() MiddlewareInformer {
func (v *version) TLSOptions() TLSOptionInformer { func (v *version) TLSOptions() TLSOptionInformer {
return &tLSOptionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} return &tLSOptionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
} }
// TraefikServices returns a TraefikServiceInformer.
func (v *version) TraefikServices() TraefikServiceInformer {
return &traefikServiceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}

View file

@ -0,0 +1,97 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2019 Containous SAS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
time "time"
versioned "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned"
internalinterfaces "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1"
traefikv1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// TraefikServiceInformer provides access to a shared informer and lister for
// TraefikServices.
type TraefikServiceInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.TraefikServiceLister
}
type traefikServiceInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewTraefikServiceInformer constructs a new informer for TraefikService type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewTraefikServiceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredTraefikServiceInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredTraefikServiceInformer constructs a new informer for TraefikService type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredTraefikServiceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.TraefikV1alpha1().TraefikServices(namespace).List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.TraefikV1alpha1().TraefikServices(namespace).Watch(options)
},
},
&traefikv1alpha1.TraefikService{},
resyncPeriod,
indexers,
)
}
func (f *traefikServiceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredTraefikServiceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *traefikServiceInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&traefikv1alpha1.TraefikService{}, f.defaultInformer)
}
func (f *traefikServiceInformer) Lister() v1alpha1.TraefikServiceLister {
return v1alpha1.NewTraefikServiceLister(f.Informer().GetIndexer())
}

View file

@ -57,3 +57,11 @@ type TLSOptionListerExpansion interface{}
// TLSOptionNamespaceListerExpansion allows custom methods to be added to // TLSOptionNamespaceListerExpansion allows custom methods to be added to
// TLSOptionNamespaceLister. // TLSOptionNamespaceLister.
type TLSOptionNamespaceListerExpansion interface{} type TLSOptionNamespaceListerExpansion interface{}
// TraefikServiceListerExpansion allows custom methods to be added to
// TraefikServiceLister.
type TraefikServiceListerExpansion interface{}
// TraefikServiceNamespaceListerExpansion allows custom methods to be added to
// TraefikServiceNamespaceLister.
type TraefikServiceNamespaceListerExpansion interface{}

View file

@ -0,0 +1,102 @@
/*
The MIT License (MIT)
Copyright (c) 2016-2019 Containous SAS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// TraefikServiceLister helps list TraefikServices.
type TraefikServiceLister interface {
// List lists all TraefikServices in the indexer.
List(selector labels.Selector) (ret []*v1alpha1.TraefikService, err error)
// TraefikServices returns an object that can list and get TraefikServices.
TraefikServices(namespace string) TraefikServiceNamespaceLister
TraefikServiceListerExpansion
}
// traefikServiceLister implements the TraefikServiceLister interface.
type traefikServiceLister struct {
indexer cache.Indexer
}
// NewTraefikServiceLister returns a new TraefikServiceLister.
func NewTraefikServiceLister(indexer cache.Indexer) TraefikServiceLister {
return &traefikServiceLister{indexer: indexer}
}
// List lists all TraefikServices in the indexer.
func (s *traefikServiceLister) List(selector labels.Selector) (ret []*v1alpha1.TraefikService, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.TraefikService))
})
return ret, err
}
// TraefikServices returns an object that can list and get TraefikServices.
func (s *traefikServiceLister) TraefikServices(namespace string) TraefikServiceNamespaceLister {
return traefikServiceNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// TraefikServiceNamespaceLister helps list and get TraefikServices.
type TraefikServiceNamespaceLister interface {
// List lists all TraefikServices in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1alpha1.TraefikService, err error)
// Get retrieves the TraefikService from the indexer for a given namespace and name.
Get(name string) (*v1alpha1.TraefikService, error)
TraefikServiceNamespaceListerExpansion
}
// traefikServiceNamespaceLister implements the TraefikServiceNamespaceLister
// interface.
type traefikServiceNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all TraefikServices in the indexer for a given namespace.
func (s traefikServiceNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.TraefikService, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.TraefikService))
})
return ret, err
}
// Get retrieves the TraefikService from the indexer for a given namespace and name.
func (s traefikServiceNamespaceLister) Get(name string) (*v1alpha1.TraefikService, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("traefikservice"), name)
}
return obj.(*v1alpha1.TraefikService), nil
}

View file

@ -16,6 +16,7 @@ import (
"github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/job" "github.com/containous/traefik/v2/pkg/job"
"github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/tls" "github.com/containous/traefik/v2/pkg/tls"
@ -30,6 +31,11 @@ const (
traefikDefaultIngressClass = "traefik" traefikDefaultIngressClass = "traefik"
) )
const (
providerName = "kubernetescrd"
providerNamespaceSeparator = "@"
)
// Provider holds configurations of the provider. // Provider holds configurations of the provider.
type Provider struct { type Provider struct {
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
@ -52,7 +58,7 @@ func (p *Provider) newK8sClient(ctx context.Context, labelSelector string) (*cli
withEndpoint := "" withEndpoint := ""
if p.Endpoint != "" { if p.Endpoint != "" {
withEndpoint = fmt.Sprintf(" with endpoint %v", p.Endpoint) withEndpoint = fmt.Sprintf(" with endpoint %s", p.Endpoint)
} }
var client *clientWrapper var client *clientWrapper
@ -83,7 +89,7 @@ func (p *Provider) Init() error {
// Provide allows the k8s provider to provide configurations to traefik // Provide allows the k8s provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
ctxLog := log.With(context.Background(), log.Str(log.ProviderName, "kubernetescrd")) ctxLog := log.With(context.Background(), log.Str(log.ProviderName, providerName))
logger := log.FromContext(ctxLog) logger := log.FromContext(ctxLog)
logger.Debugf("Using label selector: %q", p.LabelSelector) logger.Debugf("Using label selector: %q", p.LabelSelector)
@ -120,11 +126,9 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
case <-stop: case <-stop:
return nil return nil
case event := <-eventsChan: case event := <-eventsChan:
// Note that event is the *first* event that came in during this // Note that event is the *first* event that came in during this throttling interval -- if we're hitting our throttle, we may have dropped events.
// throttling interval -- if we're hitting our throttle, we may have // This is fine, because we don't treat different event types differently.
// dropped events. This is fine, because we don't treat different // But if we do in the future, we'll need to track more information about the dropped events.
// event types differently. But if we do in the future, we'll need to
// track more information about the dropped events.
conf := p.loadConfigurationFromCRD(ctxLog, k8sClient) conf := p.loadConfigurationFromCRD(ctxLog, k8sClient)
confHash, err := hashstructure.Hash(conf, nil) confHash, err := hashstructure.Hash(conf, nil)
@ -136,25 +140,25 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
default: default:
p.lastConfiguration.Set(confHash) p.lastConfiguration.Set(confHash)
configurationChan <- dynamic.Message{ configurationChan <- dynamic.Message{
ProviderName: "kubernetescrd", ProviderName: providerName,
Configuration: conf, Configuration: conf,
} }
} }
// If we're throttling, we sleep here for the throttle duration to // If we're throttling,
// enforce that we don't refresh faster than our throttle. time.Sleep // we sleep here for the throttle duration to enforce that we don't refresh faster than our throttle.
// returns immediately if p.ThrottleDuration is 0 (no throttle). // time.Sleep returns immediately if p.ThrottleDuration is 0 (no throttle).
time.Sleep(throttleDuration) time.Sleep(throttleDuration)
} }
} }
} }
notify := func(err error, time time.Duration) { notify := func(err error, time time.Duration) {
logger.Errorf("Provider connection error: %s; retrying in %s", err, time) logger.Errorf("Provider connection error: %v; retrying in %s", err, time)
} }
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil { if err != nil {
logger.Errorf("Cannot connect to Provider: %s", err) logger.Errorf("Cannot connect to Provider: %v", err)
} }
}) })
@ -173,7 +177,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
} }
for _, middleware := range client.GetMiddlewares() { for _, middleware := range client.GetMiddlewares() {
id := makeID(middleware.Namespace, middleware.Name) id := provider.Normalize(makeID(middleware.Namespace, middleware.Name))
ctxMid := log.With(ctx, log.Str(log.MiddlewareName, id)) ctxMid := log.With(ctx, log.Str(log.MiddlewareName, id))
basicAuth, err := createBasicAuthMiddleware(client, middleware.Namespace, middleware.Spec.BasicAuth) basicAuth, err := createBasicAuthMiddleware(client, middleware.Namespace, middleware.Spec.BasicAuth)
@ -231,6 +235,16 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
} }
} }
cb := configBuilder{client}
for _, service := range client.GetTraefikServices() {
err := cb.buildTraefikService(ctx, service, conf.HTTP.Services)
if err != nil {
log.FromContext(ctx).WithField(log.ServiceName, service.Name).
Errorf("Error while building TraefikService: %v", err)
continue
}
}
return conf return conf
} }
@ -244,7 +258,7 @@ func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alp
Query: errorPage.Query, Query: errorPage.Query,
} }
balancerServerHTTP, err := createLoadBalancerServerHTTP(client, namespace, errorPage.Service) balancerServerHTTP, err := configBuilder{client}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -286,7 +300,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alp
if len(auth.TLS.CertSecret) > 0 { if len(auth.TLS.CertSecret) > 0 {
authSecretCert, authSecretKey, err := loadAuthTLSSecret(namespace, auth.TLS.CertSecret, k8sClient) authSecretCert, authSecretKey, err := loadAuthTLSSecret(namespace, auth.TLS.CertSecret, k8sClient)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load auth secret: %s", err) return nil, fmt.Errorf("failed to load auth secret: %v", err)
} }
forwardAuth.TLS.Cert = authSecretCert forwardAuth.TLS.Cert = authSecretCert
forwardAuth.TLS.Key = authSecretKey forwardAuth.TLS.Key = authSecretKey
@ -298,7 +312,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alp
func loadCASecret(namespace, secretName string, k8sClient Client) (string, error) { func loadCASecret(namespace, secretName string, k8sClient Client) (string, error) {
secret, ok, err := k8sClient.GetSecret(namespace, secretName) secret, ok, err := k8sClient.GetSecret(namespace, secretName)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to fetch secret '%s/%s': %s", namespace, secretName, err) return "", fmt.Errorf("failed to fetch secret '%s/%s': %v", namespace, secretName, err)
} }
if !ok { if !ok {
return "", fmt.Errorf("secret '%s/%s' not found", namespace, secretName) return "", fmt.Errorf("secret '%s/%s' not found", namespace, secretName)
@ -377,7 +391,7 @@ func getAuthCredentials(k8sClient Client, authSecret, namespace string) ([]strin
auth, err := loadAuthCredentials(namespace, authSecret, k8sClient) auth, err := loadAuthCredentials(namespace, authSecret, k8sClient)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load auth credentials: %s", err) return nil, fmt.Errorf("failed to load auth credentials: %v", err)
} }
return auth, nil return auth, nil
@ -386,7 +400,7 @@ func getAuthCredentials(k8sClient Client, authSecret, namespace string) ([]strin
func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]string, error) { func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]string, error) {
secret, ok, err := k8sClient.GetSecret(namespace, secretName) secret, ok, err := k8sClient.GetSecret(namespace, secretName)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch secret '%s/%s': %s", namespace, secretName, err) return nil, fmt.Errorf("failed to fetch secret '%s/%s': %v", namespace, secretName, err)
} }
if !ok { if !ok {
return nil, fmt.Errorf("secret '%s/%s' not found", namespace, secretName) return nil, fmt.Errorf("secret '%s/%s' not found", namespace, secretName)
@ -412,7 +426,7 @@ func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]stri
} }
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error reading secret for %v/%v: %v", namespace, secretName, err) return nil, fmt.Errorf("error reading secret for %s/%s: %v", namespace, secretName, err)
} }
if len(credentials) == 0 { if len(credentials) == 0 {
return nil, fmt.Errorf("secret '%s/%s' does not contain any credentials", namespace, secretName) return nil, fmt.Errorf("secret '%s/%s' does not contain any credentials", namespace, secretName)
@ -428,7 +442,7 @@ func createChainMiddleware(ctx context.Context, namespace string, chain *v1alpha
var mds []string var mds []string
for _, mi := range chain.Middlewares { for _, mi := range chain.Middlewares {
if strings.Contains(mi.Name, "@") { if strings.Contains(mi.Name, providerNamespaceSeparator) {
if len(mi.Namespace) > 0 { if len(mi.Namespace) > 0 {
log.FromContext(ctx). log.FromContext(ctx).
Warnf("namespace %q is ignored in cross-provider context", mi.Namespace) Warnf("namespace %q is ignored in cross-provider context", mi.Namespace)
@ -600,14 +614,12 @@ func getCertificateBlocks(secret *corev1.Secret, namespace, secretName string) (
func getCABlocks(secret *corev1.Secret, namespace, secretName string) (string, error) { func getCABlocks(secret *corev1.Secret, namespace, secretName string) (string, error) {
tlsCrtData, tlsCrtExists := secret.Data["tls.ca"] tlsCrtData, tlsCrtExists := secret.Data["tls.ca"]
if !tlsCrtExists { if !tlsCrtExists {
return "", fmt.Errorf("the tls.ca entry is missing from secret %s/%s", return "", fmt.Errorf("the tls.ca entry is missing from secret %s/%s", namespace, secretName)
namespace, secretName)
} }
cert := string(tlsCrtData) cert := string(tlsCrtData)
if cert == "" { if cert == "" {
return "", fmt.Errorf("the tls.ca entry in secret %s/%s is empty", return "", fmt.Errorf("the tls.ca entry in secret %s/%s is empty", namespace, secretName)
namespace, secretName)
} }
return cert, nil return cert, nil
@ -620,10 +632,9 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, stop ch
// Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling) // Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling)
eventsChanBuffered := make(chan interface{}, 1) eventsChanBuffered := make(chan interface{}, 1)
// Run a goroutine that reads events from eventChan and does a // Run a goroutine that reads events from eventChan and does a non-blocking write to pendingEvent.
// non-blocking write to pendingEvent. This guarantees that writing to // This guarantees that writing to eventChan will never block,
// eventChan will never block, and that pendingEvent will have // and that pendingEvent will have something in it if there's been an event since we read from that channel.
// something in it if there's been an event since we read from that channel.
go func() { go func() {
for { for {
select { select {
@ -633,10 +644,8 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, stop ch
select { select {
case eventsChanBuffered <- nextEvent: case eventsChanBuffered <- nextEvent:
default: default:
// We already have an event in eventsChanBuffered, so we'll // We already have an event in eventsChanBuffered, so we'll do a refresh as soon as our throttle allows us to.
// do a refresh as soon as our throttle allows us to. It's fine // It's fine to drop the event and keep whatever's in the buffer -- we don't do different things for different events
// to drop the event and keep whatever's in the buffer -- we
// don't do different things for different events
log.FromContext(ctx).Debugf("Dropping event kind %T due to throttling", nextEvent) log.FromContext(ctx).Debugf("Dropping event kind %T due to throttling", nextEvent)
} }
} }

View file

@ -8,11 +8,18 @@ import (
"github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/containous/traefik/v2/pkg/tls" "github.com/containous/traefik/v2/pkg/tls"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
) )
const (
roundRobinStrategy = "RoundRobin"
httpsProtocol = "https"
httpProtocol = "http"
)
func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Client, tlsConfigs map[string]*tls.CertAndStores) *dynamic.HTTPConfiguration { func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Client, tlsConfigs map[string]*tls.CertAndStores) *dynamic.HTTPConfiguration {
conf := &dynamic.HTTPConfiguration{ conf := &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -39,6 +46,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
ingressName = ingressRoute.GenerateName ingressName = ingressRoute.GenerateName
} }
cb := configBuilder{client}
for _, route := range ingressRoute.Spec.Routes { for _, route := range ingressRoute.Spec.Routes {
if route.Kind != "Rule" { if route.Kind != "Rule" {
logger.Errorf("Unsupported match kind: %s. Only \"Rule\" is supported for now.", route.Kind) logger.Errorf("Unsupported match kind: %s. Only \"Rule\" is supported for now.", route.Kind)
@ -55,49 +63,15 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
continue continue
} }
key, err := makeServiceKey(route.Match, ingressName) serviceKey, err := makeServiceKey(route.Match, ingressName)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
continue continue
} }
serviceName := makeID(ingressRoute.Namespace, key)
for _, service := range route.Services {
balancerServerHTTP, err := createLoadBalancerServerHTTP(client, ingressRoute.Namespace, service)
if err != nil {
logger.
WithField("serviceName", service.Name).
WithField("servicePort", service.Port).
Errorf("Cannot create service: %v", err)
continue
}
// If there is only one service defined, we skip the creation of the load balancer of services,
// i.e. the service on top is directly a load balancer of servers.
if len(route.Services) == 1 {
conf.Services[serviceName] = balancerServerHTTP
break
}
serviceKey := fmt.Sprintf("%s-%s-%d", serviceName, service.Name, service.Port)
conf.Services[serviceKey] = balancerServerHTTP
srv := dynamic.WRRService{Name: serviceKey}
srv.SetDefaults()
if service.Weight != nil {
srv.Weight = service.Weight
}
if conf.Services[serviceName] == nil {
conf.Services[serviceName] = &dynamic.Service{Weighted: &dynamic.WeightedRoundRobin{}}
}
conf.Services[serviceName].Weighted.Services = append(conf.Services[serviceName].Weighted.Services, srv)
}
var mds []string var mds []string
for _, mi := range route.Middlewares { for _, mi := range route.Middlewares {
if strings.Contains(mi.Name, "@") { if strings.Contains(mi.Name, providerNamespaceSeparator) {
if len(mi.Namespace) > 0 { if len(mi.Namespace) > 0 {
logger. logger.
WithField(log.MiddlewareName, mi.Name). WithField(log.MiddlewareName, mi.Name).
@ -114,7 +88,36 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
mds = append(mds, makeID(ns, mi.Name)) mds = append(mds, makeID(ns, mi.Name))
} }
conf.Routers[serviceName] = &dynamic.Router{ normalized := provider.Normalize(makeID(ingressRoute.Namespace, serviceKey))
serviceName := normalized
if len(route.Services) > 1 {
spec := v1alpha1.ServiceSpec{
Weighted: &v1alpha1.WeightedRoundRobin{
Services: route.Services,
},
}
errBuild := cb.buildServicesLB(ctx, ingressRoute.Namespace, spec, serviceName, conf.Services)
if errBuild != nil {
logger.Error(err)
continue
}
} else if len(route.Services) == 1 {
fullName, serversLB, err := cb.nameAndService(ctx, ingressRoute.Namespace, route.Services[0].LoadBalancerSpec)
if err != nil {
logger.Error(err)
continue
}
if serversLB != nil {
conf.Services[serviceName] = serversLB
} else {
serviceName = fullName
}
}
conf.Routers[normalized] = &dynamic.Router{
Middlewares: mds, Middlewares: mds,
Priority: route.Priority, Priority: route.Priority,
EntryPoints: ingressRoute.Spec.EntryPoints, EntryPoints: ingressRoute.Spec.EntryPoints,
@ -132,7 +135,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
tlsOptionsName := ingressRoute.Spec.TLS.Options.Name tlsOptionsName := ingressRoute.Spec.TLS.Options.Name
// Is a Kubernetes CRD reference, (i.e. not a cross-provider reference) // Is a Kubernetes CRD reference, (i.e. not a cross-provider reference)
ns := ingressRoute.Spec.TLS.Options.Namespace ns := ingressRoute.Spec.TLS.Options.Namespace
if !strings.Contains(tlsOptionsName, "@") { if !strings.Contains(tlsOptionsName, providerNamespaceSeparator) {
if len(ns) == 0 { if len(ns) == 0 {
ns = ingressRoute.Namespace ns = ingressRoute.Namespace
} }
@ -145,7 +148,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
tlsConf.Options = tlsOptionsName tlsConf.Options = tlsOptionsName
} }
conf.Routers[serviceName].TLS = tlsConf conf.Routers[normalized].TLS = tlsConf
} }
} }
} }
@ -153,120 +156,277 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
return conf return conf
} }
func createLoadBalancerServerHTTP(client Client, namespace string, service v1alpha1.Service) (*dynamic.Service, error) { type configBuilder struct {
servers, err := loadServers(client, namespace, service) client Client
}
// buildTraefikService creates the configuration for the traefik service defined in tService,
// and adds it to the given conf map.
func (c configBuilder) buildTraefikService(ctx context.Context, tService *v1alpha1.TraefikService, conf map[string]*dynamic.Service) error {
id := provider.Normalize(makeID(tService.Namespace, tService.Name))
if tService.Spec.Weighted != nil {
return c.buildServicesLB(ctx, tService.Namespace, tService.Spec, id, conf)
} else if tService.Spec.Mirroring != nil {
return c.buildMirroring(ctx, tService, id, conf)
}
return errors.New("unspecified service type")
}
// buildServicesLB creates the configuration for the load-balancer of services named id, and defined in tService.
// It adds it to the given conf map.
func (c configBuilder) buildServicesLB(ctx context.Context, namespace string, tService v1alpha1.ServiceSpec, id string, conf map[string]*dynamic.Service) error {
var wrrServices []dynamic.WRRService
for _, service := range tService.Weighted.Services {
fullName, k8sService, err := c.nameAndService(ctx, namespace, service.LoadBalancerSpec)
if err != nil {
return err
}
if k8sService != nil {
conf[fullName] = k8sService
}
weight := service.Weight
if weight == nil {
weight = func(i int) *int { return &i }(1)
}
wrrServices = append(wrrServices, dynamic.WRRService{
Name: fullName,
Weight: weight,
})
}
conf[id] = &dynamic.Service{
Weighted: &dynamic.WeightedRoundRobin{
Services: wrrServices,
Sticky: tService.Weighted.Sticky,
},
}
return nil
}
// buildMirroring creates the configuration for the mirroring service named id, and defined by tService.
// It adds it to the given conf map.
func (c configBuilder) buildMirroring(ctx context.Context, tService *v1alpha1.TraefikService, id string, conf map[string]*dynamic.Service) error {
fullNameMain, k8sService, err := c.nameAndService(ctx, tService.Namespace, tService.Spec.Mirroring.LoadBalancerSpec)
if err != nil {
return err
}
if k8sService != nil {
conf[fullNameMain] = k8sService
}
var mirrorServices []dynamic.MirrorService
for _, mirror := range tService.Spec.Mirroring.Mirrors {
mirroredName, k8sService, err := c.nameAndService(ctx, tService.Namespace, mirror.LoadBalancerSpec)
if err != nil {
return err
}
if k8sService != nil {
conf[mirroredName] = k8sService
}
mirrorServices = append(mirrorServices, dynamic.MirrorService{
Name: mirroredName,
Percent: mirror.Percent,
})
}
conf[id] = &dynamic.Service{
Mirroring: &dynamic.Mirroring{
Service: fullNameMain,
Mirrors: mirrorServices,
},
}
return nil
}
// buildServersLB creates the configuration for the load-balancer of servers defined by svc.
func (c configBuilder) buildServersLB(namespace string, svc v1alpha1.LoadBalancerSpec) (*dynamic.Service, error) {
servers, err := c.loadServers(namespace, svc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: support other strategies.
lb := &dynamic.ServersLoadBalancer{} lb := &dynamic.ServersLoadBalancer{}
lb.SetDefaults() lb.SetDefaults()
lb.Servers = servers lb.Servers = servers
lb.PassHostHeader = service.PassHostHeader conf := svc
lb.PassHostHeader = conf.PassHostHeader
if lb.PassHostHeader == nil { if lb.PassHostHeader == nil {
passHostHeader := true passHostHeader := true
lb.PassHostHeader = &passHostHeader lb.PassHostHeader = &passHostHeader
} }
lb.ResponseForwarding = service.ResponseForwarding lb.ResponseForwarding = conf.ResponseForwarding
return &dynamic.Service{ lb.Sticky = svc.Sticky
LoadBalancer: lb,
}, nil return &dynamic.Service{LoadBalancer: lb}, nil
} }
func loadServers(client Client, namespace string, svc v1alpha1.Service) ([]dynamic.Server, error) { func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBalancerSpec) ([]dynamic.Server, error) {
strategy := svc.Strategy strategy := svc.Strategy
if strategy == "" { if strategy == "" {
strategy = "RoundRobin" strategy = roundRobinStrategy
} }
if strategy != "RoundRobin" { if strategy != roundRobinStrategy {
return nil, fmt.Errorf("load balancing strategy %v is not supported", strategy) return nil, fmt.Errorf("load balancing strategy %s is not supported", strategy)
} }
service, exists, err := client.GetService(namespace, svc.Name) namespace := namespaceOrFallback(svc, fallbackNamespace)
// If the service uses explicitly the provider suffix
sanitizedName := strings.TrimSuffix(svc.Name, providerNamespaceSeparator+providerName)
service, exists, err := c.client.GetService(namespace, sanitizedName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !exists { if !exists {
return nil, fmt.Errorf("service not found %s/%s", namespace, svc.Name) return nil, fmt.Errorf("kubernetes service not found: %s/%s", namespace, sanitizedName)
} }
confPort := svc.Port
var portSpec *corev1.ServicePort var portSpec *corev1.ServicePort
for _, p := range service.Spec.Ports { for _, p := range service.Spec.Ports {
if svc.Port == p.Port { if confPort == p.Port {
portSpec = &p portSpec = &p
break break
} }
} }
if portSpec == nil { if portSpec == nil {
return nil, errors.New("service port not found") return nil, errors.New("service port not found")
} }
var servers []dynamic.Server var servers []dynamic.Server
if service.Spec.Type == corev1.ServiceTypeExternalName { if service.Spec.Type == corev1.ServiceTypeExternalName {
protocol := "http" return append(servers, dynamic.Server{
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") { URL: fmt.Sprintf("http://%s:%d", service.Spec.ExternalName, portSpec.Port),
protocol = "https" }), nil
}
endpoints, endpointsExists, endpointsErr := c.client.GetEndpoints(namespace, sanitizedName)
if endpointsErr != nil {
return nil, endpointsErr
}
if !endpointsExists {
return nil, fmt.Errorf("endpoints not found for %s/%s", namespace, sanitizedName)
}
if len(endpoints.Subsets) == 0 {
return nil, fmt.Errorf("subset not found for %s/%s", namespace, sanitizedName)
}
var port int32
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if portSpec.Name == p.Name {
port = p.Port
break
}
} }
servers = append(servers, dynamic.Server{ if port == 0 {
URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, portSpec.Port), return nil, fmt.Errorf("cannot define a port for %s/%s", namespace, sanitizedName)
})
} else {
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
if endpointsErr != nil {
return nil, endpointsErr
} }
if !endpointsExists { protocol := httpProtocol
return nil, errors.New("endpoints not found") scheme := svc.Scheme
switch scheme {
case httpProtocol, httpsProtocol, "h2c":
protocol = scheme
case "":
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, httpsProtocol) {
protocol = httpsProtocol
}
default:
return nil, fmt.Errorf("invalid scheme %q specified", scheme)
} }
if len(endpoints.Subsets) == 0 { for _, addr := range subset.Addresses {
return nil, errors.New("subset not found") servers = append(servers, dynamic.Server{
} URL: fmt.Sprintf("%s://%s:%d", protocol, addr.IP, port),
})
var port int32
for _, subset := range endpoints.Subsets {
for _, p := range subset.Ports {
if portSpec.Name == p.Name {
port = p.Port
break
}
}
if port == 0 {
return nil, errors.New("cannot define a port")
}
protocol := "http"
switch svc.Scheme {
case "http", "https", "h2c":
protocol = svc.Scheme
case "":
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") {
protocol = "https"
}
default:
return nil, fmt.Errorf("invalid scheme %q specified", svc.Scheme)
}
for _, addr := range subset.Addresses {
servers = append(servers, dynamic.Server{
URL: fmt.Sprintf("%s://%s:%d", protocol, addr.IP, port),
})
}
} }
} }
return servers, nil return servers, nil
} }
// nameAndService returns the name that should be used for the svc service in the generated config.
// In addition, if the service is a Kubernetes one,
// it generates and returns the configuration part for such a service,
// so that the caller can add it to the global config map.
func (c configBuilder) nameAndService(ctx context.Context, namespaceService string, service v1alpha1.LoadBalancerSpec) (string, *dynamic.Service, error) {
svcCtx := log.With(ctx, log.Str(log.ServiceName, service.Name))
namespace := namespaceOrFallback(service, namespaceService)
switch {
case service.Kind == "" || service.Kind == "Service":
serversLB, err := c.buildServersLB(namespace, service)
if err != nil {
return "", nil, err
}
fullName := fullServiceName(svcCtx, namespace, service.Name, service.Port)
return fullName, serversLB, nil
case service.Kind == "TraefikService":
return fullServiceName(svcCtx, namespace, service.Name, 0), nil, nil
default:
return "", nil, fmt.Errorf("unsupported service kind %s", service.Kind)
}
}
func splitSvcNameProvider(name string) (string, string) {
parts := strings.Split(name, providerNamespaceSeparator)
svc := strings.Join(parts[:len(parts)-1], providerNamespaceSeparator)
pvd := parts[len(parts)-1]
return svc, pvd
}
func fullServiceName(ctx context.Context, namespace, serviceName string, port int32) string {
if port != 0 {
return provider.Normalize(fmt.Sprintf("%s-%s-%d", namespace, serviceName, port))
}
if !strings.Contains(serviceName, providerNamespaceSeparator) {
return provider.Normalize(fmt.Sprintf("%s-%s", namespace, serviceName))
}
name, pName := splitSvcNameProvider(serviceName)
if pName == providerName {
return provider.Normalize(fmt.Sprintf("%s-%s", namespace, name))
}
// At this point, if namespace == "default", we do not know whether it had been intentionally set as such,
// or if we're simply hitting the value set by default.
// But as it is most likely very much the latter,
// and we do not want to systematically log spam users in that case,
// we skip logging whenever the namespace is "default".
if namespace != "default" {
log.FromContext(ctx).Warnf("namespace %q is ignored in cross-provider context", namespace)
}
return provider.Normalize(name) + providerNamespaceSeparator + pName
}
func namespaceOrFallback(lb v1alpha1.LoadBalancerSpec, fallback string) string {
if lb.Namespace != "" {
return lb.Namespace
}
return fallback
}
func getTLSHTTP(ctx context.Context, ingressRoute *v1alpha1.IngressRoute, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error { func getTLSHTTP(ctx context.Context, ingressRoute *v1alpha1.IngressRoute, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error {
if ingressRoute.Spec.TLS == nil { if ingressRoute.Spec.TLS == nil {
return nil return nil

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,9 @@
package v1alpha1 package v1alpha1
import ( import (
"errors"
"fmt"
"github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/types" "github.com/containous/traefik/v2/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -22,13 +25,14 @@ type Route struct {
Middlewares []MiddlewareRef `json:"middlewares"` Middlewares []MiddlewareRef `json:"middlewares"`
} }
// TLS contains the TLS certificates configuration of the routes. To enable // TLS contains the TLS certificates configuration of the routes.
// Let's Encrypt, use an empty TLS struct, e.g. in YAML: // To enable Let's Encrypt, use an empty TLS struct,
// e.g. in YAML:
// //
// tls: {} # inline format // tls: {} # inline format
// //
// tls: // tls:
// secretName: # block format // secretName: # block format
type TLS struct { type TLS struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the // SecretName is the name of the referenced Kubernetes Secret to specify the
// certificate details. // certificate details.
@ -45,16 +49,58 @@ type TLSOptionRef struct {
Namespace string `json:"namespace"` Namespace string `json:"namespace"`
} }
// Service defines an upstream to proxy traffic. // LoadBalancerSpec can reference either a Kubernetes Service object (a load-balancer of servers),
type Service struct { // or a TraefikService object (a traefik load-balancer of services).
Name string `json:"name"` type LoadBalancerSpec struct {
// Name is a reference to a Kubernetes Service object (for a load-balancer of servers),
// or to a TraefikService object (service load-balancer, mirroring, etc).
// The differentiation between the two is specified in the Kind field.
Name string `json:"name"`
Kind string `json:"kind"`
Namespace string `json:"namespace"`
Sticky *dynamic.Sticky `json:"sticky,omitempty"`
// Port and all the fields below are related to a servers load-balancer,
// and therefore should only be specified when Name references a Kubernetes Service.
Port int32 `json:"port"` Port int32 `json:"port"`
Scheme string `json:"scheme,omitempty"` Scheme string `json:"scheme,omitempty"`
HealthCheck *HealthCheck `json:"healthCheck,omitempty"` HealthCheck *HealthCheck `json:"healthCheck,omitempty"`
Strategy string `json:"strategy,omitempty"` Strategy string `json:"strategy,omitempty"`
PassHostHeader *bool `json:"passHostHeader,omitempty"` PassHostHeader *bool `json:"passHostHeader,omitempty"`
ResponseForwarding *dynamic.ResponseForwarding `json:"responseForwarding,omitempty"` ResponseForwarding *dynamic.ResponseForwarding `json:"responseForwarding,omitempty"`
Weight *int `json:"weight,omitempty"`
// Weight should only be specified when Name references a TraefikService object
// (and to be precise, one that embeds a Weighted Round Robin).
Weight *int `json:"weight,omitempty"`
}
// IsServersLB reports whether lb is a load-balancer of servers
// (as opposed to a traefik load-balancer of services).
func (lb LoadBalancerSpec) IsServersLB() (bool, error) {
if lb.Name == "" {
return false, errors.New("missing Name field in service")
}
if lb.Kind == "" || lb.Kind == "Service" {
return true, nil
}
if lb.Kind != "TraefikService" {
return false, fmt.Errorf("invalid kind value: %v", lb.Kind)
}
if lb.Port != 0 ||
lb.Scheme != "" ||
lb.HealthCheck != nil ||
lb.Strategy != "" ||
lb.PassHostHeader != nil ||
lb.ResponseForwarding != nil ||
lb.Sticky != nil {
return false, fmt.Errorf("service of kind %v is incompatible with Kubernetes Service related fields", lb.Kind)
}
return false, nil
}
// Service defines an upstream to proxy traffic.
type Service struct {
LoadBalancerSpec
} }
// MiddlewareRef is a ref to the Middleware resources. // MiddlewareRef is a ref to the Middleware resources.

View file

@ -18,13 +18,14 @@ type RouteTCP struct {
Services []ServiceTCP `json:"services,omitempty"` Services []ServiceTCP `json:"services,omitempty"`
} }
// TLSTCP contains the TLS certificates configuration of the routes. To enable // TLSTCP contains the TLS certificates configuration of the routes.
// Let's Encrypt, use an empty TLS struct, e.g. in YAML: // To enable Let's Encrypt, use an empty TLS struct,
// e.g. in YAML:
// //
// tls: {} # inline format // tls: {} # inline format
// //
// tls: // tls:
// secretName: # block format // secretName: # block format
type TLSTCP struct { type TLSTCP struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the // SecretName is the name of the referenced Kubernetes Secret to specify the
// certificate details. // certificate details.

View file

@ -41,6 +41,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&MiddlewareList{}, &MiddlewareList{},
&TLSOption{}, &TLSOption{},
&TLSOptionList{}, &TLSOptionList{},
&TraefikService{},
&TraefikServiceList{},
) )
metav1.AddToGroupVersion(scheme, SchemeGroupVersion) metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil return nil

View file

@ -0,0 +1,64 @@
package v1alpha1
import (
"github.com/containous/traefik/v2/pkg/config/dynamic"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// TraefikService is the specification for a service (that an IngressRoute refers
// to) that is usually not a terminal service (i.e. not a pod of servers), as
// opposed to a Kubernetes Service. That is to say, it usually refers to other
// (children) services, which themselves can be TraefikServices or Services.
type TraefikService struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
Spec ServiceSpec `json:"spec"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// TraefikServiceList is a list of TraefikService resources.
type TraefikServiceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []TraefikService `json:"items"`
}
// +k8s:deepcopy-gen=true
// ServiceSpec defines whether a TraefikService is a load-balancer of services or a
// mirroring service.
type ServiceSpec struct {
Weighted *WeightedRoundRobin `json:"weighted,omitempty"`
Mirroring *Mirroring `json:"mirroring,omitempty"`
}
// +k8s:deepcopy-gen=true
// Mirroring defines a mirroring service, which is composed of a main
// load-balancer, and a list of mirrors.
type Mirroring struct {
LoadBalancerSpec
Mirrors []MirrorService `json:"mirrors,omitempty"`
}
// +k8s:deepcopy-gen=true
// MirrorService defines one of the mirrors of a Mirroring service.
type MirrorService struct {
LoadBalancerSpec
Percent int `json:"percent,omitempty"`
}
// +k8s:deepcopy-gen=true
// WeightedRoundRobin defines a load-balancer of services.
type WeightedRoundRobin struct {
Services []Service `json:"services,omitempty"`
Sticky *dynamic.Sticky `json:"sticky,omitempty"`
}

View file

@ -381,6 +381,47 @@ func (in *IngressRouteTCPSpec) DeepCopy() *IngressRouteTCPSpec {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
*out = *in
if in.Sticky != nil {
in, out := &in.Sticky, &out.Sticky
*out = new(dynamic.Sticky)
(*in).DeepCopyInto(*out)
}
if in.HealthCheck != nil {
in, out := &in.HealthCheck, &out.HealthCheck
*out = new(HealthCheck)
(*in).DeepCopyInto(*out)
}
if in.PassHostHeader != nil {
in, out := &in.PassHostHeader, &out.PassHostHeader
*out = new(bool)
**out = **in
}
if in.ResponseForwarding != nil {
in, out := &in.ResponseForwarding, &out.ResponseForwarding
*out = new(dynamic.ResponseForwarding)
**out = **in
}
if in.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerSpec.
func (in *LoadBalancerSpec) DeepCopy() *LoadBalancerSpec {
if in == nil {
return nil
}
out := new(LoadBalancerSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Middleware) DeepCopyInto(out *Middleware) { func (in *Middleware) DeepCopyInto(out *Middleware) {
*out = *in *out = *in
@ -578,6 +619,47 @@ func (in *MiddlewareSpec) DeepCopy() *MiddlewareSpec {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MirrorService) DeepCopyInto(out *MirrorService) {
*out = *in
in.LoadBalancerSpec.DeepCopyInto(&out.LoadBalancerSpec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MirrorService.
func (in *MirrorService) DeepCopy() *MirrorService {
if in == nil {
return nil
}
out := new(MirrorService)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Mirroring) DeepCopyInto(out *Mirroring) {
*out = *in
in.LoadBalancerSpec.DeepCopyInto(&out.LoadBalancerSpec)
if in.Mirrors != nil {
in, out := &in.Mirrors, &out.Mirrors
*out = make([]MirrorService, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mirroring.
func (in *Mirroring) DeepCopy() *Mirroring {
if in == nil {
return nil
}
out := new(Mirroring)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Route) DeepCopyInto(out *Route) { func (in *Route) DeepCopyInto(out *Route) {
*out = *in *out = *in
@ -632,26 +714,7 @@ func (in *RouteTCP) DeepCopy() *RouteTCP {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Service) DeepCopyInto(out *Service) { func (in *Service) DeepCopyInto(out *Service) {
*out = *in *out = *in
if in.HealthCheck != nil { in.LoadBalancerSpec.DeepCopyInto(&out.LoadBalancerSpec)
in, out := &in.HealthCheck, &out.HealthCheck
*out = new(HealthCheck)
(*in).DeepCopyInto(*out)
}
if in.PassHostHeader != nil {
in, out := &in.PassHostHeader, &out.PassHostHeader
*out = new(bool)
**out = **in
}
if in.ResponseForwarding != nil {
in, out := &in.ResponseForwarding, &out.ResponseForwarding
*out = new(dynamic.ResponseForwarding)
**out = **in
}
if in.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int)
**out = **in
}
return return
} }
@ -665,6 +728,32 @@ func (in *Service) DeepCopy() *Service {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
*out = *in
if in.Weighted != nil {
in, out := &in.Weighted, &out.Weighted
*out = new(WeightedRoundRobin)
(*in).DeepCopyInto(*out)
}
if in.Mirroring != nil {
in, out := &in.Mirroring, &out.Mirroring
*out = new(Mirroring)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec.
func (in *ServiceSpec) DeepCopy() *ServiceSpec {
if in == nil {
return nil
}
out := new(ServiceSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) { func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) {
*out = *in *out = *in
@ -865,3 +954,91 @@ func (in *TLSTCP) DeepCopy() *TLSTCP {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TraefikService) DeepCopyInto(out *TraefikService) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TraefikService.
func (in *TraefikService) DeepCopy() *TraefikService {
if in == nil {
return nil
}
out := new(TraefikService)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TraefikService) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TraefikServiceList) DeepCopyInto(out *TraefikServiceList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]TraefikService, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TraefikServiceList.
func (in *TraefikServiceList) DeepCopy() *TraefikServiceList {
if in == nil {
return nil
}
out := new(TraefikServiceList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TraefikServiceList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WeightedRoundRobin) DeepCopyInto(out *WeightedRoundRobin) {
*out = *in
if in.Services != nil {
in, out := &in.Services, &out.Services
*out = make([]Service, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Sticky != nil {
in, out := &in.Sticky, &out.Sticky
*out = new(dynamic.Sticky)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WeightedRoundRobin.
func (in *WeightedRoundRobin) DeepCopy() *WeightedRoundRobin {
if in == nil {
return nil
}
out := new(WeightedRoundRobin)
in.DeepCopyInto(out)
return out
}

View file

@ -12,7 +12,7 @@ import (
// MustParseYaml parses a YAML to objects. // MustParseYaml parses a YAML to objects.
func MustParseYaml(content []byte) []runtime.Object { func MustParseYaml(content []byte) []runtime.Object {
acceptedK8sTypes := regexp.MustCompile(`(Deployment|Endpoints|Service|Ingress|IngressRoute|Middleware|Secret|TLSOption)`) acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|Middleware|Secret|TLSOption|TraefikService)$`)
files := strings.Split(string(content), "---") files := strings.Split(string(content), "---")
retVal := make([]runtime.Object, 0, len(files)) retVal := make([]runtime.Object, 0, len(files))