Multi-layer routing
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
parent
8392503df7
commit
d6598f370c
37 changed files with 2834 additions and 37 deletions
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: parent-cross
|
||||
namespace: ns-a
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`cross.example.com`)
|
||||
kind: Rule
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: child-cross-allowed
|
||||
namespace: ns-b
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: parent-cross
|
||||
namespace: ns-a
|
||||
routes:
|
||||
- match: Path(`/cross`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: cross-service
|
||||
port: 9000
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: parent-cross
|
||||
namespace: ns-a
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`cross.example.com`)
|
||||
kind: Rule
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: child-cross-denied
|
||||
namespace: ns-b
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: parent-cross
|
||||
namespace: ns-a
|
||||
routes:
|
||||
- match: Path(`/denied`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: cross-service
|
||||
port: 9000
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: parent-default
|
||||
namespace: default
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`default.example.com`)
|
||||
kind: Rule
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: child-same
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: parent-default
|
||||
routes:
|
||||
- match: Path(`/same`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: same-service
|
||||
port: 9000
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: child-missing-parent
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: non-existent-parent
|
||||
namespace: default
|
||||
routes:
|
||||
- match: Path(`/missing`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: child-service
|
||||
port: 9000
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: parent-a
|
||||
namespace: default
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`a.example.com`)
|
||||
kind: Rule
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: parent-b
|
||||
namespace: default
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`b.example.com`)
|
||||
kind: Rule
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: child-multi-parents
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: parent-a
|
||||
namespace: default
|
||||
- name: parent-b
|
||||
namespace: default
|
||||
routes:
|
||||
- match: Path(`/shared`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: shared-service
|
||||
port: 9000
|
||||
139
pkg/provider/kubernetes/crd/fixtures/parent_refs_services.yml
Normal file
139
pkg/provider/kubernetes/crd/fixtures/parent_refs_services.yml
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: child-service
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: child-service-abc
|
||||
namespace: default
|
||||
labels:
|
||||
kubernetes.io/service-name: child-service
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.2.1
|
||||
- 10.10.2.2
|
||||
conditions:
|
||||
ready: true
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: users-service
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: users-service-abc
|
||||
namespace: default
|
||||
labels:
|
||||
kubernetes.io/service-name: users-service
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.5.1
|
||||
- 10.10.5.2
|
||||
conditions:
|
||||
ready: true
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: shared-service
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: shared-service-abc
|
||||
namespace: default
|
||||
labels:
|
||||
kubernetes.io/service-name: shared-service
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.8.1
|
||||
- 10.10.8.2
|
||||
conditions:
|
||||
ready: true
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: cross-service
|
||||
namespace: ns-b
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: cross-service-abc
|
||||
namespace: ns-b
|
||||
labels:
|
||||
kubernetes.io/service-name: cross-service
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.11.1
|
||||
- 10.10.11.2
|
||||
conditions:
|
||||
ready: true
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: same-service
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: same-service-abc
|
||||
namespace: default
|
||||
labels:
|
||||
kubernetes.io/service-name: same-service
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- name: web
|
||||
port: 9000
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.14.1
|
||||
- 10.10.14.2
|
||||
conditions:
|
||||
ready: true
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: parent-multi
|
||||
namespace: default
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`api.example.com`) && PathPrefix(`/v1`)
|
||||
kind: Rule
|
||||
- match: Host(`api.example.com`) && PathPrefix(`/v2`)
|
||||
kind: Rule
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: child-multi-routes
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: parent-multi
|
||||
namespace: default
|
||||
routes:
|
||||
- match: Path(`/users`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: users-service
|
||||
port: 9000
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: parent-single
|
||||
namespace: default
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`parent.example.com`)
|
||||
kind: Rule
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: child-single
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: parent-single
|
||||
namespace: default
|
||||
routes:
|
||||
- match: Path(`/api`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: child-service
|
||||
port: 9000
|
||||
|
|
@ -1379,15 +1379,18 @@ func buildCertificates(client Client, tlsStore, namespace string, certificates [
|
|||
return nil
|
||||
}
|
||||
|
||||
func makeServiceKey(rule, ingressName string) (string, error) {
|
||||
func makeServiceKey(rule, ingressName string) string {
|
||||
h := sha256.New()
|
||||
|
||||
// As explained in https://pkg.go.dev/hash#Hash,
|
||||
// Write never returns an error.
|
||||
if _, err := h.Write([]byte(rule)); err != nil {
|
||||
return "", err
|
||||
return ""
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s-%.10x", ingressName, h.Sum(nil))
|
||||
|
||||
return key, nil
|
||||
return key
|
||||
}
|
||||
|
||||
func makeID(namespace, name string) string {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,12 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
disableClusterScopeResources: p.DisableClusterScopeResources,
|
||||
}
|
||||
|
||||
parentRouterNames, err := resolveParentRouterNames(client, ingressRoute, p.AllowCrossNamespace)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Error resolving parent routers")
|
||||
continue
|
||||
}
|
||||
|
||||
for _, route := range ingressRoute.Spec.Routes {
|
||||
if len(route.Kind) > 0 && route.Kind != "Rule" {
|
||||
logger.Error().Msgf("Unsupported match kind: %s. Only \"Rule\" is supported for now.", route.Kind)
|
||||
|
|
@ -72,11 +78,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
continue
|
||||
}
|
||||
|
||||
serviceKey, err := makeServiceKey(route.Match, ingressName)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
}
|
||||
serviceKey := makeServiceKey(route.Match, ingressName)
|
||||
|
||||
mds, err := p.makeMiddlewareKeys(ctx, ingressRoute.Namespace, route.Middlewares)
|
||||
if err != nil {
|
||||
|
|
@ -87,7 +89,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
normalized := provider.Normalize(makeID(ingressRoute.Namespace, serviceKey))
|
||||
serviceName := normalized
|
||||
|
||||
if len(route.Services) > 1 {
|
||||
switch {
|
||||
case len(route.Services) > 1:
|
||||
spec := traefikv1alpha1.TraefikServiceSpec{
|
||||
Weighted: &traefikv1alpha1.WeightedRoundRobin{
|
||||
Services: route.Services,
|
||||
|
|
@ -99,7 +102,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
logger.Error().Err(errBuild).Send()
|
||||
continue
|
||||
}
|
||||
} else if len(route.Services) == 1 {
|
||||
case len(route.Services) == 1:
|
||||
fullName, serversLB, err := cb.nameAndService(ctx, ingressRoute.Namespace, route.Services[0].LoadBalancerSpec)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
|
|
@ -111,6 +114,9 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
} else {
|
||||
serviceName = fullName
|
||||
}
|
||||
default:
|
||||
// Routes without services leave serviceName empty.
|
||||
serviceName = ""
|
||||
}
|
||||
|
||||
r := &dynamic.Router{
|
||||
|
|
@ -121,6 +127,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
Rule: route.Match,
|
||||
Service: serviceName,
|
||||
Observability: route.Observability,
|
||||
ParentRefs: parentRouterNames,
|
||||
}
|
||||
|
||||
if ingressRoute.Spec.TLS != nil {
|
||||
|
|
@ -202,6 +209,50 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str
|
|||
return mds, nil
|
||||
}
|
||||
|
||||
// resolveParentRouterNames resolves parent IngressRoute references to router names.
|
||||
// It returns the list of parent router names and an error if one occurred during processing.
|
||||
func resolveParentRouterNames(client Client, ingressRoute *traefikv1alpha1.IngressRoute, allowCrossNamespace bool) ([]string, error) {
|
||||
// If no parent refs, return empty list (not an error).
|
||||
if len(ingressRoute.Spec.ParentRefs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var parentRouterNames []string
|
||||
for _, parentRef := range ingressRoute.Spec.ParentRefs {
|
||||
// Determine parent namespace (default to child namespace if not specified).
|
||||
parentNamespace := parentRef.Namespace
|
||||
if parentNamespace == "" {
|
||||
parentNamespace = ingressRoute.Namespace
|
||||
}
|
||||
|
||||
// Validate cross-namespace access.
|
||||
if !isNamespaceAllowed(allowCrossNamespace, ingressRoute.Namespace, parentNamespace) {
|
||||
return nil, fmt.Errorf("cross-namespace reference to parent IngressRoute %s/%s not allowed", parentNamespace, parentRef.Name)
|
||||
}
|
||||
|
||||
var parentIngressRoute *traefikv1alpha1.IngressRoute
|
||||
for _, ir := range client.GetIngressRoutes() {
|
||||
if ir.Name == parentRef.Name && ir.Namespace == parentNamespace {
|
||||
parentIngressRoute = ir
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if parentIngressRoute == nil {
|
||||
return nil, fmt.Errorf("parent IngressRoute %s/%s does not exist", parentNamespace, parentRef.Name)
|
||||
}
|
||||
|
||||
// Compute router names for all routes in parent IngressRoute.
|
||||
for _, route := range parentIngressRoute.Spec.Routes {
|
||||
serviceKey := makeServiceKey(route.Match, parentIngressRoute.Name)
|
||||
routerName := provider.Normalize(makeID(parentIngressRoute.Namespace, serviceKey))
|
||||
parentRouterNames = append(parentRouterNames, routerName)
|
||||
}
|
||||
}
|
||||
|
||||
return parentRouterNames, nil
|
||||
}
|
||||
|
||||
type configBuilder struct {
|
||||
client Client
|
||||
allowCrossNamespace bool
|
||||
|
|
|
|||
|
|
@ -50,11 +50,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
|
|||
continue
|
||||
}
|
||||
|
||||
key, err := makeServiceKey(route.Match, ingressName)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
}
|
||||
key := makeServiceKey(route.Match, ingressName)
|
||||
|
||||
mds, err := p.makeMiddlewareTCPKeys(ctx, ingressRouteTCP.Namespace, route.Middlewares)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -5351,6 +5351,322 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IngressRoute with single parent (single route)",
|
||||
paths: []string{"parent_refs_services.yml", "parent_refs_single_parent_single_route.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-parent-single-3c07cfffe8e5f876a01e": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`parent.example.com`)",
|
||||
},
|
||||
"default-child-single-2bba0a3de1b50b70a519": {
|
||||
Service: "default-child-single-2bba0a3de1b50b70a519",
|
||||
Rule: "Path(`/api`)",
|
||||
ParentRefs: []string{"default-parent-single-3c07cfffe8e5f876a01e"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-child-single-2bba0a3de1b50b70a519": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.2.1:9000",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.2.2:9000",
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IngressRoute with single parent (multiple routes) - all parent routers in ParentRefs",
|
||||
paths: []string{"parent_refs_services.yml", "parent_refs_single_parent_multiple_routes.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-parent-multi-4aac0d541c2b669a2d5d": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`api.example.com`) && PathPrefix(`/v1`)",
|
||||
},
|
||||
"default-parent-multi-0af1ca0a94f5b87a125e": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`api.example.com`) && PathPrefix(`/v2`)",
|
||||
},
|
||||
"default-child-multi-routes-b0479051e6a353d66211": {
|
||||
Service: "default-child-multi-routes-b0479051e6a353d66211",
|
||||
Rule: "Path(`/users`)",
|
||||
ParentRefs: []string{"default-parent-multi-4aac0d541c2b669a2d5d", "default-parent-multi-0af1ca0a94f5b87a125e"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-child-multi-routes-b0479051e6a353d66211": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.5.1:9000",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.5.2:9000",
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IngressRoute with multiple parents",
|
||||
paths: []string{"parent_refs_services.yml", "parent_refs_multiple_parents.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-parent-a-629990b524bf9a1a8d27": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`a.example.com`)",
|
||||
},
|
||||
"default-parent-b-add617f9b95cff009054": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`b.example.com`)",
|
||||
},
|
||||
"default-child-multi-parents-8013b5025acddd1761d1": {
|
||||
Service: "default-child-multi-parents-8013b5025acddd1761d1",
|
||||
Rule: "Path(`/shared`)",
|
||||
ParentRefs: []string{"default-parent-a-629990b524bf9a1a8d27", "default-parent-b-add617f9b95cff009054"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-child-multi-parents-8013b5025acddd1761d1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.8.1:9000",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.8.2:9000",
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IngressRoute with missing parent - routers skipped",
|
||||
paths: []string{"parent_refs_services.yml", "parent_refs_missing_parent.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
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: "IngressRoute with cross-namespace parent allowed",
|
||||
allowCrossNamespace: true,
|
||||
paths: []string{"parent_refs_services.yml", "parent_refs_cross_namespace_allowed.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"ns-a-parent-cross-74575ab54671a3ede28c": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`cross.example.com`)",
|
||||
},
|
||||
"ns-b-child-cross-allowed-0bad04de665623bf2362": {
|
||||
Service: "ns-b-child-cross-allowed-0bad04de665623bf2362",
|
||||
Rule: "Path(`/cross`)",
|
||||
ParentRefs: []string{"ns-a-parent-cross-74575ab54671a3ede28c"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"ns-b-child-cross-allowed-0bad04de665623bf2362": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.11.1:9000",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.11.2:9000",
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IngressRoute with cross-namespace parent denied",
|
||||
allowCrossNamespace: false,
|
||||
paths: []string{"parent_refs_services.yml", "parent_refs_cross_namespace_denied.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"ns-a-parent-cross-74575ab54671a3ede28c": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`cross.example.com`)",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "IngressRoute with parent namespace defaulting to child namespace",
|
||||
paths: []string{"parent_refs_services.yml", "parent_refs_default_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-parent-default-9b8ab283eeed3eb66561": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`default.example.com`)",
|
||||
},
|
||||
"default-child-same-9234eba1edcfbd8a7723": {
|
||||
Service: "default-child-same-9234eba1edcfbd8a7723",
|
||||
Rule: "Path(`/same`)",
|
||||
ParentRefs: []string{"default-parent-default-9b8ab283eeed3eb66561"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-child-same-9234eba1edcfbd8a7723": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.14.1:9000",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.14.2:9000",
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ type IngressRouteSpec struct {
|
|||
// TLS defines the TLS configuration.
|
||||
// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/router/#tls
|
||||
TLS *TLS `json:"tls,omitempty"`
|
||||
// ParentRefs defines references to parent IngressRoute resources for multi-layer routing.
|
||||
// When set, this IngressRoute's routers will be children of the referenced parent IngressRoute's routers.
|
||||
// More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#parentrefs
|
||||
ParentRefs []IngressRouteRef `json:"parentRefs,omitempty"`
|
||||
}
|
||||
|
||||
// Route holds the HTTP route configuration.
|
||||
|
|
@ -211,6 +215,14 @@ type MiddlewareRef struct {
|
|||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// IngressRouteRef is a reference to an IngressRoute resource.
|
||||
type IngressRouteRef struct {
|
||||
// Name defines the name of the referenced IngressRoute resource.
|
||||
Name string `json:"name"`
|
||||
// Namespace defines the namespace of the referenced IngressRoute resource.
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:storageversion
|
||||
|
|
|
|||
|
|
@ -432,6 +432,22 @@ func (in *IngressRouteList) DeepCopyObject() runtime.Object {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IngressRouteRef) DeepCopyInto(out *IngressRouteRef) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressRouteRef.
|
||||
func (in *IngressRouteRef) DeepCopy() *IngressRouteRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IngressRouteRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IngressRouteSpec) DeepCopyInto(out *IngressRouteSpec) {
|
||||
*out = *in
|
||||
|
|
@ -452,6 +468,11 @@ func (in *IngressRouteSpec) DeepCopyInto(out *IngressRouteSpec) {
|
|||
*out = new(TLS)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ParentRefs != nil {
|
||||
in, out := &in.ParentRefs, &out.ParentRefs
|
||||
*out = make([]IngressRouteRef, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue