Merge branch 'v3.0' of github.com:traefik/traefik
This commit is contained in:
commit
eea0977df4
8 changed files with 858 additions and 100 deletions
|
@ -241,10 +241,20 @@ Kubernetes cluster before creating `HTTPRoute` objects.
|
||||||
- name: api@internal
|
- name: api@internal
|
||||||
group: traefik.io # [18]
|
group: traefik.io # [18]
|
||||||
kind: TraefikService # [19]
|
kind: TraefikService # [19]
|
||||||
|
- filters: # [20]
|
||||||
|
- type: ExtensionRef # [21]
|
||||||
|
extensionRef: # [22]
|
||||||
|
group: traefik.io # [23]
|
||||||
|
kind: Middleware # [24]
|
||||||
|
name: my-middleware # [25]
|
||||||
|
- type: RequestRedirect # [26]
|
||||||
|
requestRedirect: # [27]
|
||||||
|
scheme: https # [28]
|
||||||
|
statusCode: 301 # [29]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Ref | Attribute | Description |
|
| Ref | Attribute | Description |
|
||||||
|------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|------|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [1] | `parentRefs` | References the resources (usually Gateways) that a Route wants to be attached to. |
|
| [1] | `parentRefs` | References the resources (usually Gateways) that a Route wants to be attached to. |
|
||||||
| [2] | `name` | Name of the referent. |
|
| [2] | `name` | Name of the referent. |
|
||||||
| [3] | `namespace` | Namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. |
|
| [3] | `namespace` | Namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. |
|
||||||
|
@ -256,14 +266,24 @@ Kubernetes cluster before creating `HTTPRoute` objects.
|
||||||
| [9] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). |
|
| [9] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). |
|
||||||
| [10] | `value` | The value of the HTTP path to match against. |
|
| [10] | `value` | The value of the HTTP path to match against. |
|
||||||
| [11] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. |
|
| [11] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. |
|
||||||
| [12] | `type` | Type of match for the HTTP request header match against the `values` (supported types: `Exact`). |
|
| [12] | `name` | Name of the HTTP header to be matched. |
|
||||||
| [13] | `value` | A map of HTTP Headers to be matched. It MUST contain at least one entry. |
|
| [13] | `value` | Value of HTTP Header to be matched. |
|
||||||
| [14] | `backendRefs` | Defines the backend(s) where matching requests should be sent. |
|
| [14] | `backendRefs` | Defines the backend(s) where matching requests should be sent. |
|
||||||
| [15] | `name` | The name of the referent service. |
|
| [15] | `name` | The name of the referent service. |
|
||||||
| [16] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). |
|
| [16] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). |
|
||||||
| [17] | `port` | The port of the referent service. |
|
| [17] | `port` | The port of the referent service. |
|
||||||
| [18] | `group` | Group is the group of the referent. Only `traefik.io` and `gateway.networking.k8s.io` values are supported. |
|
| [18] | `group` | Group is the group of the referent. Only `traefik.io` and `gateway.networking.k8s.io` values are supported. |
|
||||||
| [19] | `kind` | Kind is kind of the referent. Only `TraefikService` and `Service` values are supported. |
|
| [19] | `kind` | Kind is kind of the referent. Only `TraefikService` and `Service` values are supported. |
|
||||||
|
| [20] | `filters` | Defines the filters (middlewares) applied to the route. |
|
||||||
|
| [21] | `type` | Defines the type of filter; ExtensionRef is used for configuring custom HTTP filters. |
|
||||||
|
| [22] | `extensionRef` | Configuration of the custom HTTP filter. |
|
||||||
|
| [23] | `group` | Group of the kubernetes object to reference. |
|
||||||
|
| [24] | `kind` | Kind of the kubernetes object to reference. |
|
||||||
|
| [25] | `name` | Name of the kubernetes object to reference. |
|
||||||
|
| [26] | `type` | Defines the type of filter; RequestRedirect redirects a request to another location. |
|
||||||
|
| [27] | `requestRedirect` | Configuration of redirect filter. |
|
||||||
|
| [28] | `scheme` | Scheme is the scheme to be used in the value of the Location header in the response. |
|
||||||
|
| [29] | `statusCode` | StatusCode is the HTTP status code to be used in response. |
|
||||||
|
|
||||||
### Kind: `TCPRoute`
|
### Kind: `TCPRoute`
|
||||||
|
|
||||||
|
|
|
@ -288,6 +288,10 @@ func (c *Configuration) SetEffectiveConfiguration() {
|
||||||
entryPoints[epName] = gateway.Entrypoint{Address: entryPoint.GetAddress(), HasHTTPTLSConf: entryPoint.HTTP.TLS != nil}
|
entryPoints[epName] = gateway.Entrypoint{Address: entryPoint.GetAddress(), HasHTTPTLSConf: entryPoint.HTTP.TLS != nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Providers.KubernetesCRD != nil {
|
||||||
|
c.Providers.KubernetesCRD.FillExtensionBuilderRegistry(c.Providers.KubernetesGateway)
|
||||||
|
}
|
||||||
|
|
||||||
c.Providers.KubernetesGateway.EntryPoints = entryPoints
|
c.Providers.KubernetesGateway.EntryPoints = entryPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -26,6 +27,7 @@ import (
|
||||||
"github.com/traefik/traefik/v3/pkg/logs"
|
"github.com/traefik/traefik/v3/pkg/logs"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider"
|
"github.com/traefik/traefik/v3/pkg/provider"
|
||||||
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||||
"github.com/traefik/traefik/v3/pkg/safe"
|
"github.com/traefik/traefik/v3/pkg/safe"
|
||||||
"github.com/traefik/traefik/v3/pkg/tls"
|
"github.com/traefik/traefik/v3/pkg/tls"
|
||||||
|
@ -715,6 +717,24 @@ func (p *Provider) createErrorPageMiddleware(client Client, namespace string, er
|
||||||
return errorPageMiddleware, balancerServerHTTP, nil
|
return errorPageMiddleware, balancerServerHTTP, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) FillExtensionBuilderRegistry(registry gateway.ExtensionBuilderRegistry) {
|
||||||
|
registry.RegisterFilterFuncs(traefikv1alpha1.GroupName, "Middleware", func(name, namespace string) (string, *dynamic.Middleware, error) {
|
||||||
|
if len(p.Namespaces) > 0 && !slices.Contains(p.Namespaces, namespace) {
|
||||||
|
return "", nil, fmt.Errorf("namespace %q is not allowed", namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeID(namespace, name) + providerNamespaceSeparator + providerName, nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
registry.RegisterBackendFuncs(traefikv1alpha1.GroupName, "TraefikService", func(name, namespace string) (string, *dynamic.Service, error) {
|
||||||
|
if len(p.Namespaces) > 0 && !slices.Contains(p.Namespaces, namespace) {
|
||||||
|
return "", nil, fmt.Errorf("namespace %q is not allowed", namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeID(namespace, name) + providerNamespaceSeparator + providerName, nil, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traefikv1alpha1.ForwardAuth) (*dynamic.ForwardAuth, error) {
|
func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traefikv1alpha1.ForwardAuth) (*dynamic.ForwardAuth, error) {
|
||||||
if auth == nil {
|
if auth == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/traefik/traefik/v3/pkg/provider"
|
"github.com/traefik/traefik/v3/pkg/provider"
|
||||||
traefikcrdfake "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake"
|
traefikcrdfake "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake"
|
||||||
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||||
"github.com/traefik/traefik/v3/pkg/tls"
|
"github.com/traefik/traefik/v3/pkg/tls"
|
||||||
"github.com/traefik/traefik/v3/pkg/types"
|
"github.com/traefik/traefik/v3/pkg/types"
|
||||||
|
@ -7265,6 +7266,64 @@ func TestCreateBasicAuthCredentials(t *testing.T) {
|
||||||
assert.True(t, auth.CheckSecret("test2", hashedPassword))
|
assert.True(t, auth.CheckSecret("test2", hashedPassword))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFillExtensionBuilderRegistry(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
namespaces []string
|
||||||
|
wantErr require.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "no filter on namespaces",
|
||||||
|
wantErr: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "filter on default namespace",
|
||||||
|
namespaces: []string{"default"},
|
||||||
|
wantErr: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "filter on not-default namespace",
|
||||||
|
namespaces: []string{"not-default"},
|
||||||
|
wantErr: require.Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
r := &extensionBuilderRegistryMock{}
|
||||||
|
|
||||||
|
p := Provider{Namespaces: test.namespaces}
|
||||||
|
p.FillExtensionBuilderRegistry(r)
|
||||||
|
|
||||||
|
filterFunc, ok := r.groupKindFilterFuncs[traefikv1alpha1.SchemeGroupVersion.Group]["Middleware"]
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
name, conf, err := filterFunc("my-middleware", "default")
|
||||||
|
test.wantErr(t, err)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
assert.Nil(t, conf)
|
||||||
|
assert.Equal(t, "default-my-middleware@kubernetescrd", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
backendFunc, ok := r.groupKindBackendFuncs[traefikv1alpha1.SchemeGroupVersion.Group]["TraefikService"]
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
name, svc, err := backendFunc("my-service", "default")
|
||||||
|
test.wantErr(t, err)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
assert.Nil(t, svc)
|
||||||
|
assert.Equal(t, "default-my-service@kubernetescrd", name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func readResources(t *testing.T, paths []string) ([]runtime.Object, []runtime.Object) {
|
func readResources(t *testing.T, paths []string) ([]runtime.Object, []runtime.Object) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
@ -7289,3 +7348,34 @@ func readResources(t *testing.T, paths []string) ([]runtime.Object, []runtime.Ob
|
||||||
|
|
||||||
return k8sObjects, crdObjects
|
return k8sObjects, crdObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type extensionBuilderRegistryMock struct {
|
||||||
|
groupKindFilterFuncs map[string]map[string]gateway.BuildFilterFunc
|
||||||
|
groupKindBackendFuncs map[string]map[string]gateway.BuildBackendFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFilterFuncs registers an allowed Group, Kind, and builder for the Filter ExtensionRef objects.
|
||||||
|
func (p *extensionBuilderRegistryMock) RegisterFilterFuncs(group, kind string, builderFunc gateway.BuildFilterFunc) {
|
||||||
|
if p.groupKindFilterFuncs == nil {
|
||||||
|
p.groupKindFilterFuncs = map[string]map[string]gateway.BuildFilterFunc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.groupKindFilterFuncs[group] == nil {
|
||||||
|
p.groupKindFilterFuncs[group] = map[string]gateway.BuildFilterFunc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.groupKindFilterFuncs[group][kind] = builderFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterBackendFuncs registers an allowed Group, Kind, and builder for the Backend ExtensionRef objects.
|
||||||
|
func (p *extensionBuilderRegistryMock) RegisterBackendFuncs(group, kind string, builderFunc gateway.BuildBackendFunc) {
|
||||||
|
if p.groupKindBackendFuncs == nil {
|
||||||
|
p.groupKindBackendFuncs = map[string]map[string]gateway.BuildBackendFunc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.groupKindBackendFuncs[group] == nil {
|
||||||
|
p.groupKindBackendFuncs[group] = map[string]gateway.BuildBackendFunc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.groupKindBackendFuncs[group][kind] = builderFunc
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
kind: GatewayClass
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: my-gateway-class
|
||||||
|
spec:
|
||||||
|
controllerName: traefik.io/gateway-controller
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Gateway
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: my-gateway
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
gatewayClassName: my-gateway-class
|
||||||
|
listeners: # Use GatewayClass defaults for listener definition.
|
||||||
|
- name: http
|
||||||
|
protocol: HTTP
|
||||||
|
port: 80
|
||||||
|
allowedRoutes:
|
||||||
|
kinds:
|
||||||
|
- kind: HTTPRoute
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
namespaces:
|
||||||
|
from: Same
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: HTTPRoute
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: http-app-1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
kind: Gateway
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
hostnames:
|
||||||
|
- "foo.com"
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: Exact
|
||||||
|
value: /bar
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: Service
|
||||||
|
group: ""
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: my-middleware
|
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
kind: GatewayClass
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: my-gateway-class
|
||||||
|
spec:
|
||||||
|
controllerName: traefik.io/gateway-controller
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Gateway
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: my-gateway
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
gatewayClassName: my-gateway-class
|
||||||
|
listeners: # Use GatewayClass defaults for listener definition.
|
||||||
|
- name: http
|
||||||
|
protocol: HTTP
|
||||||
|
port: 80
|
||||||
|
allowedRoutes:
|
||||||
|
kinds:
|
||||||
|
- kind: HTTPRoute
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
namespaces:
|
||||||
|
from: Same
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: HTTPRoute
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: http-app-1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
kind: Gateway
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
hostnames:
|
||||||
|
- "foo.com"
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: Exact
|
||||||
|
value: /bar
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: TraefikService
|
||||||
|
group: "traefik.io"
|
|
@ -59,11 +59,53 @@ type Provider struct {
|
||||||
ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||||
EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||||
|
|
||||||
|
// groupKindFilterFuncs is the list of allowed Group and Kinds for the Filter ExtensionRef objects.
|
||||||
|
groupKindFilterFuncs map[string]map[string]BuildFilterFunc
|
||||||
|
// groupKindBackendFuncs is the list of allowed Group and Kinds for the Backend ExtensionRef objects.
|
||||||
|
groupKindBackendFuncs map[string]map[string]BuildBackendFunc
|
||||||
|
|
||||||
lastConfiguration safe.Safe
|
lastConfiguration safe.Safe
|
||||||
|
|
||||||
routerTransform k8s.RouterTransform
|
routerTransform k8s.RouterTransform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildFilterFunc returns the name of the filter and the related dynamic.Middleware if needed.
|
||||||
|
type BuildFilterFunc func(name, namespace string) (string, *dynamic.Middleware, error)
|
||||||
|
|
||||||
|
// BuildBackendFunc returns the name of the backend and the related dynamic.Service if needed.
|
||||||
|
type BuildBackendFunc func(name, namespace string) (string, *dynamic.Service, error)
|
||||||
|
|
||||||
|
type ExtensionBuilderRegistry interface {
|
||||||
|
RegisterFilterFuncs(group, kind string, builderFunc BuildFilterFunc)
|
||||||
|
RegisterBackendFuncs(group, kind string, builderFunc BuildBackendFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFilterFuncs registers an allowed Group, Kind, and builder for the Filter ExtensionRef objects.
|
||||||
|
func (p *Provider) RegisterFilterFuncs(group, kind string, builderFunc BuildFilterFunc) {
|
||||||
|
if p.groupKindFilterFuncs == nil {
|
||||||
|
p.groupKindFilterFuncs = map[string]map[string]BuildFilterFunc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.groupKindFilterFuncs[group] == nil {
|
||||||
|
p.groupKindFilterFuncs[group] = map[string]BuildFilterFunc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.groupKindFilterFuncs[group][kind] = builderFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterBackendFuncs registers an allowed Group, Kind, and builder for the Backend ExtensionRef objects.
|
||||||
|
func (p *Provider) RegisterBackendFuncs(group, kind string, builderFunc BuildBackendFunc) {
|
||||||
|
if p.groupKindBackendFuncs == nil {
|
||||||
|
p.groupKindBackendFuncs = map[string]map[string]BuildBackendFunc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.groupKindBackendFuncs[group] == nil {
|
||||||
|
p.groupKindBackendFuncs[group] = map[string]BuildBackendFunc{}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.groupKindBackendFuncs[group][kind] = builderFunc
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) SetRouterTransform(routerTransform k8s.RouterTransform) {
|
func (p *Provider) SetRouterTransform(routerTransform k8s.RouterTransform) {
|
||||||
p.routerTransform = routerTransform
|
p.routerTransform = routerTransform
|
||||||
}
|
}
|
||||||
|
@ -847,7 +889,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
middlewares, err := loadMiddlewares(listener, routerKey, routeRule.Filters)
|
middlewares, err := p.loadMiddlewares(listener, route.Namespace, routerKey, routeRule.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// update "ResolvedRefs" status true with "InvalidFilters" reason
|
// update "ResolvedRefs" status true with "InvalidFilters" reason
|
||||||
conditions = append(conditions, metav1.Condition{
|
conditions = append(conditions, metav1.Condition{
|
||||||
|
@ -864,7 +906,11 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li
|
||||||
}
|
}
|
||||||
|
|
||||||
for middlewareName, middleware := range middlewares {
|
for middlewareName, middleware := range middlewares {
|
||||||
|
// If the middleware is not defined in the return of the loadMiddlewares function, it means we just need a reference to that middleware.
|
||||||
|
if middleware != nil {
|
||||||
conf.HTTP.Middlewares[middlewareName] = middleware
|
conf.HTTP.Middlewares[middlewareName] = middleware
|
||||||
|
}
|
||||||
|
|
||||||
router.Middlewares = append(router.Middlewares, middlewareName)
|
router.Middlewares = append(router.Middlewares, middlewareName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -876,7 +922,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li
|
||||||
if len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0].BackendRef) {
|
if len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0].BackendRef) {
|
||||||
router.Service = string(routeRule.BackendRefs[0].Name)
|
router.Service = string(routeRule.BackendRefs[0].Name)
|
||||||
} else {
|
} else {
|
||||||
wrrService, subServices, err := loadServices(client, route.Namespace, routeRule.BackendRefs)
|
wrrService, subServices, err := p.loadServices(client, route.Namespace, routeRule.BackendRefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// update "ResolvedRefs" status true with "DroppedRoutes" reason
|
// update "ResolvedRefs" status true with "DroppedRoutes" reason
|
||||||
conditions = append(conditions, metav1.Condition{
|
conditions = append(conditions, metav1.Condition{
|
||||||
|
@ -893,8 +939,10 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li
|
||||||
}
|
}
|
||||||
|
|
||||||
for svcName, svc := range subServices {
|
for svcName, svc := range subServices {
|
||||||
|
if svc != nil {
|
||||||
conf.HTTP.Services[svcName] = svc
|
conf.HTTP.Services[svcName] = svc
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serviceName := provider.Normalize(routerKey + "-wrr")
|
serviceName := provider.Normalize(routerKey + "-wrr")
|
||||||
conf.HTTP.Services[serviceName] = wrrService
|
conf.HTTP.Services[serviceName] = wrrService
|
||||||
|
@ -1550,7 +1598,7 @@ func getCertificateBlocks(secret *corev1.Secret, namespace, secretName string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadServices is generating a WRR service, even when there is only one target.
|
// loadServices is generating a WRR service, even when there is only one target.
|
||||||
func loadServices(client Client, namespace string, backendRefs []gatev1.HTTPBackendRef) (*dynamic.Service, map[string]*dynamic.Service, error) {
|
func (p *Provider) loadServices(client Client, namespace string, backendRefs []gatev1.HTTPBackendRef) (*dynamic.Service, map[string]*dynamic.Service, error) {
|
||||||
services := map[string]*dynamic.Service{}
|
services := map[string]*dynamic.Service{}
|
||||||
|
|
||||||
wrrSvc := &dynamic.Service{
|
wrrSvc := &dynamic.Service{
|
||||||
|
@ -1571,13 +1619,20 @@ func loadServices(client Client, namespace string, backendRefs []gatev1.HTTPBack
|
||||||
|
|
||||||
weight := int(ptr.Deref(backendRef.Weight, 1))
|
weight := int(ptr.Deref(backendRef.Weight, 1))
|
||||||
|
|
||||||
if isTraefikService(backendRef.BackendRef) {
|
if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" {
|
||||||
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.WRRService{Name: string(backendRef.Name), Weight: &weight})
|
if backendRef.Namespace != nil && string(*backendRef.Namespace) != namespace {
|
||||||
continue
|
// TODO: support backend reference grant.
|
||||||
|
return nil, nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" {
|
name, service, err := p.loadHTTPBackendRef(namespace, backendRef)
|
||||||
return nil, nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to load HTTPBackendRef %s/%s/%s: %w", *backendRef.Group, *backendRef.Kind, backendRef.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
services[name] = service
|
||||||
|
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.WRRService{Name: name, Weight: &weight})
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
lb := &dynamic.ServersLoadBalancer{}
|
lb := &dynamic.ServersLoadBalancer{}
|
||||||
|
@ -1672,6 +1727,24 @@ func loadServices(client Client, namespace string, backendRefs []gatev1.HTTPBack
|
||||||
return wrrSvc, services, nil
|
return wrrSvc, services, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadHTTPBackendRef(namespace string, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, error) {
|
||||||
|
// Support for cross-provider references (e.g: api@internal).
|
||||||
|
// This provides the same behavior as for IngressRoutes.
|
||||||
|
if *backendRef.Kind == "TraefikService" && strings.Contains(string(backendRef.Name), "@") {
|
||||||
|
return string(backendRef.Name), nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
backendFunc, ok := p.groupKindBackendFuncs[string(*backendRef.Group)][string(*backendRef.Kind)]
|
||||||
|
if !ok {
|
||||||
|
return "", nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||||
|
}
|
||||||
|
if backendFunc == nil {
|
||||||
|
return "", nil, fmt.Errorf("undefined backendFunc for HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return backendFunc(string(backendRef.Name), namespace)
|
||||||
|
}
|
||||||
|
|
||||||
// loadTCPServices is generating a WRR service, even when there is only one target.
|
// loadTCPServices is generating a WRR service, even when there is only one target.
|
||||||
func loadTCPServices(client Client, namespace string, backendRefs []gatev1.BackendRef) (*dynamic.TCPService, map[string]*dynamic.TCPService, error) {
|
func loadTCPServices(client Client, namespace string, backendRefs []gatev1.BackendRef) (*dynamic.TCPService, map[string]*dynamic.TCPService, error) {
|
||||||
services := map[string]*dynamic.TCPService{}
|
services := map[string]*dynamic.TCPService{}
|
||||||
|
@ -1791,7 +1864,7 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1.Backe
|
||||||
return wrrSvc, services, nil
|
return wrrSvc, services, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadMiddlewares(listener gatev1.Listener, prefix string, filters []gatev1.HTTPRouteFilter) (map[string]*dynamic.Middleware, error) {
|
func (p *Provider) loadMiddlewares(listener gatev1.Listener, namespace string, prefix string, filters []gatev1.HTTPRouteFilter) (map[string]*dynamic.Middleware, error) {
|
||||||
middlewares := make(map[string]*dynamic.Middleware)
|
middlewares := make(map[string]*dynamic.Middleware)
|
||||||
|
|
||||||
// The spec allows for an empty string in which case we should use the
|
// The spec allows for an empty string in which case we should use the
|
||||||
|
@ -1815,6 +1888,16 @@ func loadMiddlewares(listener gatev1.Listener, prefix string, filters []gatev1.H
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating RedirectRegex middleware: %w", err)
|
return nil, fmt.Errorf("creating RedirectRegex middleware: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
||||||
|
middlewares[middlewareName] = middleware
|
||||||
|
case gatev1.HTTPRouteFilterExtensionRef:
|
||||||
|
name, middleware, err := p.loadHTTPRouteFilterExtensionRef(namespace, filter.ExtensionRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unsupported filter %s: %w", filter.Type, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
middlewares[name] = middleware
|
||||||
default:
|
default:
|
||||||
// As per the spec:
|
// As per the spec:
|
||||||
// https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
|
// https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
|
||||||
|
@ -1823,14 +1906,27 @@ func loadMiddlewares(listener gatev1.Listener, prefix string, filters []gatev1.H
|
||||||
// status.
|
// status.
|
||||||
return nil, fmt.Errorf("unsupported filter %s", filter.Type)
|
return nil, fmt.Errorf("unsupported filter %s", filter.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
|
||||||
middlewares[middlewareName] = middleware
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return middlewares, nil
|
return middlewares, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRef *gatev1.LocalObjectReference) (string, *dynamic.Middleware, error) {
|
||||||
|
if extensionRef == nil {
|
||||||
|
return "", nil, errors.New("filter extension ref undefined")
|
||||||
|
}
|
||||||
|
|
||||||
|
filterFunc, ok := p.groupKindFilterFuncs[string(extensionRef.Group)][string(extensionRef.Kind)]
|
||||||
|
if !ok {
|
||||||
|
return "", nil, fmt.Errorf("unsupported filter extension ref %s/%s/%s", extensionRef.Group, extensionRef.Kind, extensionRef.Name)
|
||||||
|
}
|
||||||
|
if filterFunc == nil {
|
||||||
|
return "", nil, fmt.Errorf("undefined filterFunc for filter extension ref %s/%s/%s", extensionRef.Group, extensionRef.Kind, extensionRef.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterFunc(string(extensionRef.Name), namespace)
|
||||||
|
}
|
||||||
|
|
||||||
func createRedirectRegexMiddleware(scheme string, filter *gatev1.HTTPRequestRedirectFilter) (*dynamic.Middleware, error) {
|
func createRedirectRegexMiddleware(scheme string, filter *gatev1.HTTPRequestRedirectFilter) (*dynamic.Middleware, error) {
|
||||||
// Use the HTTPRequestRedirectFilter scheme if defined.
|
// Use the HTTPRequestRedirectFilter scheme if defined.
|
||||||
filterScheme := scheme
|
filterScheme := scheme
|
||||||
|
|
|
@ -2,6 +2,7 @@ package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
ptypes "github.com/traefik/paerser/types"
|
ptypes "github.com/traefik/paerser/types"
|
||||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider"
|
"github.com/traefik/traefik/v3/pkg/provider"
|
||||||
|
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
||||||
"github.com/traefik/traefik/v3/pkg/tls"
|
"github.com/traefik/traefik/v3/pkg/tls"
|
||||||
"github.com/traefik/traefik/v3/pkg/types"
|
"github.com/traefik/traefik/v3/pkg/types"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
@ -25,6 +27,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
desc string
|
desc string
|
||||||
ingressClass string
|
ingressClass string
|
||||||
paths []string
|
paths []string
|
||||||
|
namespaces []string
|
||||||
expected *dynamic.Configuration
|
expected *dynamic.Configuration
|
||||||
entryPoints map[string]Entrypoint
|
entryPoints map[string]Entrypoint
|
||||||
}{
|
}{
|
||||||
|
@ -621,70 +624,6 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
TLS: &dynamic.TLSConfiguration{},
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "Simple HTTPRoute, with myservice@file service",
|
|
||||||
paths: []string{"services.yml", "httproute/simple_cross_provider.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{},
|
|
||||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
|
||||||
},
|
|
||||||
HTTP: &dynamic.HTTPConfiguration{
|
|
||||||
Routers: map[string]*dynamic.Router{
|
|
||||||
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
|
|
||||||
EntryPoints: []string{"web"},
|
|
||||||
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
|
||||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
|
||||||
RuleSyntax: "v3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Middlewares: map[string]*dynamic.Middleware{},
|
|
||||||
Services: map[string]*dynamic.Service{
|
|
||||||
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr": {
|
|
||||||
Weighted: &dynamic.WeightedRoundRobin{
|
|
||||||
Services: []dynamic.WRRService{
|
|
||||||
{
|
|
||||||
Name: "service@file",
|
|
||||||
Weight: func(i int) *int { return &i }(1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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: ptr.To(true),
|
|
||||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
|
||||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
|
||||||
},
|
|
||||||
TLS: &dynamic.TLSConfiguration{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
desc: "Simple HTTPRoute with protocol HTTPS",
|
desc: "Simple HTTPRoute with protocol HTTPS",
|
||||||
paths: []string{"services.yml", "httproute/with_protocol_https.yml"},
|
paths: []string{"services.yml", "httproute/with_protocol_https.yml"},
|
||||||
|
@ -1726,12 +1665,493 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p := Provider{EntryPoints: test.entryPoints}
|
p := Provider{EntryPoints: test.entryPoints}
|
||||||
|
|
||||||
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
||||||
assert.Equal(t, test.expected, conf)
|
assert.Equal(t, test.expected, conf)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadHTTPRoutes_backendExtensionRef(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
paths []string
|
||||||
|
groupKindBackendFuncs map[string]map[string]BuildBackendFunc
|
||||||
|
expected *dynamic.Configuration
|
||||||
|
entryPoints map[string]Entrypoint
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Simple HTTPRoute with TraefikService",
|
||||||
|
paths: []string{"services.yml", "httproute/simple_with_TraefikService.yml"},
|
||||||
|
groupKindBackendFuncs: map[string]map[string]BuildBackendFunc{
|
||||||
|
traefikv1alpha1.GroupName: {"TraefikService": func(name, namespace string) (string, *dynamic.Service, error) {
|
||||||
|
return name, nil, nil
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
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{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||||
|
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr": {
|
||||||
|
Weighted: &dynamic.WeightedRoundRobin{
|
||||||
|
Services: []dynamic.WRRService{
|
||||||
|
{
|
||||||
|
Name: "whoami",
|
||||||
|
Weight: func(i int) *int { return &i }(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Simple HTTPRoute with TraefikService with service configuration",
|
||||||
|
paths: []string{"services.yml", "httproute/simple_with_TraefikService.yml"},
|
||||||
|
groupKindBackendFuncs: map[string]map[string]BuildBackendFunc{
|
||||||
|
traefikv1alpha1.GroupName: {"TraefikService": func(name, namespace string) (string, *dynamic.Service, error) {
|
||||||
|
return name, &dynamic.Service{LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "foobar"}}}}, nil
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
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{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||||
|
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr": {
|
||||||
|
Weighted: &dynamic.WeightedRoundRobin{
|
||||||
|
Services: []dynamic.WRRService{
|
||||||
|
{
|
||||||
|
Name: "whoami",
|
||||||
|
Weight: func(i int) *int { return &i }(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"whoami": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{URL: "foobar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Simple HTTPRoute with invalid TraefikService kind",
|
||||||
|
paths: []string{"services.yml", "httproute/simple_with_TraefikService.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{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
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: "Simple HTTPRoute with backendFunc error",
|
||||||
|
paths: []string{"services.yml", "httproute/simple_with_TraefikService.yml"},
|
||||||
|
groupKindBackendFuncs: map[string]map[string]BuildBackendFunc{
|
||||||
|
traefikv1alpha1.GroupName: {"TraefikService": func(name, namespace string) (string, *dynamic.Service, error) {
|
||||||
|
return "", nil, errors.New("BOOM")
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
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{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
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: "Simple HTTPRoute, with myservice@file service",
|
||||||
|
paths: []string{"services.yml", "httproute/simple_cross_provider.yml"},
|
||||||
|
groupKindBackendFuncs: map[string]map[string]BuildBackendFunc{
|
||||||
|
traefikv1alpha1.GroupName: {"TraefikService": func(name, namespace string) (string, *dynamic.Service, error) {
|
||||||
|
// func should never be executed in case of cross-provider reference.
|
||||||
|
return "", nil, errors.New("BOOM")
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
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{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||||
|
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr": {
|
||||||
|
Weighted: &dynamic.WeightedRoundRobin{
|
||||||
|
Services: []dynamic.WRRService{
|
||||||
|
{
|
||||||
|
Name: "service@file",
|
||||||
|
Weight: func(i int) *int { return &i }(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if test.expected == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Provider{EntryPoints: test.entryPoints}
|
||||||
|
for group, kindFuncs := range test.groupKindBackendFuncs {
|
||||||
|
for kind, backendFunc := range kindFuncs {
|
||||||
|
p.RegisterBackendFuncs(group, kind, backendFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
||||||
|
assert.Equal(t, test.expected, conf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadHTTPRoutes_filterExtensionRef(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
groupKindFilterFuncs map[string]map[string]BuildFilterFunc
|
||||||
|
expected *dynamic.Configuration
|
||||||
|
entryPoints map[string]Entrypoint
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "HTTPRoute with ExtensionRef filter",
|
||||||
|
groupKindFilterFuncs: map[string]map[string]BuildFilterFunc{
|
||||||
|
traefikv1alpha1.GroupName: {"Middleware": func(name, namespace string) (string, *dynamic.Middleware, error) {
|
||||||
|
return namespace + "-" + name, nil, nil
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
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{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||||
|
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Middlewares: []string{"default-my-middleware"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-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: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTPRoute with ExtensionRef filter and create middleware",
|
||||||
|
groupKindFilterFuncs: map[string]map[string]BuildFilterFunc{
|
||||||
|
traefikv1alpha1.GroupName: {"Middleware": func(name, namespace string) (string, *dynamic.Middleware, error) {
|
||||||
|
return namespace + "-" + name, &dynamic.Middleware{Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"Test-Header": "Test"}}}, nil
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
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{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||||
|
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Middlewares: []string{"default-my-middleware"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
|
"default-my-middleware": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"Test-Header": "Test"}}},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-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: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ExtensionRef filter: Unknown",
|
||||||
|
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{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
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: "ExtensionRef filter with filterFunc error",
|
||||||
|
groupKindFilterFuncs: map[string]map[string]BuildFilterFunc{
|
||||||
|
traefikv1alpha1.GroupName: {"Middleware": func(name, namespace string) (string, *dynamic.Middleware, error) {
|
||||||
|
return "", nil, errors.New("BOOM")
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
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{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
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 {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if test.expected == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Provider{EntryPoints: test.entryPoints}
|
||||||
|
for group, kindFuncs := range test.groupKindFilterFuncs {
|
||||||
|
for kind, filterFunc := range kindFuncs {
|
||||||
|
p.RegisterFilterFuncs(group, kind, filterFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock([]string{"services.yml", "httproute/filter_extension_ref.yml"}...))
|
||||||
|
assert.Equal(t, test.expected, conf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadTCPRoutes(t *testing.T) {
|
func TestLoadTCPRoutes(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
Loading…
Reference in a new issue