Services middleware and Gateway API filters on HTTP backends

This commit is contained in:
Julien Salleyron
2026-01-29 17:16:04 +01:00
committed by GitHub
parent 5969d1680d
commit 8425e09806
37 changed files with 846 additions and 55 deletions
+1 -1
View File
@@ -103,7 +103,7 @@ test-integration:
#? test-gateway-api-conformance: Run the Gateway API conformance tests #? test-gateway-api-conformance: Run the Gateway API conformance tests
test-gateway-api-conformance: build-image-dirty test-gateway-api-conformance: build-image-dirty
# In case of a new Minor/Major version, the traefikVersion needs to be updated. # In case of a new Minor/Major version, the traefikVersion needs to be updated.
GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -tags gatewayAPIConformance -test.run GatewayAPIConformanceSuite -traefikVersion="v3.6" $(TESTFLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -tags gatewayAPIConformance -test.run GatewayAPIConformanceSuite -traefikVersion="v3.7" $(TESTFLAGS)
.PHONY: test-knative-conformance .PHONY: test-knative-conformance
#? test-knative-conformance: Run the Knative conformance tests #? test-knative-conformance: Run the Knative conformance tests
@@ -222,6 +222,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of
the referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -1232,6 +1251,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references to
Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -2974,6 +3012,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -3211,6 +3268,24 @@ spec:
Default value is -1, which means unlimited size. Default value is -1, which means unlimited size.
format: int64 format: int64
type: integer type: integer
middlewares:
description: Middlewares defines the list of references to Middleware
resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware resource.
properties:
name:
description: Name defines the name of the referenced Middleware
resource.
type: string
namespace:
description: Namespace defines the namespace of the referenced
Middleware resource.
type: string
required:
- name
type: object
type: array
mirrorBody: mirrorBody:
description: |- description: |-
MirrorBody defines whether the body of the request should be mirrored. MirrorBody defines whether the body of the request should be mirrored.
@@ -3298,6 +3373,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -3686,6 +3780,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -223,6 +223,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of
the referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -387,6 +387,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references to
Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -131,6 +131,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -368,6 +387,24 @@ spec:
Default value is -1, which means unlimited size. Default value is -1, which means unlimited size.
format: int64 format: int64
type: integer type: integer
middlewares:
description: Middlewares defines the list of references to Middleware
resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware resource.
properties:
name:
description: Name defines the name of the referenced Middleware
resource.
type: string
namespace:
description: Namespace defines the namespace of the referenced
Middleware resource.
type: string
required:
- name
type: object
type: array
mirrorBody: mirrorBody:
description: |- description: |-
MirrorBody defines whether the body of the request should be mirrored. MirrorBody defines whether the body of the request should be mirrored.
@@ -455,6 +492,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -843,6 +899,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -112,31 +112,33 @@
[http.services.Service03.loadBalancer.responseForwarding] [http.services.Service03.loadBalancer.responseForwarding]
flushInterval = "42s" flushInterval = "42s"
[http.services.Service04] [http.services.Service04]
[http.services.Service04.mirroring] middlewares = ["foobar", "foobar"]
[http.services.Service05]
[http.services.Service05.mirroring]
service = "foobar" service = "foobar"
mirrorBody = true mirrorBody = true
maxBodySize = 42 maxBodySize = 42
[[http.services.Service04.mirroring.mirrors]] [[http.services.Service05.mirroring.mirrors]]
name = "foobar" name = "foobar"
percent = 42 percent = 42
[[http.services.Service04.mirroring.mirrors]] [[http.services.Service05.mirroring.mirrors]]
name = "foobar" name = "foobar"
percent = 42 percent = 42
[http.services.Service04.mirroring.healthCheck] [http.services.Service05.mirroring.healthCheck]
[http.services.Service05] [http.services.Service06]
[http.services.Service05.weighted] [http.services.Service06.weighted]
[[http.services.Service05.weighted.services]] [[http.services.Service06.weighted.services]]
name = "foobar" name = "foobar"
weight = 42 weight = 42
[[http.services.Service05.weighted.services]] [[http.services.Service06.weighted.services]]
name = "foobar" name = "foobar"
weight = 42 weight = 42
[http.services.Service05.weighted.sticky] [http.services.Service06.weighted.sticky]
[http.services.Service05.weighted.sticky.cookie] [http.services.Service06.weighted.sticky.cookie]
name = "foobar" name = "foobar"
secure = true secure = true
httpOnly = true httpOnly = true
@@ -144,7 +146,7 @@
maxAge = 42 maxAge = 42
path = "foobar" path = "foobar"
domain = "foobar" domain = "foobar"
[http.services.Service05.weighted.healthCheck] [http.services.Service06.weighted.healthCheck]
[http.middlewares] [http.middlewares]
[http.middlewares.Middleware01] [http.middlewares.Middleware01]
[http.middlewares.Middleware01.addPrefix] [http.middlewares.Middleware01.addPrefix]
@@ -120,6 +120,10 @@ http:
flushInterval: 42s flushInterval: 42s
serversTransport: foobar serversTransport: foobar
Service04: Service04:
middlewares:
- foobar
- foobar
Service05:
mirroring: mirroring:
service: foobar service: foobar
mirrorBody: true mirrorBody: true
@@ -130,7 +134,7 @@ http:
- name: foobar - name: foobar
percent: 42 percent: 42
healthCheck: {} healthCheck: {}
Service05: Service06:
weighted: weighted:
services: services:
- name: foobar - name: foobar
+113
View File
@@ -223,6 +223,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of
the referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -1233,6 +1252,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references to
Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -2975,6 +3013,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -3212,6 +3269,24 @@ spec:
Default value is -1, which means unlimited size. Default value is -1, which means unlimited size.
format: int64 format: int64
type: integer type: integer
middlewares:
description: Middlewares defines the list of references to Middleware
resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware resource.
properties:
name:
description: Name defines the name of the referenced Middleware
resource.
type: string
namespace:
description: Namespace defines the namespace of the referenced
Middleware resource.
type: string
required:
- name
type: object
type: array
mirrorBody: mirrorBody:
description: |- description: |-
MirrorBody defines whether the body of the request should be mirrored. MirrorBody defines whether the body of the request should be mirrored.
@@ -3299,6 +3374,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -3687,6 +3781,25 @@ spec:
- Service - Service
- TraefikService - TraefikService
type: string type: string
middlewares:
description: Middlewares defines the list of references
to Middleware resources to apply to the service.
items:
description: MiddlewareRef is a reference to a Middleware
resource.
properties:
name:
description: Name defines the name of the referenced
Middleware resource.
type: string
namespace:
description: Namespace defines the namespace of the
referenced Middleware resource.
type: string
required:
- name
type: object
type: array
name: name:
description: |- description: |-
Name defines the name of the referenced Kubernetes Service or TraefikService. Name defines the name of the referenced Kubernetes Service or TraefikService.
@@ -0,0 +1,35 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[api]
insecure = true
[log]
level = "DEBUG"
noColor = true
[entryPoints]
[entryPoints.web]
address = ":8000"
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers]
[http.routers.router1]
service = "service1"
rule = "Path(`/whoami`)"
[http.middlewares]
[http.middlewares.add-header.headers.customRequestHeaders]
X-Custom-Header = "service-middleware-test"
[http.services]
[http.services.service1]
middlewares = ["add-header"]
[http.services.service1.loadBalancer]
[[http.services.service1.loadBalancer.servers]]
url = "{{ .Server }}"
@@ -8,7 +8,7 @@ implementation:
organization: traefik organization: traefik
project: traefik project: traefik
url: https://traefik.io/ url: https://traefik.io/
version: v3.6 version: v3.7
kind: ConformanceReport kind: ConformanceReport
mode: default mode: default
profiles: profiles:
@@ -30,12 +30,13 @@ profiles:
result: success result: success
statistics: statistics:
Failed: 0 Failed: 0
Passed: 13 Passed: 15
Skipped: 0 Skipped: 0
supportedFeatures: supportedFeatures:
- GatewayPort8080 - GatewayPort8080
- HTTPRouteBackendProtocolH2C - HTTPRouteBackendProtocolH2C
- HTTPRouteBackendProtocolWebSocket - HTTPRouteBackendProtocolWebSocket
- HTTPRouteBackendRequestHeaderModification
- HTTPRouteDestinationPortMatching - HTTPRouteDestinationPortMatching
- HTTPRouteHostRewrite - HTTPRouteHostRewrite
- HTTPRouteMethodMatching - HTTPRouteMethodMatching
@@ -50,7 +51,6 @@ profiles:
- GatewayHTTPListenerIsolation - GatewayHTTPListenerIsolation
- GatewayInfrastructurePropagation - GatewayInfrastructurePropagation
- GatewayStaticAddresses - GatewayStaticAddresses
- HTTPRouteBackendRequestHeaderModification
- HTTPRouteBackendTimeout - HTTPRouteBackendTimeout
- HTTPRouteCORS - HTTPRouteCORS
- HTTPRouteNamedRouteRule - HTTPRouteNamedRouteRule
+34
View File
@@ -2364,3 +2364,37 @@ func (s *SimpleSuite) TestEncodedCharactersDifferentEntryPoints() {
require.NoError(s.T(), err) require.NoError(s.T(), err)
} }
} }
func (s *SimpleSuite) TestServiceMiddleware() {
s.createComposeProject("base")
s.composeUp()
defer s.composeDown()
whoamiIP := s.getComposeServiceIP("whoami1")
file := s.adaptFile("fixtures/service_middleware.toml", struct {
Server string
}{Server: "http://" + whoamiIP})
s.traefikCmd(withConfigFile(file))
// Wait for Traefik to be ready
err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 2*time.Second, try.BodyContains("service1"))
require.NoError(s.T(), err)
// Make a request and verify the middleware added the custom header
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
require.NoError(s.T(), err)
response, err := http.DefaultClient.Do(req)
require.NoError(s.T(), err)
assert.Equal(s.T(), http.StatusOK, response.StatusCode)
// Read the response body to check if the whoami service received the custom header
body, err := io.ReadAll(response.Body)
require.NoError(s.T(), err)
// The whoami service should have received the X-Custom-Header that was added by the service middleware
assert.Contains(s.T(), string(body), "X-Custom-Header: service-middleware-test")
}
+1
View File
@@ -55,6 +55,7 @@ type Model struct {
// Service holds a service configuration (can only be of one type at the same time). // Service holds a service configuration (can only be of one type at the same time).
type Service struct { type Service struct {
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
LoadBalancer *ServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty" export:"true"` LoadBalancer *ServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty" export:"true"`
HighestRandomWeight *HighestRandomWeight `json:"highestRandomWeight,omitempty" toml:"highestRandomWeight,omitempty" yaml:"highestRandomWeight,omitempty" label:"-" export:"true"` HighestRandomWeight *HighestRandomWeight `json:"highestRandomWeight,omitempty" toml:"highestRandomWeight,omitempty" yaml:"highestRandomWeight,omitempty" label:"-" export:"true"`
Weighted *WeightedRoundRobin `json:"weighted,omitempty" toml:"weighted,omitempty" yaml:"weighted,omitempty" label:"-" export:"true"` Weighted *WeightedRoundRobin `json:"weighted,omitempty" toml:"weighted,omitempty" yaml:"weighted,omitempty" label:"-" export:"true"`
@@ -1672,6 +1672,11 @@ func (in *ServersTransport) DeepCopy() *ServersTransport {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Service) DeepCopyInto(out *Service) { func (in *Service) DeepCopyInto(out *Service) {
*out = *in *out = *in
if in.Middlewares != nil {
in, out := &in.Middlewares, &out.Middlewares
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.LoadBalancer != nil { if in.LoadBalancer != nil {
in, out := &in.LoadBalancer, &out.LoadBalancer in, out := &in.LoadBalancer, &out.LoadBalancer
*out = new(ServersLoadBalancer) *out = new(ServersLoadBalancer)
+4 -4
View File
@@ -13,14 +13,14 @@ const (
typeName = "Chain" typeName = "Chain"
) )
type chainBuilder interface { type middlewareChainBuilder interface {
BuildChain(ctx context.Context, middlewares []string) *alice.Chain BuildMiddlewareChain(ctx context.Context, middlewares []string) *alice.Chain
} }
// New creates a chain middleware. // New creates a chain middleware.
func New(ctx context.Context, next http.Handler, config dynamic.Chain, builder chainBuilder, name string) (http.Handler, error) { func New(ctx context.Context, next http.Handler, config dynamic.Chain, builder middlewareChainBuilder, name string) (http.Handler, error) {
middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware") middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware")
middlewareChain := builder.BuildChain(ctx, config.Middlewares) middlewareChain := builder.BuildMiddlewareChain(ctx, config.Middlewares)
return middlewareChain.Then(next) return middlewareChain.Then(next)
} }
@@ -0,0 +1,45 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: stripprefix
namespace: default
spec:
stripPrefix:
prefixes:
- /tobestripped
---
apiVersion: traefik.io/v1alpha1
kind: TraefikService
metadata:
name: test-weighted
namespace: default
spec:
weighted:
services:
- name: whoami
port: 80
weight: 1
middlewares:
- name: stripprefix
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule
priority: 12
services:
- name: test-weighted
kind: TraefikService
@@ -37,6 +37,7 @@ type LoadBalancerSpecApplyConfiguration struct {
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
Kind *string `json:"kind,omitempty"` Kind *string `json:"kind,omitempty"`
Namespace *string `json:"namespace,omitempty"` Namespace *string `json:"namespace,omitempty"`
Middlewares []MiddlewareRefApplyConfiguration `json:"middlewares,omitempty"`
Sticky *dynamic.Sticky `json:"sticky,omitempty"` Sticky *dynamic.Sticky `json:"sticky,omitempty"`
Port *intstr.IntOrString `json:"port,omitempty"` Port *intstr.IntOrString `json:"port,omitempty"`
Scheme *string `json:"scheme,omitempty"` Scheme *string `json:"scheme,omitempty"`
@@ -81,6 +82,19 @@ func (b *LoadBalancerSpecApplyConfiguration) WithNamespace(value string) *LoadBa
return b return b
} }
// WithMiddlewares adds the given value to the Middlewares field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Middlewares field.
func (b *LoadBalancerSpecApplyConfiguration) WithMiddlewares(values ...*MiddlewareRefApplyConfiguration) *LoadBalancerSpecApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithMiddlewares")
}
b.Middlewares = append(b.Middlewares, *values[i])
}
return b
}
// WithSticky sets the Sticky field in the declarative configuration to the given value // WithSticky sets the Sticky field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations. // and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Sticky field is set to the value of the last call. // If called multiple times, the Sticky field is set to the value of the last call.
@@ -70,6 +70,19 @@ func (b *MirroringApplyConfiguration) WithNamespace(value string) *MirroringAppl
return b return b
} }
// WithMiddlewares adds the given value to the Middlewares field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Middlewares field.
func (b *MirroringApplyConfiguration) WithMiddlewares(values ...*MiddlewareRefApplyConfiguration) *MirroringApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithMiddlewares")
}
b.LoadBalancerSpecApplyConfiguration.Middlewares = append(b.LoadBalancerSpecApplyConfiguration.Middlewares, *values[i])
}
return b
}
// WithSticky sets the Sticky field in the declarative configuration to the given value // WithSticky sets the Sticky field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations. // and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Sticky field is set to the value of the last call. // If called multiple times, the Sticky field is set to the value of the last call.
@@ -68,6 +68,19 @@ func (b *MirrorServiceApplyConfiguration) WithNamespace(value string) *MirrorSer
return b return b
} }
// WithMiddlewares adds the given value to the Middlewares field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Middlewares field.
func (b *MirrorServiceApplyConfiguration) WithMiddlewares(values ...*MiddlewareRefApplyConfiguration) *MirrorServiceApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithMiddlewares")
}
b.LoadBalancerSpecApplyConfiguration.Middlewares = append(b.LoadBalancerSpecApplyConfiguration.Middlewares, *values[i])
}
return b
}
// WithSticky sets the Sticky field in the declarative configuration to the given value // WithSticky sets the Sticky field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations. // and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Sticky field is set to the value of the last call. // If called multiple times, the Sticky field is set to the value of the last call.
@@ -67,6 +67,19 @@ func (b *ServiceApplyConfiguration) WithNamespace(value string) *ServiceApplyCon
return b return b
} }
// WithMiddlewares adds the given value to the Middlewares field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Middlewares field.
func (b *ServiceApplyConfiguration) WithMiddlewares(values ...*MiddlewareRefApplyConfiguration) *ServiceApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithMiddlewares")
}
b.LoadBalancerSpecApplyConfiguration.Middlewares = append(b.LoadBalancerSpecApplyConfiguration.Middlewares, *values[i])
}
return b
}
// WithSticky sets the Sticky field in the declarative configuration to the given value // WithSticky sets the Sticky field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations. // and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Sticky field is set to the value of the last call. // If called multiple times, the Sticky field is set to the value of the last call.
+3 -3
View File
@@ -266,7 +266,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
continue continue
} }
errorPage, errorPageService, err := p.createErrorPageMiddleware(client, middleware.Namespace, middleware.Spec.Errors) errorPage, errorPageService, err := p.createErrorPageMiddleware(ctxMid, client, middleware.Namespace, middleware.Spec.Errors)
if err != nil { if err != nil {
logger.Error().Err(err).Msg("Error while reading error page middleware") logger.Error().Err(err).Msg("Error while reading error page middleware")
continue continue
@@ -645,7 +645,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
return conf return conf
} }
func (p *Provider) createErrorPageMiddleware(client Client, namespace string, errorPage *traefikv1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) { func (p *Provider) createErrorPageMiddleware(ctx context.Context, client Client, namespace string, errorPage *traefikv1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {
if errorPage == nil { if errorPage == nil {
return nil, nil, nil return nil, nil, nil
} }
@@ -663,7 +663,7 @@ func (p *Provider) createErrorPageMiddleware(client Client, namespace string, er
allowEmptyServices: p.AllowEmptyServices, allowEmptyServices: p.AllowEmptyServices,
} }
balancerServerHTTP, err := cb.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec) balancerServerHTTP, err := cb.buildServersLB(ctx, namespace, errorPage.Service.LoadBalancerSpec)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
+19 -9
View File
@@ -82,7 +82,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
serviceKey := makeServiceKey(route.Match, ingressName) serviceKey := makeServiceKey(route.Match, ingressName)
mds, err := p.makeMiddlewareKeys(ctx, ingressRoute.Namespace, route.Middlewares) mds, err := makeMiddlewareKeys(ctx, ingressRoute.Namespace, route.Middlewares, p.AllowCrossNamespace)
if err != nil { if err != nil {
logger.Error().Err(err).Msg("Failed to create middleware keys") logger.Error().Err(err).Msg("Failed to create middleware keys")
continue continue
@@ -172,13 +172,13 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
return conf return conf
} }
func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace string, middlewares []traefikv1alpha1.MiddlewareRef) ([]string, error) { func makeMiddlewareKeys(ctx context.Context, namespace string, middlewares []traefikv1alpha1.MiddlewareRef, allowCrossNamespace bool) ([]string, error) {
var mds []string var mds []string
for _, mi := range middlewares { for _, mi := range middlewares {
name := mi.Name name := mi.Name
if !p.AllowCrossNamespace && strings.HasSuffix(mi.Name, providerNamespaceSeparator+providerName) { if !allowCrossNamespace && strings.HasSuffix(mi.Name, providerNamespaceSeparator+providerName) {
// Since we are not able to know if another namespace is in the name (namespace-name@kubernetescrd), // Since we are not able to know if another namespace is in the name (namespace-name@kubernetescrd),
// if the provider namespace kubernetescrd is used, // if the provider namespace kubernetescrd is used,
// we don't allow this format to avoid cross namespace references. // we don't allow this format to avoid cross namespace references.
@@ -196,10 +196,10 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str
continue continue
} }
ns := ingRouteNamespace ns := namespace
if len(mi.Namespace) > 0 { if len(mi.Namespace) > 0 {
if !isNamespaceAllowed(p.AllowCrossNamespace, ingRouteNamespace, mi.Namespace) { if !isNamespaceAllowed(allowCrossNamespace, namespace, mi.Namespace) {
return nil, fmt.Errorf("middleware %s/%s is not in the IngressRoute namespace %s", mi.Namespace, mi.Name, ingRouteNamespace) return nil, fmt.Errorf("middleware %s/%s is not in the parent namespace %s", mi.Namespace, mi.Name, namespace)
} }
ns = mi.Namespace ns = mi.Namespace
@@ -333,6 +333,7 @@ func (c configBuilder) buildServicesLB(ctx context.Context, namespace string, tS
Sticky: sticky, Sticky: sticky,
}, },
} }
return nil return nil
} }
@@ -378,7 +379,7 @@ func (c configBuilder) buildMirroring(ctx context.Context, tService *traefikv1al
} }
// buildServersLB creates the configuration for the load-balancer of servers defined by svc. // buildServersLB creates the configuration for the load-balancer of servers defined by svc.
func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.LoadBalancerSpec) (*dynamic.Service, error) { func (c configBuilder) buildServersLB(ctx context.Context, namespace string, svc traefikv1alpha1.LoadBalancerSpec) (*dynamic.Service, error) {
lb := &dynamic.ServersLoadBalancer{} lb := &dynamic.ServersLoadBalancer{}
lb.SetDefaults() lb.SetDefaults()
@@ -501,7 +502,16 @@ func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.Load
return nil, err return nil, err
} }
return &dynamic.Service{LoadBalancer: lb}, nil service := &dynamic.Service{LoadBalancer: lb}
if len(svc.Middlewares) > 0 {
mds, err := makeMiddlewareKeys(ctx, namespace, svc.Middlewares, c.allowCrossNamespace)
if err != nil {
return nil, fmt.Errorf("could not create middleware keys: %w", err)
}
service.Middlewares = mds
}
return service, nil
} }
func (c configBuilder) makeServersTransportKey(parentNamespace string, serversTransportName string) (string, error) { func (c configBuilder) makeServersTransportKey(parentNamespace string, serversTransportName string) (string, error) {
@@ -687,7 +697,7 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin
switch service.Kind { switch service.Kind {
case "", "Service": case "", "Service":
serversLB, err := c.buildServersLB(namespace, service) serversLB, err := c.buildServersLB(ctx, namespace, service)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@@ -3279,6 +3279,71 @@ func TestLoadIngressRoutes(t *testing.T) {
}, },
}, },
}, },
{
desc: "TraefikService with service middleware",
paths: []string{"services.yml", "with_traefik_service_middleware.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TLS: &dynamic.TLSConfiguration{},
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-test-route-6b204d94623b3df4370c": {
EntryPoints: []string{"web"},
Service: "default-test-weighted",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-stripprefix": {
StripPrefix: &dynamic.StripPrefix{
Prefixes: []string{"/tobestripped"},
},
},
},
Services: map[string]*dynamic.Service{
"default-test-weighted": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-80",
Weight: pointer(1),
},
},
},
},
"default-whoami-80": {
Middlewares: []string{"default-stripprefix"},
LoadBalancer: &dynamic.ServersLoadBalancer{
Strategy: dynamic.BalancerStrategyWRR,
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: pointer(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{ {
desc: "one kube services in a highest random weight", desc: "one kube services in a highest random weight",
paths: []string{"with_highest_random_weight.yml"}, paths: []string{"with_highest_random_weight.yml"},
@@ -110,6 +110,8 @@ type LoadBalancerSpec struct {
Kind string `json:"kind,omitempty"` Kind string `json:"kind,omitempty"`
// Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. // Namespace defines the namespace of the referenced Kubernetes Service or TraefikService.
Namespace string `json:"namespace,omitempty"` Namespace string `json:"namespace,omitempty"`
// Middlewares defines the list of references to Middleware resources to apply to the service.
Middlewares []MiddlewareRef `json:"middlewares,omitempty"`
// Sticky defines the sticky sessions configuration. // Sticky defines the sticky sessions configuration.
// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions
Sticky *dynamic.Sticky `json:"sticky,omitempty"` Sticky *dynamic.Sticky `json:"sticky,omitempty"`
@@ -685,6 +685,11 @@ func (in *IngressRouteUDPSpec) DeepCopy() *IngressRouteUDPSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) { func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
*out = *in *out = *in
if in.Middlewares != nil {
in, out := &in.Middlewares, &out.Middlewares
*out = make([]MiddlewareRef, len(*in))
copy(*out, *in)
}
if in.Sticky != nil { if in.Sticky != nil {
in, out := &in.Sticky, &out.Sticky in, out := &in.Sticky, &out.Sticky
*out = new(dynamic.Sticky) *out = new(dynamic.Sticky)
@@ -44,5 +44,6 @@ func extendedHTTPRouteFeatures() sets.Set[features.Feature] {
features.HTTPRouteBackendProtocolH2CFeature, features.HTTPRouteBackendProtocolH2CFeature,
features.HTTPRouteBackendProtocolWebSocketFeature, features.HTTPRouteBackendProtocolWebSocketFeature,
features.HTTPRouteDestinationPortMatchingFeature, features.HTTPRouteDestinationPortMatchingFeature,
features.HTTPRouteBackendRequestHeaderModificationFeature,
) )
} }
@@ -0,0 +1,62 @@
---
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:
- 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: PathPrefix
value: /bar
backendRefs:
- name: whoami
port: 80
weight: 1
kind: Service
group: ""
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
set:
- name: X-Foo
value: Bar
add:
- name: X-Bar
value: Foo
remove:
- X-Baz
+22 -5
View File
@@ -140,6 +140,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
var err error var err error
routerName := makeRouterName(rule, routeKey) routerName := makeRouterName(rule, routeKey)
// TODO loadMiddlewares errors could change the condition.
router.Middlewares, err = p.loadMiddlewares(conf, route.Namespace, routerName, routeRule.Filters, match.Path) router.Middlewares, err = p.loadMiddlewares(conf, route.Namespace, routerName, routeRule.Filters, match.Path)
switch { switch {
case err != nil: case err != nil:
@@ -164,7 +165,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
default: default:
var serviceCondition *metav1.Condition var serviceCondition *metav1.Condition
router.Service, serviceCondition = p.loadWRRService(ctx, listener, conf, routerName, routeRule, route) router.Service, serviceCondition = p.loadWRRService(ctx, listener, conf, routerName, routeRule, route, match.Path)
if serviceCondition != nil { if serviceCondition != nil {
condition = *serviceCondition condition = *serviceCondition
} }
@@ -179,7 +180,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
return conf, condition return conf, condition
} }
func (p *Provider) loadWRRService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute) (string, *metav1.Condition) { func (p *Provider) loadWRRService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute, pathMatch *gatev1.HTTPPathMatch) (string, *metav1.Condition) {
name := routeKey + "-wrr" name := routeKey + "-wrr"
if _, ok := conf.HTTP.Services[name]; ok { if _, ok := conf.HTTP.Services[name]; ok {
return name, nil return name, nil
@@ -188,7 +189,9 @@ func (p *Provider) loadWRRService(ctx context.Context, listener gatewayListener,
var wrr dynamic.WeightedRoundRobin var wrr dynamic.WeightedRoundRobin
var condition *metav1.Condition var condition *metav1.Condition
for _, backendRef := range routeRule.BackendRefs { for _, backendRef := range routeRule.BackendRefs {
svcName, errCondition := p.loadService(ctx, listener, conf, route, backendRef) // TODO in loadService we need to always return a non-nil serviceName even when there is an error which is not the
// usual defacto.
svcName, errCondition := p.loadService(ctx, listener, conf, route, backendRef, pathMatch)
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1))) weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
if errCondition != nil { if errCondition != nil {
log.Ctx(ctx).Error(). log.Ctx(ctx).Error().
@@ -215,7 +218,7 @@ func (p *Provider) loadWRRService(ctx context.Context, listener gatewayListener,
// loadService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef. // loadService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef.
// Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc). // Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc).
func (p *Provider) loadService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *metav1.Condition) { func (p *Provider) loadService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef, pathMatch *gatev1.HTTPPathMatch) (string, *metav1.Condition) {
kind := ptr.Deref(backendRef.Kind, kindService) kind := ptr.Deref(backendRef.Kind, kindService)
group := groupCore group := groupCore
@@ -241,6 +244,19 @@ func (p *Provider) loadService(ctx context.Context, listener gatewayListener, co
} }
} }
middlewares, err := p.loadMiddlewares(conf, namespace, serviceName, backendRef.Filters, pathMatch)
if err != nil {
return serviceName, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.RouteReasonInvalidKind),
Message: fmt.Sprintf("Cannot load filters on HTTPBackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
}
}
// TODO may be we could incorporate this "ignored" case into the loadHTTPBackendRef.
if group != groupCore || kind != kindService { if group != groupCore || kind != kindService {
name, service, err := p.loadHTTPBackendRef(namespace, backendRef) name, service, err := p.loadHTTPBackendRef(namespace, backendRef)
if err != nil { if err != nil {
@@ -255,6 +271,7 @@ func (p *Provider) loadService(ctx context.Context, listener gatewayListener, co
} }
if service != nil { if service != nil {
service.Middlewares = middlewares
conf.HTTP.Services[name] = service conf.HTTP.Services[name] = service
} }
@@ -286,7 +303,7 @@ func (p *Provider) loadService(ctx context.Context, listener gatewayListener, co
conf.HTTP.ServersTransports[serviceName] = st conf.HTTP.ServersTransports[serviceName] = st
} }
conf.HTTP.Services[serviceName] = &dynamic.Service{LoadBalancer: lb} conf.HTTP.Services[serviceName] = &dynamic.Service{LoadBalancer: lb, Middlewares: middlewares}
return serviceName, nil return serviceName, nil
} }
@@ -1917,6 +1917,77 @@ func TestLoadHTTPRoutes(t *testing.T) {
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{},
}, },
}, },
{
desc: "Simple HTTPRoute, backend filter request header modifier",
paths: []string{"services.yml", "httproute/backend_filter_request_header_modifier.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{
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-99f4d29346f69ccb6fc3": {
EntryPoints: []string{"web"},
Service: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-99f4d29346f69ccb6fc3-wrr",
Rule: "Host(`foo.com`) && (Path(`/bar`) || PathPrefix(`/bar/`))",
Priority: 10408,
RuleSyntax: "default",
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-whoami-http-requestheadermodifier-0": {
RequestHeaderModifier: &dynamic.HeaderModifier{
Set: map[string]string{"X-Foo": "Bar"},
Add: map[string]string{"X-Bar": "Foo"},
Remove: []string{"X-Baz"},
},
},
},
Services: map[string]*dynamic.Service{
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-99f4d29346f69ccb6fc3-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-http-80",
Weight: ptr.To(1),
},
},
},
},
"default-whoami-http-80": {
Middlewares: []string{"default-whoami-http-requestheadermodifier-0"},
LoadBalancer: &dynamic.ServersLoadBalancer{
Strategy: dynamic.BalancerStrategyWRR,
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, redirect HTTP to HTTPS", desc: "Simple HTTPRoute, redirect HTTP to HTTPS",
paths: []string{"services.yml", "httproute/filter_http_to_https.yml"}, paths: []string{"services.yml", "httproute/filter_http_to_https.yml"},
@@ -1954,6 +2025,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
}, },
}, },
}, },
Services: map[string]*dynamic.Service{ Services: map[string]*dynamic.Service{
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-364ce6ec04c3d49b19c4-wrr": { "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-364ce6ec04c3d49b19c4-wrr": {
Weighted: &dynamic.WeightedRoundRobin{}, Weighted: &dynamic.WeightedRoundRobin{},
@@ -46,6 +46,7 @@ type ServiceIng struct {
ServersScheme string `json:"serversScheme,omitempty"` ServersScheme string `json:"serversScheme,omitempty"`
ServersTransport string `json:"serversTransport,omitempty"` ServersTransport string `json:"serversTransport,omitempty"`
PassHostHeader *bool `json:"passHostHeader"` PassHostHeader *bool `json:"passHostHeader"`
Middlewares []string `json:"middlewares,omitempty"`
Sticky *dynamic.Sticky `json:"sticky,omitempty" label:"allowEmpty"` Sticky *dynamic.Sticky `json:"sticky,omitempty" label:"allowEmpty"`
NativeLB *bool `json:"nativeLB,omitempty"` NativeLB *bool `json:"nativeLB,omitempty"`
NodePortLB bool `json:"nodePortLB,omitempty"` NodePortLB bool `json:"nodePortLB,omitempty"`
@@ -161,6 +161,18 @@ func Test_parseServiceConfig(t *testing.T) {
}, },
}, },
}, },
{
desc: "service middlewares annotation",
annotations: map[string]string{
"traefik.ingress.kubernetes.io/service.middlewares": "middleware1,middleware2",
},
expected: &ServiceConfig{
Service: &ServiceIng{
Middlewares: []string{"middleware1", "middleware2"},
PassHostHeader: pointer(true),
},
},
},
{ {
desc: "empty map", desc: "empty map",
annotations: map[string]string{}, annotations: map[string]string{},
@@ -563,6 +563,7 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
if svcConfig != nil && svcConfig.Service != nil { if svcConfig != nil && svcConfig.Service != nil {
svc.LoadBalancer.Sticky = svcConfig.Service.Sticky svc.LoadBalancer.Sticky = svcConfig.Service.Sticky
svc.Middlewares = svcConfig.Service.Middlewares
if svcConfig.Service.PassHostHeader != nil { if svcConfig.Service.PassHostHeader != nil {
svc.LoadBalancer.PassHostHeader = svcConfig.Service.PassHostHeader svc.LoadBalancer.PassHostHeader = svcConfig.Service.PassHostHeader
+2 -2
View File
@@ -56,8 +56,8 @@ func NewBuilder(configs map[string]*runtime.MiddlewareInfo, serviceBuilder servi
return &Builder{configs: configs, serviceBuilder: serviceBuilder, pluginBuilder: pluginBuilder} return &Builder{configs: configs, serviceBuilder: serviceBuilder, pluginBuilder: pluginBuilder}
} }
// BuildChain creates a middleware chain. // BuildMiddlewareChain creates a middleware chain.
func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.Chain { func (b *Builder) BuildMiddlewareChain(ctx context.Context, middlewares []string) *alice.Chain {
chain := alice.New() chain := alice.New()
for _, name := range middlewares { for _, name := range middlewares {
middlewareName := provider.GetQualifiedName(ctx, name) middlewareName := provider.GetQualifiedName(ctx, name)
+3 -3
View File
@@ -19,7 +19,7 @@ func TestBuilder_BuildChainNilConfig(t *testing.T) {
} }
middlewaresBuilder := NewBuilder(testConfig, nil, nil) middlewaresBuilder := NewBuilder(testConfig, nil, nil)
chain := middlewaresBuilder.BuildChain(t.Context(), []string{"empty"}) chain := middlewaresBuilder.BuildMiddlewareChain(t.Context(), []string{"empty"})
_, err := chain.Then(nil) _, err := chain.Then(nil)
require.Error(t, err) require.Error(t, err)
} }
@@ -30,7 +30,7 @@ func TestBuilder_BuildChainNonExistentChain(t *testing.T) {
} }
middlewaresBuilder := NewBuilder(testConfig, nil, nil) middlewaresBuilder := NewBuilder(testConfig, nil, nil)
chain := middlewaresBuilder.BuildChain(t.Context(), []string{"empty"}) chain := middlewaresBuilder.BuildMiddlewareChain(t.Context(), []string{"empty"})
_, err := chain.Then(nil) _, err := chain.Then(nil)
require.Error(t, err) require.Error(t, err)
} }
@@ -271,7 +271,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
}) })
builder := NewBuilder(rtConf.Middlewares, nil, nil) builder := NewBuilder(rtConf.Middlewares, nil, nil)
result := builder.BuildChain(ctx, test.buildChain) result := builder.BuildMiddlewareChain(ctx, test.buildChain)
handlers, err := result.Then(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) handlers, err := result.Then(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }))
if test.expectedError != nil { if test.expectedError != nil {
+5 -5
View File
@@ -28,8 +28,8 @@ import (
const maxUserPriority = math.MaxInt - 1000 const maxUserPriority = math.MaxInt - 1000
type middlewareBuilder interface { type middlewareChainBuilder interface {
BuildChain(ctx context.Context, names []string) *alice.Chain BuildMiddlewareChain(ctx context.Context, names []string) *alice.Chain
} }
type serviceManager interface { type serviceManager interface {
@@ -42,7 +42,7 @@ type Manager struct {
routerHandlers map[string]http.Handler routerHandlers map[string]http.Handler
serviceManager serviceManager serviceManager serviceManager
observabilityMgr *middleware.ObservabilityMgr observabilityMgr *middleware.ObservabilityMgr
middlewaresBuilder middlewareBuilder middlewaresBuilder middlewareChainBuilder
conf *runtime.Configuration conf *runtime.Configuration
tlsManager *tls.Manager tlsManager *tls.Manager
parser httpmuxer.SyntaxParser parser httpmuxer.SyntaxParser
@@ -51,7 +51,7 @@ type Manager struct {
// NewManager creates a new Manager. // NewManager creates a new Manager.
func NewManager(conf *runtime.Configuration, func NewManager(conf *runtime.Configuration,
serviceManager serviceManager, serviceManager serviceManager,
middlewaresBuilder middlewareBuilder, middlewaresBuilder middlewareChainBuilder,
observabilityMgr *middleware.ObservabilityMgr, observabilityMgr *middleware.ObservabilityMgr,
tlsManager *tls.Manager, tlsManager *tls.Manager,
parser httpmuxer.SyntaxParser, parser httpmuxer.SyntaxParser,
@@ -372,7 +372,7 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
}) })
} }
mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares) mHandler := m.middlewaresBuilder.BuildMiddlewareChain(ctx, router.Middlewares)
return chain.Extend(*mHandler).Then(nextHandler) return chain.Extend(*mHandler).Then(nextHandler)
} }
+1 -1
View File
@@ -2000,7 +2000,7 @@ func (m *mockServiceManager) LaunchHealthCheck(_ context.Context) {}
type mockMiddlewareBuilder struct{} type mockMiddlewareBuilder struct{}
func (m *mockMiddlewareBuilder) BuildChain(_ context.Context, _ []string) *alice.Chain { func (m *mockMiddlewareBuilder) BuildMiddlewareChain(_ context.Context, _ []string) *alice.Chain {
chain := alice.New() chain := alice.New()
return &chain return &chain
} }
+2
View File
@@ -104,6 +104,8 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
serviceManager.SetMiddlewareChainBuilder(middlewaresBuilder)
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager, f.parser) routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager, f.parser)
routerManager.ParseRouterTree() routerManager.ParseRouterTree()
+26 -3
View File
@@ -48,6 +48,10 @@ type ServiceBuilder interface {
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
} }
type middlewareChainBuilder interface {
BuildMiddlewareChain(ctx context.Context, middlewares []string) *alice.Chain
}
// Manager The service manager. // Manager The service manager.
type Manager struct { type Manager struct {
routinePool *safe.Pool routinePool *safe.Pool
@@ -60,6 +64,7 @@ type Manager struct {
configs map[string]*runtime.ServiceInfo configs map[string]*runtime.ServiceInfo
healthCheckers map[string]*healthcheck.ServiceHealthChecker healthCheckers map[string]*healthcheck.ServiceHealthChecker
rand *rand.Rand // For the initial shuffling of load-balancers. rand *rand.Rand // For the initial shuffling of load-balancers.
middlewareChainBuilder middlewareChainBuilder
} }
// NewManager creates a new Manager. // NewManager creates a new Manager.
@@ -77,6 +82,11 @@ func NewManager(configs map[string]*runtime.ServiceInfo, observabilityMgr *middl
} }
} }
// SetMiddlewareChainBuilder sets the MiddlewareChainBuilder.
func (m *Manager) SetMiddlewareChainBuilder(middlewareChainBuilder middlewareChainBuilder) {
m.middlewareChainBuilder = middlewareChainBuilder
}
// BuildHTTP Creates a http.Handler for a service configuration. // BuildHTTP Creates a http.Handler for a service configuration.
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) { func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
serviceName = provider.GetQualifiedName(rootCtx, serviceName) serviceName = provider.GetQualifiedName(rootCtx, serviceName)
@@ -113,7 +123,7 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
value := reflect.ValueOf(*conf.Service) value := reflect.ValueOf(*conf.Service)
var count int var count int
for i := range value.NumField() { for i := range value.NumField() {
if !value.Field(i).IsNil() { if value.Type().Field(i).Name != "Middlewares" && !value.Field(i).IsNil() {
count++ count++
} }
} }
@@ -173,9 +183,22 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
return nil, sErr return nil, sErr
} }
m.services[serviceName] = lb if len(conf.Middlewares) > 0 {
if m.middlewareChainBuilder == nil {
// This should happen only in tests.
return nil, errors.New("chain builder not defined")
}
chain := m.middlewareChainBuilder.BuildMiddlewareChain(ctx, conf.Middlewares)
var err error
lb, err = chain.Then(lb)
if err != nil {
conf.AddError(err, true)
return nil, err
}
}
return lb, nil m.services[serviceName] = lb
return m.services[serviceName], nil
} }
// LaunchHealthCheck launches the health checks. // LaunchHealthCheck launches the health checks.