Add support for TCP (in kubernetes CRD)

Co-authored-by: Jean-Baptiste Doumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
mpl 2019-06-11 15:12:04 +02:00 committed by Traefiker Bot
parent c1dc783512
commit c4df78b4b9
44 changed files with 2070 additions and 93 deletions

View file

@ -11,3 +11,18 @@ spec:
plural: ingressroutes
singular: ingressroute
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutetcps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteTCP
plural: ingressroutetcps
singular: ingressroutetcp
scope: Namespaced

View file

@ -145,7 +145,7 @@ If you're in a hurry, maybe you'd rather go through the [dynamic](../reference/d
--8<-- "content/providers/crd_ingress_route.yml"
```
That `IngressRoute` kind can then be used to define an `IngressRoute` object, such as:
That `IngressRoute` kind can then be used to define an `IngressRoute` object, such as in:
```yaml
apiVersion: traefik.containo.us/v1alpha1
@ -170,6 +170,22 @@ spec:
services:
- name: whoami
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: ingressroutetcpfoo.crd
spec:
entryPoints:
- footcp
routes:
# Match is the rule corresponding to an underlying router.
- match: HostSNI(`*`)
services:
- name: whoamitcp
port: 8080
```
### Middleware

View file

@ -26,6 +26,21 @@ spec:
singular: middleware
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutetcps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteTCP
plural: ingressroutetcps
singular: ingressroutetcp
scope: Namespaced
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
@ -67,5 +82,25 @@ spec:
middlewares:
- name: stripprefix
- name: addprefix
# use an empty tls object for TLS with Let's Encrypt
tls:
secretName: supersecret
secretName: supersecret
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: ingressroutetcp.crd
namespace: default
spec:
entryPoints:
- footcp
routes:
- match: HostSNI(`bar.com`)
services:
- name: whoamitcp
port: 8080
tls:
secretName: foosecret
passthrough: false

View file

@ -12,6 +12,21 @@ spec:
singular: ingressroute
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutetcps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteTCP
plural: ingressroutetcps
singular: ingressroutetcp
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
@ -74,6 +89,14 @@ rules:
- get
- list
- watch
- apiGroups:
- traefik.containo.us
resources:
- ingressroutetcps
verbs:
- get
- list
- watch
---
kind: ClusterRoleBinding

View file

@ -26,5 +26,5 @@ spec:
services:
- name: whoami
port: 80
tls:
secretName: ""
# Please note the use of an empty TLS object to enable TLS with Let's Encrypt.
tls: {}

View file

@ -26,3 +26,18 @@ spec:
plural: middlewares
singular: middleware
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutetcps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteTCP
plural: ingressroutetcps
singular: ingressroutetcp
scope: Namespaced

View file

@ -39,3 +39,46 @@ spec:
selector:
app: containous
task: whoami
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: whoamitcp
namespace: default
labels:
app: containous
name: whoamitcp
spec:
replicas: 2
selector:
matchLabels:
app: containous
task: whoamitcp
template:
metadata:
labels:
app: containous
task: whoamitcp
spec:
containers:
- name: containouswhoamitcp
image: containous/whoamitcp
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoamitcp
namespace: default
spec:
ports:
- name: footcp
port: 8080
selector:
app: containous
task: whoamitcp

View file

@ -0,0 +1,14 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test3.crd
namespace: default
spec:
entryPoints:
- footcp
routes:
- match: HostSNI(`*`)
services:
- name: whoamitcp
port: 8080

View file

@ -8,6 +8,9 @@ level = "DEBUG"
[entryPoints]
[entryPoints.web]
address = ":8000"
[entryPoints.footcp]
address = ":8093"
[api]

View file

@ -1,16 +1,28 @@
package integration
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"time"
"github.com/containous/traefik/integration/try"
"github.com/containous/traefik/pkg/api"
"github.com/go-check/check"
"github.com/pmezard/go-difflib/difflib"
checker "github.com/vdemeester/shakers"
)
var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata")
// K8sSuite
type K8sSuite struct{ BaseSuite }
@ -48,7 +60,7 @@ func (s *K8sSuite) TearDownSuite(c *check.C) {
}
}
func (s *K8sSuite) TestIngressSimple(c *check.C) {
func (s *K8sSuite) TestIngressConfiguration(c *check.C) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_default.toml"))
defer display(c)
@ -56,11 +68,10 @@ func (s *K8sSuite) TestIngressSimple(c *check.C) {
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("Host(`whoami.test`)"))
c.Assert(err, checker.IsNil)
testConfiguration(c, "testdata/rawdata-ingress.json")
}
func (s *K8sSuite) TestCRDSimple(c *check.C) {
func (s *K8sSuite) TestCRDConfiguration(c *check.C) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_crd.toml"))
defer display(c)
@ -68,15 +79,105 @@ func (s *K8sSuite) TestCRDSimple(c *check.C) {
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("Host(`foo.com`)"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("PathPrefix(`/tobestripped`)"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("default/stripprefix"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("stripprefix"))
c.Assert(err, checker.IsNil)
testConfiguration(c, "testdata/rawdata-crd.json")
}
func testConfiguration(c *check.C, path string) {
expectedJSON := filepath.FromSlash(path)
if *updateExpected {
fi, err := os.Create(expectedJSON)
c.Assert(err, checker.IsNil)
err = fi.Close()
c.Assert(err, checker.IsNil)
}
var buf bytes.Buffer
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 20*time.Second, try.StatusCodeIs(http.StatusOK), matchesConfig(expectedJSON, &buf))
if !*updateExpected {
if err != nil {
c.Error(err)
}
return
}
if err != nil {
c.Logf("In file update mode, got expected error: %v", err)
}
var rtRepr api.RunTimeRepresentation
err = json.Unmarshal(buf.Bytes(), &rtRepr)
c.Assert(err, checker.IsNil)
newJSON, err := json.MarshalIndent(rtRepr, "", "\t")
c.Assert(err, checker.IsNil)
err = ioutil.WriteFile(expectedJSON, newJSON, 0644)
c.Assert(err, checker.IsNil)
c.Errorf("We do not want a passing test in file update mode")
}
func matchesConfig(wantConfig string, buf *bytes.Buffer) try.ResponseCondition {
return func(res *http.Response) error {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %s", err)
}
if err := res.Body.Close(); err != nil {
return err
}
var obtained api.RunTimeRepresentation
err = json.Unmarshal(body, &obtained)
if err != nil {
return err
}
if buf != nil {
buf.Reset()
if _, err := io.Copy(buf, bytes.NewReader(body)); err != nil {
return err
}
}
got, err := json.MarshalIndent(obtained, "", "\t")
if err != nil {
return err
}
expected, err := ioutil.ReadFile(wantConfig)
if err != nil {
return err
}
// The pods IPs are dynamic, so we cannot predict them,
// which is why we have to ignore them in the comparison.
rxURL := regexp.MustCompile(`"(url|address)":\s+(".*")`)
sanitizedExpected := rxURL.ReplaceAll(expected, []byte(`"$1": "XXXX"`))
sanitizedGot := rxURL.ReplaceAll(got, []byte(`"$1": "XXXX"`))
rxServerStatus := regexp.MustCompile(`"http://.*?":\s+(".*")`)
sanitizedExpected = rxServerStatus.ReplaceAll(sanitizedExpected, []byte(`"http://XXXX": $1`))
sanitizedGot = rxServerStatus.ReplaceAll(sanitizedGot, []byte(`"http://XXXX": $1`))
if bytes.Equal(sanitizedExpected, sanitizedGot) {
return nil
}
diff := difflib.UnifiedDiff{
FromFile: "Expected",
A: difflib.SplitLines(string(sanitizedExpected)),
ToFile: "Got",
B: difflib.SplitLines(string(sanitizedGot)),
Context: 3,
}
text, err := difflib.GetUnifiedDiffString(diff)
if err != nil {
return err
}
return errors.New(text)
}
}

102
integration/testdata/rawdata-crd.json vendored Normal file
View file

@ -0,0 +1,102 @@
{
"routers": {
"kubernetescrd.default/test-crd-6b204d94623b3df4370c": {
"entryPoints": [
"web"
],
"service": "default/test-crd-6b204d94623b3df4370c",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/bar`)",
"priority": 12
},
"kubernetescrd.default/test2-crd-23c7f4c450289ee29016": {
"entryPoints": [
"web"
],
"middlewares": [
"default/stripprefix"
],
"service": "default/test2-crd-23c7f4c450289ee29016",
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/tobestripped`)"
}
},
"middlewares": {
"kubernetescrd.default/stripprefix": {
"stripPrefix": {
"prefixes": [
"/tobestripped"
]
},
"usedBy": [
"kubernetescrd.default/test2-crd-23c7f4c450289ee29016"
]
}
},
"services": {
"kubernetescrd.default/test-crd-6b204d94623b3df4370c": {
"loadbalancer": {
"servers": [
{
"url": "http://10.42.0.4:80"
},
{
"url": "http://10.42.0.5:80"
}
],
"passHostHeader": true
},
"usedBy": [
"kubernetescrd.default/test-crd-6b204d94623b3df4370c"
],
"serverStatus": {
"http://10.42.0.4:80": "UP",
"http://10.42.0.5:80": "UP"
}
},
"kubernetescrd.default/test2-crd-23c7f4c450289ee29016": {
"loadbalancer": {
"servers": [
{
"url": "http://10.42.0.4:80"
},
{
"url": "http://10.42.0.5:80"
}
],
"passHostHeader": true
},
"usedBy": [
"kubernetescrd.default/test2-crd-23c7f4c450289ee29016"
],
"serverStatus": {
"http://10.42.0.4:80": "UP",
"http://10.42.0.5:80": "UP"
}
}
},
"tcpRouters": {
"kubernetescrd.default/test3-crd-673acf455cb2dab0b43a": {
"entryPoints": [
"footcp"
],
"service": "default/test3-crd-673acf455cb2dab0b43a",
"rule": "HostSNI(`*`)"
}
},
"tcpServices": {
"kubernetescrd.default/test3-crd-673acf455cb2dab0b43a": {
"loadbalancer": {
"servers": [
{
"address": "10.42.0.2:8080"
},
{
"address": "10.42.0.3:8080"
}
]
},
"usedBy": [
"kubernetescrd.default/test3-crd-673acf455cb2dab0b43a"
]
}
}
}

View file

@ -0,0 +1,31 @@
{
"routers": {
"kubernetes.whoami-test/whoami": {
"entryPoints": null,
"service": "default/whoami/http",
"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)"
}
},
"services": {
"kubernetes.default/whoami/http": {
"loadbalancer": {
"servers": [
{
"url": "http://10.42.0.2:80"
},
{
"url": "http://10.42.0.5:80"
}
],
"passHostHeader": true
},
"usedBy": [
"kubernetes.whoami-test/whoami"
],
"serverStatus": {
"http://10.42.0.2:80": "UP",
"http://10.42.0.5:80": "UP"
}
}
}
}

View file

@ -266,6 +266,21 @@ func (in *Headers) DeepCopyInto(out *Headers) {
(*out)[key] = val
}
}
if in.AccessControlAllowHeaders != nil {
in, out := &in.AccessControlAllowHeaders, &out.AccessControlAllowHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AccessControlAllowMethods != nil {
in, out := &in.AccessControlAllowMethods, &out.AccessControlAllowMethods
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AccessControlExposeHeaders != nil {
in, out := &in.AccessControlExposeHeaders, &out.AccessControlExposeHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AllowedHosts != nil {
in, out := &in.AllowedHosts, &out.AllowedHosts
*out = make([]string, len(*in))

View file

@ -44,6 +44,7 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
if conf.KubernetesCRD != nil {
p.quietAddProvider(conf.KubernetesCRD)
}
if conf.Rancher != nil {
p.quietAddProvider(conf.Rancher)
}

View file

@ -48,6 +48,7 @@ type Client interface {
WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error)
GetIngressRoutes() []*v1alpha1.IngressRoute
GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP
GetMiddlewares() []*v1alpha1.Middleware
GetIngresses() []*extensionsv1beta1.Ingress
@ -157,6 +158,7 @@ func (c *clientWrapper) WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct
factoryCrd := externalversions.NewSharedInformerFactoryWithOptions(c.csCrd, resyncPeriod, externalversions.WithNamespace(ns))
factoryCrd.Traefik().V1alpha1().IngressRoutes().Informer().AddEventHandler(eventHandler)
factoryCrd.Traefik().V1alpha1().Middlewares().Informer().AddEventHandler(eventHandler)
factoryCrd.Traefik().V1alpha1().IngressRouteTCPs().Informer().AddEventHandler(eventHandler)
factoryKube := informers.NewFilteredSharedInformerFactory(c.csKube, resyncPeriod, ns, nil)
factoryKube.Extensions().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler)
@ -212,6 +214,20 @@ func (c *clientWrapper) GetIngressRoutes() []*v1alpha1.IngressRoute {
return result
}
func (c *clientWrapper) GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP {
var result []*v1alpha1.IngressRouteTCP
for ns, factory := range c.factoriesCrd {
ings, err := factory.Traefik().V1alpha1().IngressRouteTCPs().Lister().List(c.labelSelector)
if err != nil {
log.Errorf("Failed to list tcp ingresses in namespace %s: %s", ns, err)
}
result = append(result, ings...)
}
return result
}
func (c *clientWrapper) GetMiddlewares() []*v1alpha1.Middleware {
var result []*v1alpha1.Middleware

View file

@ -3,6 +3,7 @@ package crd
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/containous/traefik/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
@ -33,8 +34,9 @@ type clientMock struct {
apiEndpointsError error
apiIngressStatusError error
ingressRoutes []*v1alpha1.IngressRoute
middlewares []*v1alpha1.Middleware
ingressRoutes []*v1alpha1.IngressRoute
ingressRouteTCPs []*v1alpha1.IngressRouteTCP
middlewares []*v1alpha1.Middleware
watchChan chan interface{}
}
@ -43,7 +45,7 @@ func newClientMock(paths ...string) clientMock {
var c clientMock
for _, path := range paths {
yamlContent, err := ioutil.ReadFile("./fixtures/" + path)
yamlContent, err := ioutil.ReadFile(filepath.FromSlash("./fixtures/" + path))
if err != nil {
panic(err)
}
@ -57,6 +59,8 @@ func newClientMock(paths ...string) clientMock {
c.endpoints = append(c.endpoints, o)
case *v1alpha1.IngressRoute:
c.ingressRoutes = append(c.ingressRoutes, o)
case *v1alpha1.IngressRouteTCP:
c.ingressRouteTCPs = append(c.ingressRouteTCPs, o)
case *v1alpha1.Middleware:
c.middlewares = append(c.middlewares, o)
case *v1beta12.Ingress:
@ -76,6 +80,10 @@ func (c clientMock) GetIngressRoutes() []*v1alpha1.IngressRoute {
return c.ingressRoutes
}
func (c clientMock) GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP {
return c.ingressRouteTCPs
}
func (c clientMock) GetMiddlewares() []*v1alpha1.Middleware {
return c.middlewares
}

View file

@ -0,0 +1,88 @@
apiVersion: v1
kind: Service
metadata:
name: whoamitcp
namespace: default
spec:
ports:
- name: myapp
port: 8000
selector:
app: containous
task: whoamitcp
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoamitcp
namespace: default
subsets:
- addresses:
- ip: 10.10.0.1
- ip: 10.10.0.2
ports:
- name: myapp
port: 8000
---
apiVersion: v1
kind: Service
metadata:
name: whoamitcp2
namespace: default
spec:
ports:
- name: myapp2
port: 8080
selector:
app: containous
task: whoamitcp2
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoamitcp2
namespace: default
subsets:
- addresses:
- ip: 10.10.0.3
- ip: 10.10.0.4
ports:
- name: myapp2
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: whoamitcptls
namespace: default
spec:
ports:
- name: web-secure
port: 443
selector:
app: containous
task: whoamitcptls2
---
kind: Endpoints
apiVersion: v1
metadata:
name: whoamitcptls
namespace: default
subsets:
- addresses:
- ip: 10.10.0.5
- ip: 10.10.0.6
ports:
- name: web-secure
port: 443

View file

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

View file

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

View file

@ -0,0 +1,15 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.crd
namespace: default
spec:
entryPoints:
- foo
routes:
- match: ""
services:
- name: whoamitcp
port: 8000

View file

@ -0,0 +1,29 @@
apiVersion: v1
kind: Secret
metadata:
name: supersecret
namespace: default
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.crd
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: whoamitcp
port: 8000
tls:
secretName: supersecret

View file

@ -0,0 +1,18 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.crd
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: whoamitcp
port: 8000
tls:
secretName:

View file

@ -0,0 +1,30 @@
apiVersion: v1
kind: Secret
metadata:
name: supersecret
namespace: default
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.crd
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: whoamitcp
port: 8000
tls:
secretName: supersecret
passthrough: true

View file

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

View file

@ -0,0 +1,18 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.crd
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: whoamitcp
port: 8000
- name: whoamitcp2
port: 8080

View file

@ -16,5 +16,4 @@ spec:
- name: whoami
port: 80
tls:
secretName:
tls: {}

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/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"
)
// FakeIngressRouteTCPs implements IngressRouteTCPInterface
type FakeIngressRouteTCPs struct {
Fake *FakeTraefikV1alpha1
ns string
}
var ingressroutetcpsResource = schema.GroupVersionResource{Group: "traefik.containo.us", Version: "v1alpha1", Resource: "ingressroutetcps"}
var ingressroutetcpsKind = schema.GroupVersionKind{Group: "traefik.containo.us", Version: "v1alpha1", Kind: "IngressRouteTCP"}
// Get takes name of the ingressRouteTCP, and returns the corresponding ingressRouteTCP object, and an error if there is any.
func (c *FakeIngressRouteTCPs) Get(name string, options v1.GetOptions) (result *v1alpha1.IngressRouteTCP, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(ingressroutetcpsResource, c.ns, name), &v1alpha1.IngressRouteTCP{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.IngressRouteTCP), err
}
// List takes label and field selectors, and returns the list of IngressRouteTCPs that match those selectors.
func (c *FakeIngressRouteTCPs) List(opts v1.ListOptions) (result *v1alpha1.IngressRouteTCPList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(ingressroutetcpsResource, ingressroutetcpsKind, c.ns, opts), &v1alpha1.IngressRouteTCPList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.IngressRouteTCPList{ListMeta: obj.(*v1alpha1.IngressRouteTCPList).ListMeta}
for _, item := range obj.(*v1alpha1.IngressRouteTCPList).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 ingressRouteTCPs.
func (c *FakeIngressRouteTCPs) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(ingressroutetcpsResource, c.ns, opts))
}
// Create takes the representation of a ingressRouteTCP and creates it. Returns the server's representation of the ingressRouteTCP, and an error, if there is any.
func (c *FakeIngressRouteTCPs) Create(ingressRouteTCP *v1alpha1.IngressRouteTCP) (result *v1alpha1.IngressRouteTCP, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(ingressroutetcpsResource, c.ns, ingressRouteTCP), &v1alpha1.IngressRouteTCP{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.IngressRouteTCP), err
}
// Update takes the representation of a ingressRouteTCP and updates it. Returns the server's representation of the ingressRouteTCP, and an error, if there is any.
func (c *FakeIngressRouteTCPs) Update(ingressRouteTCP *v1alpha1.IngressRouteTCP) (result *v1alpha1.IngressRouteTCP, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(ingressroutetcpsResource, c.ns, ingressRouteTCP), &v1alpha1.IngressRouteTCP{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.IngressRouteTCP), err
}
// Delete takes name of the ingressRouteTCP and deletes it. Returns an error if one occurs.
func (c *FakeIngressRouteTCPs) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(ingressroutetcpsResource, c.ns, name), &v1alpha1.IngressRouteTCP{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeIngressRouteTCPs) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(ingressroutetcpsResource, c.ns, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha1.IngressRouteTCPList{})
return err
}
// Patch applies the patch and returns the patched ingressRouteTCP.
func (c *FakeIngressRouteTCPs) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.IngressRouteTCP, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(ingressroutetcpsResource, c.ns, name, data, subresources...), &v1alpha1.IngressRouteTCP{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.IngressRouteTCP), err
}

View file

@ -40,6 +40,10 @@ func (c *FakeTraefikV1alpha1) IngressRoutes(namespace string) v1alpha1.IngressRo
return &FakeIngressRoutes{c, namespace}
}
func (c *FakeTraefikV1alpha1) IngressRouteTCPs(namespace string) v1alpha1.IngressRouteTCPInterface {
return &FakeIngressRouteTCPs{c, namespace}
}
func (c *FakeTraefikV1alpha1) Middlewares(namespace string) v1alpha1.MiddlewareInterface {
return &FakeMiddlewares{c, namespace}
}

View file

@ -28,4 +28,6 @@ package v1alpha1
type IngressRouteExpansion interface{}
type IngressRouteTCPExpansion interface{}
type MiddlewareExpansion interface{}

View file

@ -0,0 +1,165 @@
/*
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 (
scheme "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme"
v1alpha1 "github.com/containous/traefik/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"
)
// IngressRouteTCPsGetter has a method to return a IngressRouteTCPInterface.
// A group's client should implement this interface.
type IngressRouteTCPsGetter interface {
IngressRouteTCPs(namespace string) IngressRouteTCPInterface
}
// IngressRouteTCPInterface has methods to work with IngressRouteTCP resources.
type IngressRouteTCPInterface interface {
Create(*v1alpha1.IngressRouteTCP) (*v1alpha1.IngressRouteTCP, error)
Update(*v1alpha1.IngressRouteTCP) (*v1alpha1.IngressRouteTCP, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha1.IngressRouteTCP, error)
List(opts v1.ListOptions) (*v1alpha1.IngressRouteTCPList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.IngressRouteTCP, err error)
IngressRouteTCPExpansion
}
// ingressRouteTCPs implements IngressRouteTCPInterface
type ingressRouteTCPs struct {
client rest.Interface
ns string
}
// newIngressRouteTCPs returns a IngressRouteTCPs
func newIngressRouteTCPs(c *TraefikV1alpha1Client, namespace string) *ingressRouteTCPs {
return &ingressRouteTCPs{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the ingressRouteTCP, and returns the corresponding ingressRouteTCP object, and an error if there is any.
func (c *ingressRouteTCPs) Get(name string, options v1.GetOptions) (result *v1alpha1.IngressRouteTCP, err error) {
result = &v1alpha1.IngressRouteTCP{}
err = c.client.Get().
Namespace(c.ns).
Resource("ingressroutetcps").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of IngressRouteTCPs that match those selectors.
func (c *ingressRouteTCPs) List(opts v1.ListOptions) (result *v1alpha1.IngressRouteTCPList, err error) {
result = &v1alpha1.IngressRouteTCPList{}
err = c.client.Get().
Namespace(c.ns).
Resource("ingressroutetcps").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested ingressRouteTCPs.
func (c *ingressRouteTCPs) Watch(opts v1.ListOptions) (watch.Interface, error) {
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("ingressroutetcps").
VersionedParams(&opts, scheme.ParameterCodec).
Watch()
}
// Create takes the representation of a ingressRouteTCP and creates it. Returns the server's representation of the ingressRouteTCP, and an error, if there is any.
func (c *ingressRouteTCPs) Create(ingressRouteTCP *v1alpha1.IngressRouteTCP) (result *v1alpha1.IngressRouteTCP, err error) {
result = &v1alpha1.IngressRouteTCP{}
err = c.client.Post().
Namespace(c.ns).
Resource("ingressroutetcps").
Body(ingressRouteTCP).
Do().
Into(result)
return
}
// Update takes the representation of a ingressRouteTCP and updates it. Returns the server's representation of the ingressRouteTCP, and an error, if there is any.
func (c *ingressRouteTCPs) Update(ingressRouteTCP *v1alpha1.IngressRouteTCP) (result *v1alpha1.IngressRouteTCP, err error) {
result = &v1alpha1.IngressRouteTCP{}
err = c.client.Put().
Namespace(c.ns).
Resource("ingressroutetcps").
Name(ingressRouteTCP.Name).
Body(ingressRouteTCP).
Do().
Into(result)
return
}
// Delete takes name of the ingressRouteTCP and deletes it. Returns an error if one occurs.
func (c *ingressRouteTCPs) Delete(name string, options *v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("ingressroutetcps").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *ingressRouteTCPs) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("ingressroutetcps").
VersionedParams(&listOptions, scheme.ParameterCodec).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched ingressRouteTCP.
func (c *ingressRouteTCPs) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.IngressRouteTCP, err error) {
result = &v1alpha1.IngressRouteTCP{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("ingressroutetcps").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View file

@ -36,6 +36,7 @@ import (
type TraefikV1alpha1Interface interface {
RESTClient() rest.Interface
IngressRoutesGetter
IngressRouteTCPsGetter
MiddlewaresGetter
}
@ -48,6 +49,10 @@ func (c *TraefikV1alpha1Client) IngressRoutes(namespace string) IngressRouteInte
return newIngressRoutes(c, namespace)
}
func (c *TraefikV1alpha1Client) IngressRouteTCPs(namespace string) IngressRouteTCPInterface {
return newIngressRouteTCPs(c, namespace)
}
func (c *TraefikV1alpha1Client) Middlewares(namespace string) MiddlewareInterface {
return newMiddlewares(c, namespace)
}

View file

@ -63,6 +63,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
// Group=traefik.containo.us, Version=v1alpha1
case v1alpha1.SchemeGroupVersion.WithResource("ingressroutes"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().IngressRoutes().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("ingressroutetcps"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().IngressRouteTCPs().Informer()}, nil
case v1alpha1.SchemeGroupVersion.WithResource("middlewares"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().Middlewares().Informer()}, nil

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/pkg/provider/kubernetes/crd/generated/clientset/versioned"
internalinterfaces "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1"
traefikv1alpha1 "github.com/containous/traefik/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"
)
// IngressRouteTCPInformer provides access to a shared informer and lister for
// IngressRouteTCPs.
type IngressRouteTCPInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.IngressRouteTCPLister
}
type ingressRouteTCPInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewIngressRouteTCPInformer constructs a new informer for IngressRouteTCP 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 NewIngressRouteTCPInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredIngressRouteTCPInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredIngressRouteTCPInformer constructs a new informer for IngressRouteTCP 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 NewFilteredIngressRouteTCPInformer(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().IngressRouteTCPs(namespace).List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.TraefikV1alpha1().IngressRouteTCPs(namespace).Watch(options)
},
},
&traefikv1alpha1.IngressRouteTCP{},
resyncPeriod,
indexers,
)
}
func (f *ingressRouteTCPInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredIngressRouteTCPInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *ingressRouteTCPInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&traefikv1alpha1.IngressRouteTCP{}, f.defaultInformer)
}
func (f *ingressRouteTCPInformer) Lister() v1alpha1.IngressRouteTCPLister {
return v1alpha1.NewIngressRouteTCPLister(f.Informer().GetIndexer())
}

View file

@ -34,6 +34,8 @@ import (
type Interface interface {
// IngressRoutes returns a IngressRouteInformer.
IngressRoutes() IngressRouteInformer
// IngressRouteTCPs returns a IngressRouteTCPInformer.
IngressRouteTCPs() IngressRouteTCPInformer
// Middlewares returns a MiddlewareInformer.
Middlewares() MiddlewareInformer
}
@ -54,6 +56,11 @@ func (v *version) IngressRoutes() IngressRouteInformer {
return &ingressRouteInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// IngressRouteTCPs returns a IngressRouteTCPInformer.
func (v *version) IngressRouteTCPs() IngressRouteTCPInformer {
return &ingressRouteTCPInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// Middlewares returns a MiddlewareInformer.
func (v *version) Middlewares() MiddlewareInformer {
return &middlewareInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View file

@ -34,6 +34,14 @@ type IngressRouteListerExpansion interface{}
// IngressRouteNamespaceLister.
type IngressRouteNamespaceListerExpansion interface{}
// IngressRouteTCPListerExpansion allows custom methods to be added to
// IngressRouteTCPLister.
type IngressRouteTCPListerExpansion interface{}
// IngressRouteTCPNamespaceListerExpansion allows custom methods to be added to
// IngressRouteTCPNamespaceLister.
type IngressRouteTCPNamespaceListerExpansion interface{}
// MiddlewareListerExpansion allows custom methods to be added to
// MiddlewareLister.
type MiddlewareListerExpansion 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/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// IngressRouteTCPLister helps list IngressRouteTCPs.
type IngressRouteTCPLister interface {
// List lists all IngressRouteTCPs in the indexer.
List(selector labels.Selector) (ret []*v1alpha1.IngressRouteTCP, err error)
// IngressRouteTCPs returns an object that can list and get IngressRouteTCPs.
IngressRouteTCPs(namespace string) IngressRouteTCPNamespaceLister
IngressRouteTCPListerExpansion
}
// ingressRouteTCPLister implements the IngressRouteTCPLister interface.
type ingressRouteTCPLister struct {
indexer cache.Indexer
}
// NewIngressRouteTCPLister returns a new IngressRouteTCPLister.
func NewIngressRouteTCPLister(indexer cache.Indexer) IngressRouteTCPLister {
return &ingressRouteTCPLister{indexer: indexer}
}
// List lists all IngressRouteTCPs in the indexer.
func (s *ingressRouteTCPLister) List(selector labels.Selector) (ret []*v1alpha1.IngressRouteTCP, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.IngressRouteTCP))
})
return ret, err
}
// IngressRouteTCPs returns an object that can list and get IngressRouteTCPs.
func (s *ingressRouteTCPLister) IngressRouteTCPs(namespace string) IngressRouteTCPNamespaceLister {
return ingressRouteTCPNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// IngressRouteTCPNamespaceLister helps list and get IngressRouteTCPs.
type IngressRouteTCPNamespaceLister interface {
// List lists all IngressRouteTCPs in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1alpha1.IngressRouteTCP, err error)
// Get retrieves the IngressRouteTCP from the indexer for a given namespace and name.
Get(name string) (*v1alpha1.IngressRouteTCP, error)
IngressRouteTCPNamespaceListerExpansion
}
// ingressRouteTCPNamespaceLister implements the IngressRouteTCPNamespaceLister
// interface.
type ingressRouteTCPNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all IngressRouteTCPs in the indexer for a given namespace.
func (s ingressRouteTCPNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.IngressRouteTCP, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.IngressRouteTCP))
})
return ret, err
}
// Get retrieves the IngressRouteTCP from the indexer for a given namespace and name.
func (s ingressRouteTCPNamespaceLister) Get(name string) (*v1alpha1.IngressRouteTCP, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("ingressroutetcp"), name)
}
return obj.(*v1alpha1.IngressRouteTCP), nil
}

View file

@ -151,6 +151,71 @@ func checkStringQuoteValidity(value string) error {
return err
}
func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([]config.TCPServer, error) {
service, exists, err := client.GetService(namespace, svc.Name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.New("service not found")
}
var portSpec *corev1.ServicePort
for _, p := range service.Spec.Ports {
if svc.Port == p.Port {
portSpec = &p
break
}
}
if portSpec == nil {
return nil, errors.New("service port not found")
}
var servers []config.TCPServer
if service.Spec.Type == corev1.ServiceTypeExternalName {
servers = append(servers, config.TCPServer{
Address: fmt.Sprintf("%s:%d", service.Spec.ExternalName, portSpec.Port),
})
} else {
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
if endpointsErr != nil {
return nil, endpointsErr
}
if !endpointsExists {
return nil, errors.New("endpoints not found")
}
if len(endpoints.Subsets) == 0 {
return nil, errors.New("subset not found")
}
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")
}
for _, addr := range subset.Addresses {
servers = append(servers, config.TCPServer{
Address: fmt.Sprintf("%s:%d", addr.IP, port),
})
}
}
}
return servers, nil
}
func loadServers(client Client, namespace string, svc v1alpha1.Service) ([]config.Server, error) {
strategy := svc.Strategy
if strategy == "" {
@ -169,18 +234,15 @@ func loadServers(client Client, namespace string, svc v1alpha1.Service) ([]confi
return nil, errors.New("service not found")
}
var portSpec corev1.ServicePort
var match bool
// TODO: support name ports? do we actually care?
var portSpec *corev1.ServicePort
for _, p := range service.Spec.Ports {
if svc.Port == p.Port {
portSpec = p
match = true
portSpec = &p
break
}
}
if !match {
if portSpec == nil {
return nil, errors.New("service port not found")
}
@ -233,14 +295,16 @@ func loadServers(client Client, namespace string, svc v1alpha1.Service) ([]confi
}
func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Client) *config.Configuration {
conf := &config.Configuration{
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
}
tlsConfigs := make(map[string]*tls.Configuration)
@ -252,7 +316,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
continue
}
err := getTLS(ctx, ingressRoute, client, tlsConfigs)
err := getTLSHTTP(ctx, ingressRoute, client, tlsConfigs)
if err != nil {
logger.Errorf("Error configuring TLS: %v", err)
}
@ -306,13 +370,11 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
mds = append(mds, makeID(ns, mi.Name))
}
h := sha256.New()
_, err = h.Write([]byte(route.Match))
key, err := makeServiceKey(route.Match, ingressName)
if err != nil {
logger.Error(err)
continue
}
key := fmt.Sprintf("%s-%.10x", ingressName, h.Sum(nil))
serviceName := makeID(ingressRoute.Namespace, key)
@ -336,19 +398,103 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
}
}
conf.TLS = getTLSConfig(tlsConfigs)
for _, middleware := range client.GetMiddlewares() {
conf.HTTP.Middlewares[makeID(middleware.Namespace, middleware.Name)] = &middleware.Spec
}
for _, ingressRouteTCP := range client.GetIngressRouteTCPs() {
logger := log.FromContext(log.With(ctx, log.Str("ingress", ingressRouteTCP.Name), log.Str("namespace", ingressRouteTCP.Namespace)))
if !shouldProcessIngress(p.IngressClass, ingressRouteTCP.Annotations[annotationKubernetesIngressClass]) {
continue
}
if ingressRouteTCP.Spec.TLS != nil && !ingressRouteTCP.Spec.TLS.Passthrough {
err := getTLSTCP(ctx, ingressRouteTCP, client, tlsConfigs)
if err != nil {
logger.Errorf("Error configuring TLS: %v", err)
}
}
ingressName := ingressRouteTCP.Name
if len(ingressName) == 0 {
ingressName = ingressRouteTCP.GenerateName
}
for _, route := range ingressRouteTCP.Spec.Routes {
if len(route.Match) == 0 {
logger.Errorf("Empty match rule")
continue
}
if err := checkStringQuoteValidity(route.Match); err != nil {
logger.Errorf("Invalid syntax for match rule: %s", route.Match)
continue
}
var allServers []config.TCPServer
for _, service := range route.Services {
servers, err := loadTCPServers(client, ingressRouteTCP.Namespace, service)
if err != nil {
logger.
WithField("serviceName", service.Name).
WithField("servicePort", service.Port).
Errorf("Cannot create service: %v", err)
continue
}
allServers = append(allServers, servers...)
}
key, e := makeServiceKey(route.Match, ingressName)
if e != nil {
logger.Error(e)
continue
}
serviceName := makeID(ingressRouteTCP.Namespace, key)
conf.TCP.Routers[serviceName] = &config.TCPRouter{
EntryPoints: ingressRouteTCP.Spec.EntryPoints,
Rule: route.Match,
Service: serviceName,
}
if ingressRouteTCP.Spec.TLS != nil {
conf.TCP.Routers[serviceName].TLS = &config.RouterTCPTLSConfig{
Passthrough: ingressRouteTCP.Spec.TLS.Passthrough,
}
}
conf.TCP.Services[serviceName] = &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: allServers,
},
}
}
}
conf.TLS = getTLSConfig(tlsConfigs)
return conf
}
func makeServiceKey(rule, ingressName string) (string, error) {
h := sha256.New()
if _, err := h.Write([]byte(rule)); err != nil {
return "", err
}
ingressName = strings.ReplaceAll(ingressName, ".", "-")
key := fmt.Sprintf("%s-%.10x", ingressName, h.Sum(nil))
return key, nil
}
func makeID(namespace, name string) string {
if namespace == "" {
return name
}
return namespace + "/" + name
}
@ -357,7 +503,7 @@ func shouldProcessIngress(ingressClass string, ingressClassAnnotation string) bo
(len(ingressClass) == 0 && ingressClassAnnotation == traefikDefaultIngressClass)
}
func getTLS(ctx context.Context, ingressRoute *v1alpha1.IngressRoute, k8sClient Client, tlsConfigs map[string]*tls.Configuration) error {
func getTLSHTTP(ctx context.Context, ingressRoute *v1alpha1.IngressRoute, k8sClient Client, tlsConfigs map[string]*tls.Configuration) error {
if ingressRoute.Spec.TLS == nil {
return nil
}
@ -368,30 +514,61 @@ func getTLS(ctx context.Context, ingressRoute *v1alpha1.IngressRoute, k8sClient
configKey := ingressRoute.Namespace + "/" + ingressRoute.Spec.TLS.SecretName
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
secret, exists, err := k8sClient.GetSecret(ingressRoute.Namespace, ingressRoute.Spec.TLS.SecretName)
if err != nil {
return fmt.Errorf("failed to fetch secret %s/%s: %v", ingressRoute.Namespace, ingressRoute.Spec.TLS.SecretName, err)
}
if !exists {
return fmt.Errorf("secret %s/%s does not exist", ingressRoute.Namespace, ingressRoute.Spec.TLS.SecretName)
}
cert, key, err := getCertificateBlocks(secret, ingressRoute.Namespace, ingressRoute.Spec.TLS.SecretName)
tlsConf, err := getTLS(k8sClient, ingressRoute.Spec.TLS.SecretName, ingressRoute.Namespace)
if err != nil {
return err
}
tlsConfigs[configKey] = &tls.Configuration{
Certificate: &tls.Certificate{
CertFile: tls.FileOrContent(cert),
KeyFile: tls.FileOrContent(key),
},
}
tlsConfigs[configKey] = tlsConf
}
return nil
}
func getTLSTCP(ctx context.Context, ingressRoute *v1alpha1.IngressRouteTCP, k8sClient Client, tlsConfigs map[string]*tls.Configuration) error {
if ingressRoute.Spec.TLS == nil {
return nil
}
if ingressRoute.Spec.TLS.SecretName == "" {
log.FromContext(ctx).Debugf("Skipping TLS sub-section for TCP: No secret name provided")
return nil
}
configKey := ingressRoute.Namespace + "/" + ingressRoute.Spec.TLS.SecretName
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
tlsConf, err := getTLS(k8sClient, ingressRoute.Spec.TLS.SecretName, ingressRoute.Namespace)
if err != nil {
return err
}
tlsConfigs[configKey] = tlsConf
}
return nil
}
func getTLS(k8sClient Client, secretName, namespace string) (*tls.Configuration, error) {
secret, exists, err := k8sClient.GetSecret(namespace, secretName)
if err != nil {
return nil, fmt.Errorf("failed to fetch secret %s/%s: %v", namespace, secretName, err)
}
if !exists {
return nil, fmt.Errorf("secret %s/%s does not exist", namespace, secretName)
}
cert, key, err := getCertificateBlocks(secret, namespace, secretName)
if err != nil {
return nil, err
}
return &tls.Configuration{
Certificate: &tls.Certificate{
CertFile: tls.FileOrContent(cert),
KeyFile: tls.FileOrContent(key),
},
}, nil
}
func getTLSConfig(tlsConfigs map[string]*tls.Configuration) []*tls.Configuration {
var secretNames []string
for secretName := range tlsConfigs {

View file

@ -12,6 +12,346 @@ import (
var _ provider.Provider = (*Provider)(nil)
func TestLoadIngressRouteTCPs(t *testing.T) {
testCases := []struct {
desc string
ingressClass string
paths []string
expected *config.Configuration
}{
{
desc: "Empty",
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
},
},
{
desc: "Simple Ingress Route, with foo entrypoint",
paths: []string{"tcp/services.yml", "tcp/simple.yml"},
expected: &config.Configuration{
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"default/test-crd-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default/test-crd-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Services: map[string]*config.TCPService{
"default/test-crd-fdd3e9338e47a45efefc": {
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
},
},
},
},
},
{
desc: "One ingress Route with two different rules",
paths: []string{"tcp/services.yml", "tcp/with_two_rules.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"default/test-crd-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default/test-crd-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
"default/test-crd-f44ce589164e656d231c": {
EntryPoints: []string{"foo"},
Service: "default/test-crd-f44ce589164e656d231c",
Rule: "HostSNI(`bar.com`)",
},
},
Services: map[string]*config.TCPService{
"default/test-crd-fdd3e9338e47a45efefc": {
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
},
"default/test-crd-f44ce589164e656d231c": {
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
},
},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
},
},
{
desc: "One ingress Route with two different services, their servers will merge",
paths: []string{"tcp/services.yml", "tcp/with_two_services.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"default/test-crd-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default/test-crd-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Services: map[string]*config.TCPService{
"default/test-crd-fdd3e9338e47a45efefc": {
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
{
Address: "10.10.0.3:8080",
Port: "",
},
{
Address: "10.10.0.4:8080",
Port: "",
},
},
},
}},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
},
},
{
desc: "Ingress class does not match",
paths: []string{"tcp/services.yml", "tcp/simple.yml"},
ingressClass: "tchouk",
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
},
},
{
desc: "Route with empty rule value is ignored",
paths: []string{"tcp/services.yml", "tcp/with_no_rule_value.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
},
},
{
desc: "check rule quoting validity",
paths: []string{"tcp/services.yml", "tcp/with_bad_host_rule.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
},
},
{
desc: "TLS",
paths: []string{"tcp/services.yml", "tcp/with_tls.yml"},
expected: &config.Configuration{
TLS: []*tls.Configuration{
{
Certificate: &tls.Certificate{
CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"),
KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"),
},
},
},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"default/test-crd-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default/test-crd-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
TLS: &config.RouterTCPTLSConfig{},
},
},
Services: map[string]*config.TCPService{
"default/test-crd-fdd3e9338e47a45efefc": {
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
},
},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
},
},
{
desc: "TLS with passthrough",
paths: []string{"tcp/services.yml", "tcp/with_tls_passthrough.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"default/test-crd-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default/test-crd-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
TLS: &config.RouterTCPTLSConfig{
Passthrough: true,
},
},
},
Services: map[string]*config.TCPService{
"default/test-crd-fdd3e9338e47a45efefc": {
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
},
},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
},
},
{
desc: "TLS with ACME",
paths: []string{"tcp/services.yml", "tcp/with_tls_acme.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{
"default/test-crd-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default/test-crd-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
TLS: &config.RouterTCPTLSConfig{},
},
},
Services: map[string]*config.TCPService{
"default/test-crd-fdd3e9338e47a45efefc": {
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "10.10.0.1:8000",
Port: "",
},
{
Address: "10.10.0.2:8000",
Port: "",
},
},
},
},
},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
if test.expected == nil {
return
}
p := Provider{IngressClass: test.ingressClass}
conf := p.loadConfigurationFromIngresses(context.Background(), newClientMock(test.paths...))
assert.Equal(t, test.expected, conf)
})
}
}
func TestLoadIngressRoutes(t *testing.T) {
testCases := []struct {
desc string
@ -22,7 +362,10 @@ func TestLoadIngressRoutes(t *testing.T) {
{
desc: "Empty",
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
@ -34,19 +377,22 @@ func TestLoadIngressRoutes(t *testing.T) {
desc: "Simple Ingress Route, with foo entrypoint",
paths: []string{"services.yml", "simple.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
EntryPoints: []string{"foo"},
Service: "default/test.crd-6b204d94623b3df4370c",
Service: "default/test-crd-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
},
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
@ -67,12 +413,15 @@ func TestLoadIngressRoutes(t *testing.T) {
desc: "Simple Ingress Route with middleware",
paths: []string{"services.yml", "with_middleware.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"default/test2.crd-23c7f4c450289ee29016": {
"default/test2-crd-23c7f4c450289ee29016": {
EntryPoints: []string{"web"},
Service: "default/test2.crd-23c7f4c450289ee29016",
Service: "default/test2-crd-23c7f4c450289ee29016",
Rule: "Host(`foo.com`) && PathPrefix(`/tobestripped`)",
Priority: 12,
Middlewares: []string{"default/stripprefix", "foo/addprefix"},
@ -91,7 +440,7 @@ func TestLoadIngressRoutes(t *testing.T) {
},
},
Services: map[string]*config.Service{
"default/test2.crd-23c7f4c450289ee29016": {
"default/test2-crd-23c7f4c450289ee29016": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
@ -112,25 +461,28 @@ func TestLoadIngressRoutes(t *testing.T) {
desc: "One ingress Route with two different rules",
paths: []string{"services.yml", "with_two_rules.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
EntryPoints: []string{"web"},
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Service: "default/test.crd-6b204d94623b3df4370c",
Service: "default/test-crd-6b204d94623b3df4370c",
Priority: 14,
},
"default/test.crd-77c62dfe9517144aeeaa": {
"default/test-crd-77c62dfe9517144aeeaa": {
EntryPoints: []string{"web"},
Service: "default/test.crd-77c62dfe9517144aeeaa",
Service: "default/test-crd-77c62dfe9517144aeeaa",
Rule: "Host(`foo.com`) && PathPrefix(`/foo`)",
Priority: 12,
},
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
@ -143,7 +495,7 @@ func TestLoadIngressRoutes(t *testing.T) {
PassHostHeader: true,
},
},
"default/test.crd-77c62dfe9517144aeeaa": {
"default/test-crd-77c62dfe9517144aeeaa": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
@ -164,19 +516,22 @@ func TestLoadIngressRoutes(t *testing.T) {
desc: "One ingress Route with two different services, their servers will merge",
paths: []string{"services.yml", "with_two_services.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"default/test.crd-77c62dfe9517144aeeaa": {
"default/test-crd-77c62dfe9517144aeeaa": {
EntryPoints: []string{"web"},
Service: "default/test.crd-77c62dfe9517144aeeaa",
Service: "default/test-crd-77c62dfe9517144aeeaa",
Rule: "Host(`foo.com`) && PathPrefix(`/foo`)",
Priority: 12,
},
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"default/test.crd-77c62dfe9517144aeeaa": {
"default/test-crd-77c62dfe9517144aeeaa": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
@ -204,7 +559,10 @@ func TestLoadIngressRoutes(t *testing.T) {
paths: []string{"services.yml", "simple.yml"},
ingressClass: "tchouk",
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
@ -216,7 +574,10 @@ func TestLoadIngressRoutes(t *testing.T) {
desc: "Route with empty rule value is ignored",
paths: []string{"services.yml", "with_no_rule_value.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
@ -228,7 +589,10 @@ func TestLoadIngressRoutes(t *testing.T) {
desc: "Route with kind not of a rule type (empty kind) is ignored",
paths: []string{"services.yml", "with_wrong_rule_kind.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
@ -240,7 +604,10 @@ func TestLoadIngressRoutes(t *testing.T) {
desc: "check rule quoting validity",
paths: []string{"services.yml", "with_bad_host_rule.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
@ -260,12 +627,15 @@ func TestLoadIngressRoutes(t *testing.T) {
},
},
},
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
EntryPoints: []string{"web"},
Service: "default/test.crd-6b204d94623b3df4370c",
Service: "default/test-crd-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
TLS: &config.RouterTLSConfig{},
@ -273,7 +643,7 @@ func TestLoadIngressRoutes(t *testing.T) {
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
@ -294,12 +664,15 @@ func TestLoadIngressRoutes(t *testing.T) {
desc: "TLS with ACME",
paths: []string{"services.yml", "with_tls_acme.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
EntryPoints: []string{"web"},
Service: "default/test.crd-6b204d94623b3df4370c",
Service: "default/test-crd-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
TLS: &config.RouterTLSConfig{},
@ -307,7 +680,7 @@ func TestLoadIngressRoutes(t *testing.T) {
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
@ -328,19 +701,22 @@ func TestLoadIngressRoutes(t *testing.T) {
desc: "Simple Ingress Route, defaulting to https for servers",
paths: []string{"services.yml", "with_https_default.yml"},
expected: &config.Configuration{
TCP: &config.TCPConfiguration{},
TCP: &config.TCPConfiguration{
Routers: map[string]*config.TCPRouter{},
Services: map[string]*config.TCPService{},
},
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
EntryPoints: []string{"foo"},
Service: "default/test.crd-6b204d94623b3df4370c",
Service: "default/test-crd-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
},
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"default/test.crd-6b204d94623b3df4370c": {
"default/test-crd-6b204d94623b3df4370c": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{

View file

@ -21,7 +21,12 @@ type Route struct {
}
// TLS contains the TLS certificates configuration of the routes. To enable
// Let's Encrypt, set a SecretName with an empty value.
// Let's Encrypt, use an empty TLS struct, e.g. in YAML:
//
// tls: {} # inline format
//
// tls:
// secretName: # block format
type TLS struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the
// certificate details.

View file

@ -0,0 +1,58 @@
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// IngressRouteTCPSpec is a specification for a IngressRouteTCPSpec resource.
type IngressRouteTCPSpec struct {
Routes []RouteTCP `json:"routes"`
EntryPoints []string `json:"entryPoints"`
TLS *TLSTCP `json:"tls,omitempty"`
}
// RouteTCP contains the set of routes.
type RouteTCP struct {
Match string `json:"match"`
Services []ServiceTCP `json:"services,omitempty"`
}
// TLSTCP contains the TLS certificates configuration of the routes. To enable
// Let's Encrypt, use an empty TLS struct, e.g. in YAML:
//
// tls: {} # inline format
//
// tls:
// secretName: # block format
type TLSTCP struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the
// certificate details.
SecretName string `json:"secretName"`
Passthrough bool `json:"passthrough"`
}
// ServiceTCP defines an upstream to proxy traffic.
type ServiceTCP struct {
Name string `json:"name"`
Port int32 `json:"port"`
}
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// IngressRouteTCP is an Ingress CRD specification.
type IngressRouteTCP struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
Spec IngressRouteTCPSpec `json:"spec"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// IngressRouteTCPList is a list of IngressRoutes.
type IngressRouteTCPList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []IngressRouteTCP `json:"items"`
}

View file

@ -35,6 +35,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&IngressRoute{},
&IngressRouteList{},
&IngressRouteTCP{},
&IngressRouteTCPList{},
&Middleware{},
&MiddlewareList{},
)

View file

@ -148,6 +148,99 @@ func (in *IngressRouteSpec) DeepCopy() *IngressRouteSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IngressRouteTCP) DeepCopyInto(out *IngressRouteTCP) {
*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 IngressRouteTCP.
func (in *IngressRouteTCP) DeepCopy() *IngressRouteTCP {
if in == nil {
return nil
}
out := new(IngressRouteTCP)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *IngressRouteTCP) 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 *IngressRouteTCPList) DeepCopyInto(out *IngressRouteTCPList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]IngressRouteTCP, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressRouteTCPList.
func (in *IngressRouteTCPList) DeepCopy() *IngressRouteTCPList {
if in == nil {
return nil
}
out := new(IngressRouteTCPList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *IngressRouteTCPList) 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 *IngressRouteTCPSpec) DeepCopyInto(out *IngressRouteTCPSpec) {
*out = *in
if in.Routes != nil {
in, out := &in.Routes, &out.Routes
*out = make([]RouteTCP, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.EntryPoints != nil {
in, out := &in.EntryPoints, &out.EntryPoints
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSTCP)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressRouteTCPSpec.
func (in *IngressRouteTCPSpec) DeepCopy() *IngressRouteTCPSpec {
if in == nil {
return nil
}
out := new(IngressRouteTCPSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Middleware) DeepCopyInto(out *Middleware) {
*out = *in
@ -252,6 +345,27 @@ func (in *Route) DeepCopy() *Route {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteTCP) DeepCopyInto(out *RouteTCP) {
*out = *in
if in.Services != nil {
in, out := &in.Services, &out.Services
*out = make([]ServiceTCP, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTCP.
func (in *RouteTCP) DeepCopy() *RouteTCP {
if in == nil {
return nil
}
out := new(RouteTCP)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Service) DeepCopyInto(out *Service) {
*out = *in
@ -273,6 +387,22 @@ func (in *Service) DeepCopy() *Service {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceTCP.
func (in *ServiceTCP) DeepCopy() *ServiceTCP {
if in == nil {
return nil
}
out := new(ServiceTCP)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLS) DeepCopyInto(out *TLS) {
*out = *in
@ -288,3 +418,19 @@ func (in *TLS) DeepCopy() *TLS {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSTCP) DeepCopyInto(out *TLSTCP) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSTCP.
func (in *TLSTCP) DeepCopy() *TLSTCP {
if in == nil {
return nil
}
out := new(TLSTCP)
in.DeepCopyInto(out)
return out
}

View file

@ -318,7 +318,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
}
serviceName := ingress.Namespace + "/" + p.Backend.ServiceName + "/" + p.Backend.ServicePort.String()
serviceName = strings.ReplaceAll(serviceName, ".", "-")
var rules []string
if len(rule.Host) > 0 {
rules = []string{"Host(`" + rule.Host + "`)"}

View file

@ -43,7 +43,7 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han
loadBalancer := tcp.NewRRLoadBalancer()
for _, server := range conf.LoadBalancer.Servers {
for name, server := range conf.LoadBalancer.Servers {
if _, _, err := net.SplitHostPort(server.Address); err != nil {
logger.Errorf("In service %q: %v", serviceQualifiedName, err)
continue
@ -56,6 +56,7 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han
}
loadBalancer.AddServer(handler)
logger.WithField(log.ServerName, name).Debugf("Creating TCP server %d at %s", name, server.Address)
}
return loadBalancer, nil
}