From 969dd088a26e7f3153b3fd2161fa59642bba1970 Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Mon, 4 Oct 2021 15:46:08 +0200 Subject: [PATCH] gateway api: support RouteNamespaces Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com> --- docs/content/migration/v2.md | 6 + .../kubernetes-gateway-rbac.yml | 7 + .../routing/providers/kubernetes-gateway.md | 63 +- pkg/provider/kubernetes/gateway/client.go | 113 +- .../kubernetes/gateway/client_mock_test.go | 62 +- .../fixtures/httproute/with_namespace_all.yml | 68 + .../httproute/with_namespace_same.yml | 68 + .../httproute/with_namespace_selector.yml | 79 ++ .../gateway/fixtures/mixed/simple.yml | 1 + ...with_multiple_protocol_using_same_port.yml | 1 + .../fixtures/mixed/with_namespace_all.yml | 177 +++ .../fixtures/mixed/with_namespace_same.yml | 177 +++ .../mixed/with_namespace_selector.yml | 201 +++ .../kubernetes/gateway/fixtures/services.yml | 72 +- .../fixtures/tcproute/with_namespace_all.yml | 57 + .../fixtures/tcproute/with_namespace_same.yml | 57 + .../tcproute/with_namespace_selector.yml | 68 + .../tlsroute/with_multiple_routes_kind.yml | 1 - .../fixtures/tlsroute/with_namespace_all.yml | 66 + .../fixtures/tlsroute/with_namespace_same.yml | 65 + .../tlsroute/with_namespace_selector.yml | 76 ++ pkg/provider/kubernetes/gateway/kubernetes.go | 126 +- .../kubernetes/gateway/kubernetes_test.go | 1111 +++++++++++++++++ pkg/provider/kubernetes/k8s/parser.go | 2 +- 24 files changed, 2629 insertions(+), 95 deletions(-) create mode 100644 pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_all.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_same.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_selector.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_all.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_same.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_selector.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_all.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_same.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_selector.yml diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 1298354b2..483e599bb 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -424,3 +424,9 @@ Traefik v2.6 introduces the `AdvertisedPort` option, which allows advertising, in the `Alt-Svc` header, a UDP port different from the one on which Traefik is actually listening (the EntryPoint's port). By doing so, it introduces a new configuration structure `http3`, which replaces the `enableHTTP3` option (which therefore doesn't exist anymore). To enable HTTP3 on an EntryPoint, please check out the [HTTP3 configuration](../routing/entrypoints.md#http3) documentation. + +### Kubernetes Gateway provider + +In `v2.6`, the [Kubernetes Gateway provider](../providers/kubernetes-gateway.md) now supports [route namespaces](https://gateway-api.sigs.k8s.io/v1alpha1/references/spec/#networking.x-k8s.io/v1alpha1.RouteNamespaces) selectors, +which requires Traefik to fetch and watch the cluster namespaces. +Therefore, the RBAC definitions must be updated, please check out the [RBAC configuration reference](../reference/dynamic-configuration/kubernetes-gateway.md#rbac). diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml index c864f55af..7542b170b 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml @@ -4,6 +4,13 @@ kind: ClusterRole metadata: name: gateway-role rules: + - apiGroups: + - "" + resources: + - namespaces + verbs: + - list + - watch - apiGroups: - "" resources: diff --git a/docs/content/routing/providers/kubernetes-gateway.md b/docs/content/routing/providers/kubernetes-gateway.md index 97a857eb9..43adff0db 100644 --- a/docs/content/routing/providers/kubernetes-gateway.md +++ b/docs/content/routing/providers/kubernetes-gateway.md @@ -97,8 +97,13 @@ Depending on the Listener Protocol, different modes and Route types are supporte port: 80 # [4] routes: # [8] kind: HTTPRoute # [9] - selector: # [10] - matchLabels: # [11] + namespaces: + from: Selector # [10] + selector: # [11] + matchLabels: + app: foo + selector: # [12] + matchLabels: app: foo ``` @@ -120,8 +125,13 @@ Depending on the Listener Protocol, different modes and Route types are supporte name: "mysecret" routes: # [8] kind: HTTPRoute # [9] - selector: # [10] - matchLabels: # [11] + namespaces: + from: Selector # [10] + selector: # [11] + matchLabels: + app: foo + selector: # [12] + matchLabels: app: foo ``` @@ -138,8 +148,13 @@ Depending on the Listener Protocol, different modes and Route types are supporte port: 8000 # [4] routes: # [8] kind: TCPRoute # [9] - selector: # [10] - matchLabels: # [11] + namespaces: + from: Selector # [10] + selector: # [11] + matchLabels: + app: footcp + selector: # [12] + matchLabels: app: footcp ``` @@ -162,24 +177,30 @@ Depending on the Listener Protocol, different modes and Route types are supporte name: "mysecret" routes: # [8] kind: TLSRoute # [9] - selector: # [10] - matchLabels: # [11] + namespaces: + from: Selector # [10] + selector: # [11] + matchLabels: + app: footcp + selector: # [12] + matchLabels: app: footcp ``` -| Ref | Attribute | Description | -|------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| [1] | `gatewayClassName` | GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. | -| [2] | `listeners` | Logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. | -| [3] | `protocol` | The network protocol this listener expects to receive (only HTTP and HTTPS are implemented). | -| [4] | `port` | The network port. | -| [5] | `hostname` | Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, “”, or *, all hostnames are matched. | -| [6] | `tls` | TLS configuration for the Listener. This field is required if the Protocol field is "HTTPS" or "TLS" and ignored otherwise. | -| [7] | `certificateRef` | The reference to Kubernetes object that contains a TLS certificate and private key. | -| [8] | `routes` | A schema for associating routes with the Listener using selectors. | -| [9] | `kind` | The kind of the referent. | -| [10] | `selector` | Routes in namespaces selected by the selector may be used by this Gateway routes to associate with the Gateway. | -| [11] | `matchLabels` | A set of route labels used for selecting routes to associate with the Gateway. | +| Ref | Attribute | Description | +|------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `gatewayClassName` | GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. | +| [2] | `listeners` | Logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. | +| [3] | `protocol` | The network protocol this listener expects to receive (only HTTP and HTTPS are implemented). | +| [4] | `port` | The network port. | +| [5] | `hostname` | Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, “”, or *, all hostnames are matched. | +| [6] | `tls` | TLS configuration for the Listener. This field is required if the Protocol field is "HTTPS" or "TLS" and ignored otherwise. | +| [7] | `certificateRef` | The reference to Kubernetes object that contains a TLS certificate and private key. | +| [8] | `routes` | A schema for associating routes with the Listener using selectors. | +| [9] | `kind` | The kind of the referent. | +| [10] | `from` | From indicates in which namespaces the Routes will be selected for this Gateway. Possible values are `All`, `Same` and `Selector` (Defaults to `Same`). | +| [11] | `selector` | Selector must be specified when From is set to `Selector`. In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. | +| [12] | `selector` | Selector specifies a set of route labels used for selecting routes to associate with the Gateway. An empty Selector matches all routes. | ### Kind: `HTTPRoute` diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index f514a6298..6ec6a2e5d 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -55,19 +55,21 @@ type Client interface { UpdateGatewayStatus(gateway *v1alpha1.Gateway, gatewayStatus v1alpha1.GatewayStatus) error UpdateGatewayClassStatus(gatewayClass *v1alpha1.GatewayClass, condition metav1.Condition) error GetGateways() []*v1alpha1.Gateway - GetHTTPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) - GetTCPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TCPRoute, error) - GetTLSRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TLSRoute, error) + GetHTTPRoutes(namespaces []string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) + GetTCPRoutes(namespaces []string, selector labels.Selector) ([]*v1alpha1.TCPRoute, error) + GetTLSRoutes(namespaces []string, selector labels.Selector) ([]*v1alpha1.TLSRoute, error) GetService(namespace, name string) (*corev1.Service, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) + GetNamespaces(selector labels.Selector) ([]string, error) } type clientWrapper struct { csGateway versioned.Interface csKube kubernetes.Interface + factoryNamespace informers.SharedInformerFactory factoryGatewayClass externalversions.SharedInformerFactory factoriesGateway map[string]externalversions.SharedInformerFactory factoriesKube map[string]informers.SharedInformerFactory @@ -171,6 +173,9 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< options.LabelSelector = c.labelSelector } + c.factoryNamespace = informers.NewSharedInformerFactory(c.csKube, resyncPeriod) + c.factoryNamespace.Core().V1().Namespaces().Informer().AddEventHandler(eventHandler) + c.factoryGatewayClass = externalversions.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, externalversions.WithTweakListOptions(labelSelectorOptions)) c.factoryGatewayClass.Networking().V1alpha1().GatewayClasses().Informer().AddEventHandler(eventHandler) @@ -193,6 +198,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< c.factoriesSecret[ns] = factorySecret } + c.factoryNamespace.Start(stopCh) c.factoryGatewayClass.Start(stopCh) for _, ns := range namespaces { @@ -201,6 +207,12 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< c.factoriesSecret[ns].Start(stopCh) } + for t, ok := range c.factoryNamespace.WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", t.String()) + } + } + for t, ok := range c.factoryGatewayClass.WaitForCacheSync(stopCh) { if !ok { return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", t.String()) @@ -230,53 +242,90 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< return eventCh, nil } -func (c *clientWrapper) GetHTTPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) { - if !c.isWatchedNamespace(namespace) { - return nil, fmt.Errorf("failed to get HTTPRoutes %s with labels selector %s: namespace is not within watched namespaces", namespace, selector) - } - httpRoutes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().HTTPRoutes().Lister().HTTPRoutes(namespace).List(selector) +func (c *clientWrapper) GetNamespaces(selector labels.Selector) ([]string, error) { + ns, err := c.factoryNamespace.Core().V1().Namespaces().Lister().List(selector) if err != nil { return nil, err } - if len(httpRoutes) == 0 { - log.WithoutContext().Debugf("No HTTPRoutes found in %q namespace with labels selector %s", namespace, selector) + var namespaces []string + for _, namespace := range ns { + if !c.isWatchedNamespace(namespace.Name) { + log.WithoutContext().Warnf("Namespace %q is not within watched namespaces", selector, namespace) + continue + } + namespaces = append(namespaces, namespace.Name) + } + return namespaces, nil +} + +func (c *clientWrapper) GetHTTPRoutes(namespaces []string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) { + var httpRoutes []*v1alpha1.HTTPRoute + for _, namespace := range namespaces { + if !c.isWatchedNamespace(namespace) { + log.WithoutContext().Warnf("Failed to get HTTPRoutes with labels selector %s: %q is not within watched namespaces", selector, namespace) + continue + } + + routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().HTTPRoutes().Lister().HTTPRoutes(namespace).List(selector) + if err != nil { + return nil, err + } + + if len(routes) == 0 { + log.WithoutContext().Debugf("No HTTPRoutes found in %q namespace with labels selector %s", namespace, selector) + continue + } + + httpRoutes = append(httpRoutes, routes...) } return httpRoutes, nil } -func (c *clientWrapper) GetTCPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TCPRoute, error) { - if !c.isWatchedNamespace(namespace) { - return nil, fmt.Errorf("failed to get TCPRoutes %s with labels selector %s: namespace is not within watched namespaces", namespace, selector) - } +func (c *clientWrapper) GetTCPRoutes(namespaces []string, selector labels.Selector) ([]*v1alpha1.TCPRoute, error) { + var tcpRoutes []*v1alpha1.TCPRoute + for _, namespace := range namespaces { + if !c.isWatchedNamespace(namespace) { + log.WithoutContext().Warnf("Failed to get TCPRoutes with labels selector %s: %q is not within watched namespaces", selector, namespace) + continue + } - tcpRoutes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().TCPRoutes().Lister().TCPRoutes(namespace).List(selector) - if err != nil { - return nil, err - } + routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().TCPRoutes().Lister().TCPRoutes(namespace).List(selector) + if err != nil { + return nil, err + } - if len(tcpRoutes) == 0 { - log.WithoutContext().Debugf("No TCPRoutes found in %q namespace with labels selector %s", namespace, selector) - } + if len(routes) == 0 { + log.WithoutContext().Debugf("No TCPRoutes found in %q namespace with labels selector %s", namespace, selector) + continue + } + tcpRoutes = append(tcpRoutes, routes...) + } return tcpRoutes, nil } -func (c *clientWrapper) GetTLSRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TLSRoute, error) { - if !c.isWatchedNamespace(namespace) { - return nil, fmt.Errorf("failed to get TLSRoutes %s with labels selector %s: namespace is not within watched namespaces", namespace, selector) - } +func (c *clientWrapper) GetTLSRoutes(namespaces []string, selector labels.Selector) ([]*v1alpha1.TLSRoute, error) { + var tlsRoutes []*v1alpha1.TLSRoute + for _, namespace := range namespaces { + if !c.isWatchedNamespace(namespace) { + log.WithoutContext().Warnf("Failed to get TLSRoutes with labels selector %s: %q is not within watched namespaces", selector, namespace) + continue + } - tlsRoutes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().TLSRoutes().Lister().TLSRoutes(namespace).List(selector) - if err != nil { - return nil, err - } + routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Networking().V1alpha1().TLSRoutes().Lister().TLSRoutes(namespace).List(selector) + if err != nil { + return nil, err + } - if len(tlsRoutes) == 0 { - log.WithoutContext().Debugf("No TLSRoutes found in %q namespace with labels selector %s", namespace, selector) - } + if len(routes) == 0 { + log.WithoutContext().Debugf("No TLSRoutes found in %q namespace with labels selector %s", namespace, selector) + continue + } + tlsRoutes = append(tlsRoutes, routes...) + } return tlsRoutes, nil } diff --git a/pkg/provider/kubernetes/gateway/client_mock_test.go b/pkg/provider/kubernetes/gateway/client_mock_test.go index f92063e44..df0f7fd97 100644 --- a/pkg/provider/kubernetes/gateway/client_mock_test.go +++ b/pkg/provider/kubernetes/gateway/client_mock_test.go @@ -24,9 +24,10 @@ func init() { } type clientMock struct { - services []*corev1.Service - secrets []*corev1.Secret - endpoints []*corev1.Endpoints + services []*corev1.Service + secrets []*corev1.Secret + endpoints []*corev1.Endpoints + namespaces []*corev1.Namespace apiServiceError error apiSecretError error @@ -57,6 +58,8 @@ func newClientMock(paths ...string) clientMock { c.services = append(c.services, o) case *corev1.Secret: c.secrets = append(c.secrets, o) + case *corev1.Namespace: + c.namespaces = append(c.namespaces, o) case *corev1.Endpoints: c.endpoints = append(c.endpoints, o) case *v1alpha1.GatewayClass: @@ -131,34 +134,51 @@ func (c clientMock) GetGateways() []*v1alpha1.Gateway { return c.gateways } -func (c clientMock) GetHTTPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) { - var httpRoutes []*v1alpha1.HTTPRoute +func inNamespace(m metav1.ObjectMeta, s string) bool { + return s == metav1.NamespaceAll || m.Namespace == s +} - for _, httpRoute := range c.httpRoutes { - if httpRoute.Namespace == namespace && selector.Matches(labels.Set(httpRoute.Labels)) { - httpRoutes = append(httpRoutes, httpRoute) +func (c clientMock) GetNamespaces(selector labels.Selector) ([]string, error) { + var ns []string + for _, namespace := range c.namespaces { + if selector.Matches(labels.Set(namespace.Labels)) { + ns = append(ns, namespace.Name) + } + } + return ns, nil +} + +func (c clientMock) GetHTTPRoutes(namespaces []string, selector labels.Selector) ([]*v1alpha1.HTTPRoute, error) { + var httpRoutes []*v1alpha1.HTTPRoute + for _, namespace := range namespaces { + for _, httpRoute := range c.httpRoutes { + if inNamespace(httpRoute.ObjectMeta, namespace) && selector.Matches(labels.Set(httpRoute.Labels)) { + httpRoutes = append(httpRoutes, httpRoute) + } } } return httpRoutes, nil } -func (c clientMock) GetTCPRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TCPRoute, error) { +func (c clientMock) GetTCPRoutes(namespaces []string, selector labels.Selector) ([]*v1alpha1.TCPRoute, error) { var tcpRoutes []*v1alpha1.TCPRoute - - for _, tcpRoute := range c.tcpRoutes { - if tcpRoute.Namespace == namespace && selector.Matches(labels.Set(tcpRoute.Labels)) { - tcpRoutes = append(tcpRoutes, tcpRoute) + for _, namespace := range namespaces { + for _, tcpRoute := range c.tcpRoutes { + if inNamespace(tcpRoute.ObjectMeta, namespace) && selector.Matches(labels.Set(tcpRoute.Labels)) { + tcpRoutes = append(tcpRoutes, tcpRoute) + } } } return tcpRoutes, nil } -func (c clientMock) GetTLSRoutes(namespace string, selector labels.Selector) ([]*v1alpha1.TLSRoute, error) { +func (c clientMock) GetTLSRoutes(namespaces []string, selector labels.Selector) ([]*v1alpha1.TLSRoute, error) { var tlsRoutes []*v1alpha1.TLSRoute - - for _, tlsRoute := range c.tlsRoutes { - if tlsRoute.Namespace == namespace && selector.Matches(labels.Set(tlsRoute.Labels)) { - tlsRoutes = append(tlsRoutes, tlsRoute) + for _, namespace := range namespaces { + for _, tlsRoute := range c.tlsRoutes { + if inNamespace(tlsRoute.ObjectMeta, namespace) && selector.Matches(labels.Set(tlsRoute.Labels)) { + tlsRoutes = append(tlsRoutes, tlsRoute) + } } } return tlsRoutes, nil @@ -170,7 +190,7 @@ func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, e } for _, service := range c.services { - if service.Namespace == namespace && service.Name == name { + if inNamespace(service.ObjectMeta, namespace) && service.Name == name { return service, true, nil } } @@ -183,7 +203,7 @@ func (c clientMock) GetEndpoints(namespace, name string) (*corev1.Endpoints, boo } for _, endpoints := range c.endpoints { - if endpoints.Namespace == namespace && endpoints.Name == name { + if inNamespace(endpoints.ObjectMeta, namespace) && endpoints.Name == name { return endpoints, true, nil } } @@ -197,7 +217,7 @@ func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, err } for _, secret := range c.secrets { - if secret.Namespace == namespace && secret.Name == name { + if inNamespace(secret.ObjectMeta, namespace) && secret.Name == name { return secret, true, nil } } diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_all.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_all.yml new file mode 100644 index 000000000..6257471a4 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_all.yml @@ -0,0 +1,68 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 80 + routes: + kind: HTTPRoute + namespaces: + from: All + selector: + matchLabels: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-default + namespace: default + labels: + app: foo +spec: + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /foo + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-bar + namespace: bar + labels: + app: foo +spec: + hostnames: + - "bar.com" + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - serviceName: whoami-bar + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_same.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_same.yml new file mode 100644 index 000000000..e9b10719d --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_same.yml @@ -0,0 +1,68 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 80 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-default + namespace: default + labels: + app: foo +spec: + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /foo + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-bar + namespace: bar + labels: + app: foo +spec: + hostnames: + - "bar.com" + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - serviceName: whoami-bar + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_selector.yml new file mode 100644 index 000000000..cdff9a276 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_selector.yml @@ -0,0 +1,79 @@ +--- +kind: Namespace +apiVersion: v1 +metadata: + name: bar + labels: + foo: bar + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 80 + routes: + kind: HTTPRoute + namespaces: + from: Selector + selector: + matchLabels: + foo: bar + selector: + matchLabels: + app: foo + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-default + namespace: default + labels: + app: foo +spec: + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /foo + forwardTo: + - serviceName: whoami + port: 80 + weight: 1 + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-bar + namespace: bar + labels: + app: foo +spec: + hostnames: + - "bar.com" + rules: + - matches: + - path: + type: Exact + value: /bar + forwardTo: + - serviceName: whoami-bar + port: 80 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml index 77c66c1c9..5906c05eb 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml @@ -100,6 +100,7 @@ spec: - serviceName: whoami port: 80 weight: 1 + --- kind: TCPRoute apiVersion: networking.x-k8s.io/v1alpha1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_protocol_using_same_port.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_protocol_using_same_port.yml index cb4dd126d..fa45fe0f3 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_protocol_using_same_port.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_protocol_using_same_port.yml @@ -93,6 +93,7 @@ spec: - serviceName: whoami port: 80 weight: 1 + --- kind: HTTPRoute apiVersion: networking.x-k8s.io/v1alpha1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml new file mode 100644 index 000000000..493700124 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml @@ -0,0 +1,177 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 9080 + routes: + kind: HTTPRoute + namespaces: + from: All + selector: + matchLabels: + app: http-app + - protocol: HTTPS + port: 9443 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: HTTPRoute + namespaces: + from: All + selector: + matchLabels: + app: http-app + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: All + selector: + matchLabels: + app: tcp-app + - protocol: TLS + port: 10000 + hostname: tls.foo.example.com + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: All + selector: + matchLabels: + app: tcp-app + - protocol: TLS + port: 11000 + hostname: pass.tls.foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-default + namespace: default + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-default + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-default + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-bar + namespace: bar + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami-bar + port: 80 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-bar + namespace: bar + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-bar + namespace: bar + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml new file mode 100644 index 000000000..e75c052e7 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml @@ -0,0 +1,177 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 9080 + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: http-app + - protocol: HTTPS + port: 9443 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: HTTPRoute + namespaces: + from: Same + selector: + matchLabels: + app: http-app + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + - protocol: TLS + port: 10000 + hostname: tls.foo.example.com + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: tcp-app + - protocol: TLS + port: 11000 + hostname: pass.tls.foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-default + namespace: default + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-default + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-default + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-bar + namespace: bar + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami-bar + port: 80 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-bar + namespace: bar + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-bar + namespace: bar + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml new file mode 100644 index 000000000..3d9e0aff8 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml @@ -0,0 +1,201 @@ +--- +kind: Namespace +apiVersion: v1 +metadata: + name: bar + labels: + foo: bar + +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: HTTP + port: 9080 + routes: + kind: HTTPRoute + namespaces: + from: Selector + selector: + matchLabels: + foo: bar + selector: + matchLabels: + app: http-app + - protocol: HTTPS + port: 9443 + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: HTTPRoute + namespaces: + from: Selector + selector: + matchLabels: + foo: bar + selector: + matchLabels: + app: http-app + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: Selector + selector: + matchLabels: + foo: bar + selector: + matchLabels: + app: tcp-app + - protocol: TLS + port: 10000 + hostname: tls.foo.example.com + tls: + certificateRef: + kind: Secret + name: supersecret + group: core + routes: + kind: TCPRoute + namespaces: + from: Selector + selector: + matchLabels: + foo: bar + selector: + matchLabels: + app: tcp-app + - protocol: TLS + port: 11000 + hostname: pass.tls.foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Selector + selector: + matchLabels: + foo: bar + selector: + matchLabels: + app: tls-app + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-default + namespace: default + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami + port: 80 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-default + namespace: default + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-default + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: HTTPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: http-app-bar + namespace: bar + labels: + app: http-app +spec: + rules: + - forwardTo: + - serviceName: whoami-bar + port: 80 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-bar + namespace: bar + labels: + app: tcp-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-bar + namespace: bar + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 + diff --git a/pkg/provider/kubernetes/gateway/fixtures/services.yml b/pkg/provider/kubernetes/gateway/fixtures/services.yml index b2c81d94b..2dee27a3e 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/services.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/services.yml @@ -33,6 +33,42 @@ subsets: - name: web2 port: 8000 +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami-bar + namespace: bar + +spec: + ports: + - name: web2 + port: 8000 + targetPort: web2 + - name: web + port: 80 + targetPort: web + selector: + app: containous + task: whoami + +--- +kind: Endpoints +apiVersion: v1 +metadata: + name: whoami-bar + namespace: bar + +subsets: + - addresses: + - ip: 10.10.0.11 + - ip: 10.10.0.12 + ports: + - name: web + port: 80 + - name: web2 + port: 8000 + --- apiVersion: v1 kind: Service @@ -164,7 +200,6 @@ spec: protocol: TCP port: 443 - --- kind: Endpoints apiVersion: v1 @@ -199,3 +234,38 @@ spec: - protocol: TCP port: 10000 name: tcp-2 + +--- +kind: Endpoints +apiVersion: v1 +metadata: + name: whoamitcp-bar + namespace: bar + +subsets: + - addresses: + - ip: 10.10.0.13 + - ip: 10.10.0.14 + ports: + - name: tcp-1 + protocol: TCP + port: 9000 + - name: tcp-2 + protocol: TCP + port: 10000 + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoamitcp-bar + namespace: bar + +spec: + ports: + - protocol: TCP + port: 9000 + name: tcp-1 + - protocol: TCP + port: 10000 + name: tcp-2 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_all.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_all.yml new file mode 100644 index 000000000..07d498fb4 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_all.yml @@ -0,0 +1,57 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tcp-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: All + selector: + matchLabels: + app: whoamitcp + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-default + namespace: default + labels: + app: whoamitcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-bar + namespace: bar + labels: + app: whoamitcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_same.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_same.yml new file mode 100644 index 000000000..02eedf059 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_same.yml @@ -0,0 +1,57 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tcp-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: Same + selector: + matchLabels: + app: whoamitcp + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-default + namespace: default + labels: + app: whoamitcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-bar + namespace: bar + labels: + app: whoamitcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_selector.yml new file mode 100644 index 000000000..39e3aa214 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_selector.yml @@ -0,0 +1,68 @@ +--- +kind: Namespace +apiVersion: v1 +metadata: + name: bar + labels: + foo: bar + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class + namespace: default +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-tcp-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TCP + port: 9000 + routes: + kind: TCPRoute + namespaces: + from: Selector + selector: + matchLabels: + foo: bar + selector: + matchLabels: + app: whoamitcp + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-default + namespace: default + labels: + app: whoamitcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + +--- +kind: TCPRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tcp-app-bar + namespace: bar + labels: + app: whoamitcp +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml index a156b6d3a..8d665c11e 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml @@ -50,7 +50,6 @@ spec: matchLabels: app: label-tls-app-1 - --- kind: TCPRoute apiVersion: networking.x-k8s.io/v1alpha1 diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_all.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_all.yml new file mode 100644 index 000000000..3a93290f4 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_all.yml @@ -0,0 +1,66 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9001 + hostname: foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: All + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-default + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + matches: + - snis: + - foo.default + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-bar + namespace: bar + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 + matches: + - snis: + - foo.bar + diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_same.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_same.yml new file mode 100644 index 000000000..0f7616120 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_same.yml @@ -0,0 +1,65 @@ +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9001 + hostname: foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Same + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-default + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + matches: + - snis: + - foo.default + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-bar + namespace: bar + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 + matches: + - snis: + - foo.bar diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_selector.yml new file mode 100644 index 000000000..cddbf5a2f --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_selector.yml @@ -0,0 +1,76 @@ +--- +kind: Namespace +apiVersion: v1 +metadata: + name: bar + labels: + foo: bar + +--- +kind: GatewayClass +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway-class +spec: + controller: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - protocol: TLS + port: 9001 + hostname: foo.example.com + tls: + mode: Passthrough + routes: + kind: TLSRoute + namespaces: + from: Selector + selector: + matchLabels: + foo: bar + selector: + matchLabels: + app: tls-app + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-default + namespace: default + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp + port: 9000 + weight: 1 + matches: + - snis: + - foo.default + +--- +kind: TLSRoute +apiVersion: networking.x-k8s.io/v1alpha1 +metadata: + name: tls-app-bar + namespace: bar + labels: + app: tls-app +spec: + rules: + - forwardTo: + - serviceName: whoamitcp-bar + port: 9000 + weight: 1 + matches: + - snis: + - foo.bar diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 5bea3a6af..d0be9c5ad 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -492,13 +492,23 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * } func gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener v1alpha1.Listener, gateway *v1alpha1.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { - // TODO: support RouteNamespaces selector := labels.Everything() + if listener.Routes.Selector != nil { - selector = labels.SelectorFromSet(listener.Routes.Selector.MatchLabels) + var err error + selector, err = metav1.LabelSelectorAsSelector(listener.Routes.Selector) + if err != nil { + return []metav1.Condition{{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Invalid routes selector: %v", err), + }} + } } - httpRoutes, err := client.GetHTTPRoutes(gateway.Namespace, selector) + namespaces, err := getRouteBindingSelectorNamespace(client, gateway.Namespace, listener.Routes.Namespaces) if err != nil { // update "ResolvedRefs" status true with "InvalidRoutesRef" reason return []metav1.Condition{{ @@ -506,7 +516,19 @@ func gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener v1alpha Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), - Message: fmt.Sprintf("Cannot fetch %ss for namespace %q and matchLabels %v", listener.Routes.Kind, gateway.Namespace, listener.Routes.Selector.MatchLabels), + Message: fmt.Sprintf("Invalid route namespaces selector: %v", err), + }} + } + + httpRoutes, err := client.GetHTTPRoutes(namespaces, selector) + if err != nil { + // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + return []metav1.Condition{{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Cannot fetch %ss: %v", listener.Routes.Kind, err), }} } @@ -577,7 +599,7 @@ func gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener v1alpha if len(routeRule.ForwardTo) == 1 && isInternalService(routeRule.ForwardTo[0]) { router.Service = routeRule.ForwardTo[0].BackendRef.Name } else { - wrrService, subServices, err := loadServices(client, gateway.Namespace, routeRule.ForwardTo) + wrrService, subServices, err := loadServices(client, httpRoute.Namespace, routeRule.ForwardTo) if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ @@ -585,7 +607,7 @@ func gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener v1alpha Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: string(v1alpha1.ListenerReasonDegradedRoutes), - Message: fmt.Sprintf("Cannot load service from %s %s/%s : %v", listener.Routes.Kind, gateway.Namespace, httpRoute.Name, err), + Message: fmt.Sprintf("Cannot load service from %s %s/%s: %v", listener.Routes.Kind, httpRoute.Namespace, httpRoute.Name, err), }) // TODO update the RouteStatus condition / deduplicate conditions on listener @@ -611,13 +633,23 @@ func gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener v1alpha } func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1.Listener, gateway *v1alpha1.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { - // TODO: support RouteNamespaces selector := labels.Everything() + if listener.Routes.Selector != nil { - selector = labels.SelectorFromSet(listener.Routes.Selector.MatchLabels) + var err error + selector, err = metav1.LabelSelectorAsSelector(listener.Routes.Selector) + if err != nil { + return []metav1.Condition{{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Invalid routes selector: %v", err), + }} + } } - tcpRoutes, err := client.GetTCPRoutes(gateway.Namespace, selector) + namespaces, err := getRouteBindingSelectorNamespace(client, gateway.Namespace, listener.Routes.Namespaces) if err != nil { // update "ResolvedRefs" status true with "InvalidRoutesRef" reason return []metav1.Condition{{ @@ -625,7 +657,19 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1. Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), - Message: fmt.Sprintf("Cannot fetch %ss for namespace %q and matchLabels %v", listener.Routes.Kind, gateway.Namespace, listener.Routes.Selector.MatchLabels), + Message: fmt.Sprintf("Invalid route namespaces selector: %v", err), + }} + } + + tcpRoutes, err := client.GetTCPRoutes(namespaces, selector) + if err != nil { + // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + return []metav1.Condition{{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Cannot fetch %ss: %v", listener.Routes.Kind, err), }} } @@ -681,7 +725,7 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1. continue } - wrrService, subServices, err := loadTCPServices(client, gateway.Namespace, routeRule.ForwardTo) + wrrService, subServices, err := loadTCPServices(client, tcpRoute.Namespace, routeRule.ForwardTo) if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ @@ -689,7 +733,7 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1. Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: string(v1alpha1.ListenerReasonDegradedRoutes), - Message: fmt.Sprintf("Cannot load service from %s %s/%s : %v", listener.Routes.Kind, gateway.Namespace, tcpRoute.Name, err), + Message: fmt.Sprintf("Cannot load service from %s %s/%s: %v", listener.Routes.Kind, tcpRoute.Namespace, tcpRoute.Name, err), }) // TODO update the RouteStatus condition / deduplicate conditions on listener @@ -714,13 +758,23 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1. } func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1.Listener, gateway *v1alpha1.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { - // TODO: support RouteNamespaces selector := labels.Everything() + if listener.Routes.Selector != nil { - selector = labels.SelectorFromSet(listener.Routes.Selector.MatchLabels) + var err error + selector, err = metav1.LabelSelectorAsSelector(listener.Routes.Selector) + if err != nil { + return []metav1.Condition{{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Invalid routes selector: %v", err), + }} + } } - tlsRoutes, err := client.GetTLSRoutes(gateway.Namespace, selector) + namespaces, err := getRouteBindingSelectorNamespace(client, gateway.Namespace, listener.Routes.Namespaces) if err != nil { // update "ResolvedRefs" status true with "InvalidRoutesRef" reason return []metav1.Condition{{ @@ -728,7 +782,19 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1. Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), - Message: fmt.Sprintf("Cannot fetch %ss for namespace %q and matchLabels %v", listener.Routes.Kind, gateway.Namespace, listener.Routes.Selector.MatchLabels), + Message: fmt.Sprintf("Invalid route namespaces selector: %v", err), + }} + } + + tlsRoutes, err := client.GetTLSRoutes(namespaces, selector) + if err != nil { + // update "ResolvedRefs" status true with "InvalidRoutesRef" reason + return []metav1.Condition{{ + Type: string(v1alpha1.ListenerConditionResolvedRefs), + Status: metav1.ConditionFalse, + LastTransitionTime: metav1.Now(), + Reason: string(v1alpha1.ListenerReasonInvalidRoutesRef), + Message: fmt.Sprintf("Cannot fetch %ss: %v", listener.Routes.Kind, err), }} } @@ -786,7 +852,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1. continue } - wrrService, subServices, err := loadTCPServices(client, gateway.Namespace, routeRule.ForwardTo) + wrrService, subServices, err := loadTCPServices(client, tlsRoute.Namespace, routeRule.ForwardTo) if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ @@ -794,7 +860,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1. Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), Reason: string(v1alpha1.ListenerReasonDegradedRoutes), - Message: fmt.Sprintf("Cannot load service from %s %s/%s : %v", listener.Routes.Kind, gateway.Namespace, tlsRoute.Name, err), + Message: fmt.Sprintf("Cannot load service from %s %s/%s: %v", listener.Routes.Kind, tlsRoute.Namespace, tlsRoute.Name, err), }) // TODO update the RouteStatus condition / deduplicate conditions on listener @@ -818,6 +884,30 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener v1alpha1. return conditions } +func getRouteBindingSelectorNamespace(client Client, gatewayNamespace string, routeNamespaces *v1alpha1.RouteNamespaces) ([]string, error) { + if routeNamespaces == nil || routeNamespaces.From == nil { + return []string{gatewayNamespace}, nil + } + + switch *routeNamespaces.From { + case v1alpha1.RouteSelectAll: + return []string{metav1.NamespaceAll}, nil + + case v1alpha1.RouteSelectSame: + return []string{gatewayNamespace}, nil + + case v1alpha1.RouteSelectSelector: + selector, err := metav1.LabelSelectorAsSelector(routeNamespaces.Selector) + if err != nil { + return nil, fmt.Errorf("malformed selector: %w", err) + } + + return client.GetNamespaces(selector) + } + + return nil, fmt.Errorf("unsupported RouteSelectType: %q", *routeNamespaces.From) +} + func (p *Provider) makeGatewayStatus(listenerStatuses []v1alpha1.ListenerStatus) (v1alpha1.GatewayStatus, error) { // As Status.Addresses are not implemented yet, we initialize an empty array to follow the API expectations. gatewayStatus := v1alpha1.GatewayStatus{ diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 33e882ef5..f972df5e2 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -1253,6 +1253,199 @@ func TestLoadHTTPRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "HTTPRoute with Same namespace selector", + paths: []string{"services.yml", "httproute/with_namespace_same.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":80"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-default-my-gateway-web-efde1997778109a1f6eb": { + EntryPoints: []string{"web"}, + Service: "default-http-app-default-my-gateway-web-efde1997778109a1f6eb-wrr", + Rule: "Host(`foo.com`) && Path(`/foo`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-default-my-gateway-web-efde1997778109a1f6eb-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: pointer.Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "HTTPRoute with All namespace selector", + paths: []string{"services.yml", "httproute/with_namespace_all.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":80"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-default-my-gateway-web-efde1997778109a1f6eb": { + EntryPoints: []string{"web"}, + Service: "default-http-app-default-my-gateway-web-efde1997778109a1f6eb-wrr", + Rule: "Host(`foo.com`) && Path(`/foo`)", + }, + "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597": { + EntryPoints: []string{"web"}, + Service: "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597-wrr", + Rule: "Host(`bar.com`) && Path(`/bar`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-default-my-gateway-web-efde1997778109a1f6eb-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "bar-whoami-bar-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: pointer.Bool(true), + }, + }, + "bar-whoami-bar-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.11:80", + }, + { + URL: "http://10.10.0.12:80", + }, + }, + PassHostHeader: pointer.Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "HTTPRoute with Selector Route Binding", + paths: []string{"services.yml", "httproute/with_namespace_selector.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":80"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597": { + EntryPoints: []string{"web"}, + Service: "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597-wrr", + Rule: "Host(`bar.com`) && Path(`/bar`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "bar-whoami-bar-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-whoami-bar-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.11:80", + }, + { + URL: "http://10.10.0.12:80", + }, + }, + PassHostHeader: pointer.Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases { @@ -1710,6 +1903,195 @@ func TestLoadTCPRoutes(t *testing.T) { }, }, }, + { + desc: "TCPRoute with Same namespace selector", + paths: []string{"services.yml", "tcproute/with_namespace_same.yml"}, + entryPoints: map[string]Entrypoint{ + "tcp": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "TCPRoute with All namespace selector", + paths: []string{"services.yml", "tcproute/with_namespace_all.yml"}, + entryPoints: map[string]Entrypoint{ + "tcp": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "bar-whoamitcp-bar-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + "bar-whoamitcp-bar-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.13:9000", + }, + { + Address: "10.10.0.14:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "TCPRoute with Selector Route Binding", + paths: []string{"services.yml", "tcproute/with_namespace_selector.yml"}, + entryPoints: map[string]Entrypoint{ + "tcp": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "bar-whoamitcp-bar-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-whoamitcp-bar-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.13:9000", + }, + { + Address: "10.10.0.14:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases { @@ -2478,6 +2860,207 @@ func TestLoadTLSRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "TLSRoute with Same namespace selector", + paths: []string{"services.yml", "tlsroute/with_namespace_same.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9001"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5": { + EntryPoints: []string{"tls"}, + Service: "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5-wrr", + Rule: "HostSNI(`foo.default`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "TLSRoute with All namespace selector", + paths: []string{"services.yml", "tlsroute/with_namespace_all.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9001"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5": { + EntryPoints: []string{"tls"}, + Service: "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5-wrr", + Rule: "HostSNI(`foo.default`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26": { + EntryPoints: []string{"tls"}, + Service: "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26-wrr", + Rule: "HostSNI(`foo.bar`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "bar-whoamitcp-bar-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + "bar-whoamitcp-bar-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.13:9000", + }, + { + Address: "10.10.0.14:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "TLSRoute with Selector Route Binding", + paths: []string{"services.yml", "tlsroute/with_namespace_selector.yml"}, + entryPoints: map[string]Entrypoint{ + "tls": {Address: ":9001"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26": { + EntryPoints: []string{"tls"}, + Service: "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26-wrr", + Rule: "HostSNI(`foo.bar`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "bar-whoamitcp-bar-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-whoamitcp-bar-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.13:9000", + }, + { + Address: "10.10.0.14:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases { @@ -2775,6 +3358,534 @@ func TestLoadMixedRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Mixed routes with Same namespace selector", + paths: []string{"services.yml", "mixed/with_namespace_same.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":9080"}, + "websecure": {Address: ":9443"}, + "tcp": {Address: ":9000"}, + "tls-1": {Address: ":10000"}, + "tls-2": {Address: ":11000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tls-1"}, + Service: "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + "default-tls-app-default-my-gateway-tls-2-673acf455cb2dab0b43a": { + EntryPoints: []string{"tls-2"}, + Service: "default-tls-app-default-my-gateway-tls-2-673acf455cb2dab0b43a-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-tls-app-default-my-gateway-tls-2-673acf455cb2dab0b43a-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-default-my-gateway-web-a431b128267aabc954fd": { + EntryPoints: []string{"web"}, + Service: "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + }, + "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd": { + EntryPoints: []string{"websecure"}, + Service: "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: pointer.Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + { + desc: "Mixed routes with All namespace selector", + paths: []string{"services.yml", "mixed/with_namespace_all.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":9080"}, + "websecure": {Address: ":9443"}, + "tcp": {Address: ":9000"}, + "tls-1": {Address: ":10000"}, + "tls-2": {Address: ":11000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tls-1"}, + Service: "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + "default-tls-app-default-my-gateway-tls-2-673acf455cb2dab0b43a": { + EntryPoints: []string{"tls-2"}, + Service: "default-tls-app-default-my-gateway-tls-2-673acf455cb2dab0b43a-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tls-1"}, + Service: "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-tls-app-default-my-gateway-tls-2-673acf455cb2dab0b43a-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoamitcp-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.9:9000", + }, + { + Address: "10.10.0.10:9000", + }, + }, + }, + }, + "bar-whoamitcp-bar-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.13:9000", + }, + { + Address: "10.10.0.14:9000", + }, + }, + }, + }, + "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "bar-whoamitcp-bar-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "bar-whoamitcp-bar-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-default-my-gateway-web-a431b128267aabc954fd": { + EntryPoints: []string{"web"}, + Service: "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + }, + "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd": { + EntryPoints: []string{"websecure"}, + Service: "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + TLS: &dynamic.RouterTLSConfig{}, + }, + "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd": { + EntryPoints: []string{"web"}, + Service: "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + }, + "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd": { + EntryPoints: []string{"websecure"}, + Service: "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: pointer.Bool(true), + }, + }, + "bar-whoami-bar-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.11:80", + }, + { + URL: "http://10.10.0.12:80", + }, + }, + PassHostHeader: pointer.Bool(true), + }, + }, + "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "bar-whoami-bar-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "bar-whoami-bar-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, + { + desc: "Mixed routes with Selector Route Binding", + paths: []string{"services.yml", "mixed/with_namespace_selector.yml"}, + entryPoints: map[string]Entrypoint{ + "web": {Address: ":9080"}, + "websecure": {Address: ":9443"}, + "tcp": {Address: ":9000"}, + "tls-1": {Address: ":10000"}, + "tls-2": {Address: ":11000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + }, + "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tls-1"}, + Service: "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + "bar-tls-app-bar-my-gateway-tls-2-673acf455cb2dab0b43a": { + EntryPoints: []string{"tls-2"}, + Service: "bar-tls-app-bar-my-gateway-tls-2-673acf455cb2dab0b43a-wrr", + Rule: "HostSNI(`*`)", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "bar-whoamitcp-bar-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.13:9000", + }, + { + Address: "10.10.0.14:9000", + }, + }, + }, + }, + "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "bar-whoamitcp-bar-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "bar-whoamitcp-bar-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-tls-app-bar-my-gateway-tls-2-673acf455cb2dab0b43a-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "bar-whoamitcp-bar-9000", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd": { + EntryPoints: []string{"web"}, + Service: "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + }, + "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd": { + EntryPoints: []string{"websecure"}, + Service: "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd-wrr", + Rule: "PathPrefix(`/`)", + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "bar-whoami-bar-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.11:80", + }, + { + URL: "http://10.10.0.12:80", + }, + }, + PassHostHeader: pointer.Bool(true), + }, + }, + "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "bar-whoami-bar-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "bar-whoami-bar-80", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + }, + }, } for _, test := range testCases { diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index f7336c5bd..306f862fe 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -12,7 +12,7 @@ import ( // MustParseYaml parses a YAML to objects. func MustParseYaml(content []byte) []runtime.Object { - acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`) + acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`) files := strings.Split(string(content), "---") retVal := make([]runtime.Object, 0, len(files))