mirror of
https://github.com/traefik/traefik
synced 2026-02-03 05:20:32 +00:00
Services middleware and Gateway API filters on HTTP backends
This commit is contained in:
@@ -103,7 +103,7 @@ test-integration:
|
||||
#? test-gateway-api-conformance: Run the Gateway API conformance tests
|
||||
test-gateway-api-conformance: build-image-dirty
|
||||
# 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
|
||||
#? test-knative-conformance: Run the Knative conformance tests
|
||||
|
||||
@@ -222,6 +222,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -1232,6 +1251,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -2974,6 +3012,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -3211,6 +3268,24 @@ spec:
|
||||
Default value is -1, which means unlimited size.
|
||||
format: int64
|
||||
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:
|
||||
description: |-
|
||||
MirrorBody defines whether the body of the request should be mirrored.
|
||||
@@ -3298,6 +3373,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -3686,6 +3780,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
|
||||
@@ -223,6 +223,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
|
||||
@@ -387,6 +387,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
|
||||
@@ -131,6 +131,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -368,6 +387,24 @@ spec:
|
||||
Default value is -1, which means unlimited size.
|
||||
format: int64
|
||||
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:
|
||||
description: |-
|
||||
MirrorBody defines whether the body of the request should be mirrored.
|
||||
@@ -455,6 +492,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -843,6 +899,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
|
||||
@@ -112,31 +112,33 @@
|
||||
[http.services.Service03.loadBalancer.responseForwarding]
|
||||
flushInterval = "42s"
|
||||
[http.services.Service04]
|
||||
[http.services.Service04.mirroring]
|
||||
middlewares = ["foobar", "foobar"]
|
||||
[http.services.Service05]
|
||||
[http.services.Service05.mirroring]
|
||||
service = "foobar"
|
||||
mirrorBody = true
|
||||
maxBodySize = 42
|
||||
|
||||
[[http.services.Service04.mirroring.mirrors]]
|
||||
[[http.services.Service05.mirroring.mirrors]]
|
||||
name = "foobar"
|
||||
percent = 42
|
||||
|
||||
[[http.services.Service04.mirroring.mirrors]]
|
||||
[[http.services.Service05.mirroring.mirrors]]
|
||||
name = "foobar"
|
||||
percent = 42
|
||||
[http.services.Service04.mirroring.healthCheck]
|
||||
[http.services.Service05]
|
||||
[http.services.Service05.weighted]
|
||||
[http.services.Service05.mirroring.healthCheck]
|
||||
[http.services.Service06]
|
||||
[http.services.Service06.weighted]
|
||||
|
||||
[[http.services.Service05.weighted.services]]
|
||||
[[http.services.Service06.weighted.services]]
|
||||
name = "foobar"
|
||||
weight = 42
|
||||
|
||||
[[http.services.Service05.weighted.services]]
|
||||
[[http.services.Service06.weighted.services]]
|
||||
name = "foobar"
|
||||
weight = 42
|
||||
[http.services.Service05.weighted.sticky]
|
||||
[http.services.Service05.weighted.sticky.cookie]
|
||||
[http.services.Service06.weighted.sticky]
|
||||
[http.services.Service06.weighted.sticky.cookie]
|
||||
name = "foobar"
|
||||
secure = true
|
||||
httpOnly = true
|
||||
@@ -144,7 +146,7 @@
|
||||
maxAge = 42
|
||||
path = "foobar"
|
||||
domain = "foobar"
|
||||
[http.services.Service05.weighted.healthCheck]
|
||||
[http.services.Service06.weighted.healthCheck]
|
||||
[http.middlewares]
|
||||
[http.middlewares.Middleware01]
|
||||
[http.middlewares.Middleware01.addPrefix]
|
||||
|
||||
@@ -120,6 +120,10 @@ http:
|
||||
flushInterval: 42s
|
||||
serversTransport: foobar
|
||||
Service04:
|
||||
middlewares:
|
||||
- foobar
|
||||
- foobar
|
||||
Service05:
|
||||
mirroring:
|
||||
service: foobar
|
||||
mirrorBody: true
|
||||
@@ -130,7 +134,7 @@ http:
|
||||
- name: foobar
|
||||
percent: 42
|
||||
healthCheck: {}
|
||||
Service05:
|
||||
Service06:
|
||||
weighted:
|
||||
services:
|
||||
- name: foobar
|
||||
|
||||
@@ -223,6 +223,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -1233,6 +1252,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -2975,6 +3013,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -3212,6 +3269,24 @@ spec:
|
||||
Default value is -1, which means unlimited size.
|
||||
format: int64
|
||||
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:
|
||||
description: |-
|
||||
MirrorBody defines whether the body of the request should be mirrored.
|
||||
@@ -3299,6 +3374,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
Name defines the name of the referenced Kubernetes Service or TraefikService.
|
||||
@@ -3687,6 +3781,25 @@ spec:
|
||||
- Service
|
||||
- TraefikService
|
||||
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:
|
||||
description: |-
|
||||
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 }}"
|
||||
+3
-3
@@ -8,7 +8,7 @@ implementation:
|
||||
organization: traefik
|
||||
project: traefik
|
||||
url: https://traefik.io/
|
||||
version: v3.6
|
||||
version: v3.7
|
||||
kind: ConformanceReport
|
||||
mode: default
|
||||
profiles:
|
||||
@@ -30,12 +30,13 @@ profiles:
|
||||
result: success
|
||||
statistics:
|
||||
Failed: 0
|
||||
Passed: 13
|
||||
Passed: 15
|
||||
Skipped: 0
|
||||
supportedFeatures:
|
||||
- GatewayPort8080
|
||||
- HTTPRouteBackendProtocolH2C
|
||||
- HTTPRouteBackendProtocolWebSocket
|
||||
- HTTPRouteBackendRequestHeaderModification
|
||||
- HTTPRouteDestinationPortMatching
|
||||
- HTTPRouteHostRewrite
|
||||
- HTTPRouteMethodMatching
|
||||
@@ -50,7 +51,6 @@ profiles:
|
||||
- GatewayHTTPListenerIsolation
|
||||
- GatewayInfrastructurePropagation
|
||||
- GatewayStaticAddresses
|
||||
- HTTPRouteBackendRequestHeaderModification
|
||||
- HTTPRouteBackendTimeout
|
||||
- HTTPRouteCORS
|
||||
- HTTPRouteNamedRouteRule
|
||||
@@ -2364,3 +2364,37 @@ func (s *SimpleSuite) TestEncodedCharactersDifferentEntryPoints() {
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ type Model struct {
|
||||
|
||||
// Service holds a service configuration (can only be of one type at the same time).
|
||||
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"`
|
||||
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"`
|
||||
|
||||
@@ -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.
|
||||
func (in *Service) DeepCopyInto(out *Service) {
|
||||
*out = *in
|
||||
if in.Middlewares != nil {
|
||||
in, out := &in.Middlewares, &out.Middlewares
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.LoadBalancer != nil {
|
||||
in, out := &in.LoadBalancer, &out.LoadBalancer
|
||||
*out = new(ServersLoadBalancer)
|
||||
|
||||
@@ -13,14 +13,14 @@ const (
|
||||
typeName = "Chain"
|
||||
)
|
||||
|
||||
type chainBuilder interface {
|
||||
BuildChain(ctx context.Context, middlewares []string) *alice.Chain
|
||||
type middlewareChainBuilder interface {
|
||||
BuildMiddlewareChain(ctx context.Context, middlewares []string) *alice.Chain
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
middlewareChain := builder.BuildChain(ctx, config.Middlewares)
|
||||
middlewareChain := builder.BuildMiddlewareChain(ctx, config.Middlewares)
|
||||
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
|
||||
+14
@@ -37,6 +37,7 @@ type LoadBalancerSpecApplyConfiguration struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Kind *string `json:"kind,omitempty"`
|
||||
Namespace *string `json:"namespace,omitempty"`
|
||||
Middlewares []MiddlewareRefApplyConfiguration `json:"middlewares,omitempty"`
|
||||
Sticky *dynamic.Sticky `json:"sticky,omitempty"`
|
||||
Port *intstr.IntOrString `json:"port,omitempty"`
|
||||
Scheme *string `json:"scheme,omitempty"`
|
||||
@@ -81,6 +82,19 @@ func (b *LoadBalancerSpecApplyConfiguration) WithNamespace(value string) *LoadBa
|
||||
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
|
||||
// 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.
|
||||
|
||||
+13
@@ -70,6 +70,19 @@ func (b *MirroringApplyConfiguration) WithNamespace(value string) *MirroringAppl
|
||||
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
|
||||
// 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.
|
||||
|
||||
+13
@@ -68,6 +68,19 @@ func (b *MirrorServiceApplyConfiguration) WithNamespace(value string) *MirrorSer
|
||||
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
|
||||
// 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.
|
||||
|
||||
+13
@@ -67,6 +67,19 @@ func (b *ServiceApplyConfiguration) WithNamespace(value string) *ServiceApplyCon
|
||||
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
|
||||
// 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.
|
||||
|
||||
@@ -266,7 +266,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||
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 {
|
||||
logger.Error().Err(err).Msg("Error while reading error page middleware")
|
||||
continue
|
||||
@@ -645,7 +645,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||
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 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
@@ -663,7 +663,7 @@ func (p *Provider) createErrorPageMiddleware(client Client, namespace string, er
|
||||
allowEmptyServices: p.AllowEmptyServices,
|
||||
}
|
||||
|
||||
balancerServerHTTP, err := cb.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
balancerServerHTTP, err := cb.buildServersLB(ctx, namespace, errorPage.Service.LoadBalancerSpec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||
|
||||
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 {
|
||||
logger.Error().Err(err).Msg("Failed to create middleware keys")
|
||||
continue
|
||||
@@ -172,13 +172,13 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||
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
|
||||
|
||||
for _, mi := range middlewares {
|
||||
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),
|
||||
// if the provider namespace kubernetescrd is used,
|
||||
// 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
|
||||
}
|
||||
|
||||
ns := ingRouteNamespace
|
||||
ns := namespace
|
||||
if len(mi.Namespace) > 0 {
|
||||
if !isNamespaceAllowed(p.AllowCrossNamespace, ingRouteNamespace, mi.Namespace) {
|
||||
return nil, fmt.Errorf("middleware %s/%s is not in the IngressRoute namespace %s", mi.Namespace, mi.Name, ingRouteNamespace)
|
||||
if !isNamespaceAllowed(allowCrossNamespace, namespace, mi.Namespace) {
|
||||
return nil, fmt.Errorf("middleware %s/%s is not in the parent namespace %s", mi.Namespace, mi.Name, namespace)
|
||||
}
|
||||
|
||||
ns = mi.Namespace
|
||||
@@ -333,6 +333,7 @@ func (c configBuilder) buildServicesLB(ctx context.Context, namespace string, tS
|
||||
Sticky: sticky,
|
||||
},
|
||||
}
|
||||
|
||||
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.
|
||||
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.SetDefaults()
|
||||
|
||||
@@ -501,7 +502,16 @@ func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.Load
|
||||
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) {
|
||||
@@ -687,7 +697,7 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin
|
||||
|
||||
switch service.Kind {
|
||||
case "", "Service":
|
||||
serversLB, err := c.buildServersLB(namespace, service)
|
||||
serversLB, err := c.buildServersLB(ctx, namespace, service)
|
||||
if err != nil {
|
||||
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",
|
||||
paths: []string{"with_highest_random_weight.yml"},
|
||||
|
||||
@@ -110,6 +110,8 @@ type LoadBalancerSpec struct {
|
||||
Kind string `json:"kind,omitempty"`
|
||||
// Namespace defines the namespace of the referenced Kubernetes Service or TraefikService.
|
||||
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.
|
||||
// More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/load-balancing/service/#sticky-sessions
|
||||
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.
|
||||
func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
|
||||
*out = *in
|
||||
if in.Middlewares != nil {
|
||||
in, out := &in.Middlewares, &out.Middlewares
|
||||
*out = make([]MiddlewareRef, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Sticky != nil {
|
||||
in, out := &in.Sticky, &out.Sticky
|
||||
*out = new(dynamic.Sticky)
|
||||
|
||||
@@ -44,5 +44,6 @@ func extendedHTTPRouteFeatures() sets.Set[features.Feature] {
|
||||
features.HTTPRouteBackendProtocolH2CFeature,
|
||||
features.HTTPRouteBackendProtocolWebSocketFeature,
|
||||
features.HTTPRouteDestinationPortMatchingFeature,
|
||||
features.HTTPRouteBackendRequestHeaderModificationFeature,
|
||||
)
|
||||
}
|
||||
|
||||
+62
@@ -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
|
||||
@@ -140,6 +140,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
|
||||
|
||||
var err error
|
||||
routerName := makeRouterName(rule, routeKey)
|
||||
// TODO loadMiddlewares errors could change the condition.
|
||||
router.Middlewares, err = p.loadMiddlewares(conf, route.Namespace, routerName, routeRule.Filters, match.Path)
|
||||
switch {
|
||||
case err != nil:
|
||||
@@ -164,7 +165,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
|
||||
|
||||
default:
|
||||
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 {
|
||||
condition = *serviceCondition
|
||||
}
|
||||
@@ -179,7 +180,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
|
||||
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"
|
||||
if _, ok := conf.HTTP.Services[name]; ok {
|
||||
return name, nil
|
||||
@@ -188,7 +189,9 @@ func (p *Provider) loadWRRService(ctx context.Context, listener gatewayListener,
|
||||
var wrr dynamic.WeightedRoundRobin
|
||||
var condition *metav1.Condition
|
||||
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)))
|
||||
if errCondition != nil {
|
||||
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.
|
||||
// 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)
|
||||
|
||||
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 {
|
||||
name, service, err := p.loadHTTPBackendRef(namespace, backendRef)
|
||||
if err != nil {
|
||||
@@ -255,6 +271,7 @@ func (p *Provider) loadService(ctx context.Context, listener gatewayListener, co
|
||||
}
|
||||
|
||||
if service != nil {
|
||||
service.Middlewares = middlewares
|
||||
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.Services[serviceName] = &dynamic.Service{LoadBalancer: lb}
|
||||
conf.HTTP.Services[serviceName] = &dynamic.Service{LoadBalancer: lb, Middlewares: middlewares}
|
||||
|
||||
return serviceName, nil
|
||||
}
|
||||
|
||||
@@ -1917,6 +1917,77 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||
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",
|
||||
paths: []string{"services.yml", "httproute/filter_http_to_https.yml"},
|
||||
@@ -1954,6 +2025,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Services: map[string]*dynamic.Service{
|
||||
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-364ce6ec04c3d49b19c4-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{},
|
||||
|
||||
@@ -46,6 +46,7 @@ type ServiceIng struct {
|
||||
ServersScheme string `json:"serversScheme,omitempty"`
|
||||
ServersTransport string `json:"serversTransport,omitempty"`
|
||||
PassHostHeader *bool `json:"passHostHeader"`
|
||||
Middlewares []string `json:"middlewares,omitempty"`
|
||||
Sticky *dynamic.Sticky `json:"sticky,omitempty" label:"allowEmpty"`
|
||||
NativeLB *bool `json:"nativeLB,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",
|
||||
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 {
|
||||
svc.LoadBalancer.Sticky = svcConfig.Service.Sticky
|
||||
svc.Middlewares = svcConfig.Service.Middlewares
|
||||
|
||||
if svcConfig.Service.PassHostHeader != nil {
|
||||
svc.LoadBalancer.PassHostHeader = svcConfig.Service.PassHostHeader
|
||||
|
||||
@@ -56,8 +56,8 @@ func NewBuilder(configs map[string]*runtime.MiddlewareInfo, serviceBuilder servi
|
||||
return &Builder{configs: configs, serviceBuilder: serviceBuilder, pluginBuilder: pluginBuilder}
|
||||
}
|
||||
|
||||
// BuildChain creates a middleware chain.
|
||||
func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.Chain {
|
||||
// BuildMiddlewareChain creates a middleware chain.
|
||||
func (b *Builder) BuildMiddlewareChain(ctx context.Context, middlewares []string) *alice.Chain {
|
||||
chain := alice.New()
|
||||
for _, name := range middlewares {
|
||||
middlewareName := provider.GetQualifiedName(ctx, name)
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestBuilder_BuildChainNilConfig(t *testing.T) {
|
||||
}
|
||||
middlewaresBuilder := NewBuilder(testConfig, nil, nil)
|
||||
|
||||
chain := middlewaresBuilder.BuildChain(t.Context(), []string{"empty"})
|
||||
chain := middlewaresBuilder.BuildMiddlewareChain(t.Context(), []string{"empty"})
|
||||
_, err := chain.Then(nil)
|
||||
require.Error(t, err)
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func TestBuilder_BuildChainNonExistentChain(t *testing.T) {
|
||||
}
|
||||
middlewaresBuilder := NewBuilder(testConfig, nil, nil)
|
||||
|
||||
chain := middlewaresBuilder.BuildChain(t.Context(), []string{"empty"})
|
||||
chain := middlewaresBuilder.BuildMiddlewareChain(t.Context(), []string{"empty"})
|
||||
_, err := chain.Then(nil)
|
||||
require.Error(t, err)
|
||||
}
|
||||
@@ -271,7 +271,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
|
||||
})
|
||||
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) }))
|
||||
if test.expectedError != nil {
|
||||
|
||||
@@ -28,8 +28,8 @@ import (
|
||||
|
||||
const maxUserPriority = math.MaxInt - 1000
|
||||
|
||||
type middlewareBuilder interface {
|
||||
BuildChain(ctx context.Context, names []string) *alice.Chain
|
||||
type middlewareChainBuilder interface {
|
||||
BuildMiddlewareChain(ctx context.Context, names []string) *alice.Chain
|
||||
}
|
||||
|
||||
type serviceManager interface {
|
||||
@@ -42,7 +42,7 @@ type Manager struct {
|
||||
routerHandlers map[string]http.Handler
|
||||
serviceManager serviceManager
|
||||
observabilityMgr *middleware.ObservabilityMgr
|
||||
middlewaresBuilder middlewareBuilder
|
||||
middlewaresBuilder middlewareChainBuilder
|
||||
conf *runtime.Configuration
|
||||
tlsManager *tls.Manager
|
||||
parser httpmuxer.SyntaxParser
|
||||
@@ -51,7 +51,7 @@ type Manager struct {
|
||||
// NewManager creates a new Manager.
|
||||
func NewManager(conf *runtime.Configuration,
|
||||
serviceManager serviceManager,
|
||||
middlewaresBuilder middlewareBuilder,
|
||||
middlewaresBuilder middlewareChainBuilder,
|
||||
observabilityMgr *middleware.ObservabilityMgr,
|
||||
tlsManager *tls.Manager,
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -2000,7 +2000,7 @@ func (m *mockServiceManager) LaunchHealthCheck(_ context.Context) {}
|
||||
|
||||
type mockMiddlewareBuilder struct{}
|
||||
|
||||
func (m *mockMiddlewareBuilder) BuildChain(_ context.Context, _ []string) *alice.Chain {
|
||||
func (m *mockMiddlewareBuilder) BuildMiddlewareChain(_ context.Context, _ []string) *alice.Chain {
|
||||
chain := alice.New()
|
||||
return &chain
|
||||
}
|
||||
|
||||
@@ -104,6 +104,8 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
|
||||
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
|
||||
|
||||
serviceManager.SetMiddlewareChainBuilder(middlewaresBuilder)
|
||||
|
||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager, f.parser)
|
||||
|
||||
routerManager.ParseRouterTree()
|
||||
|
||||
@@ -48,6 +48,10 @@ type ServiceBuilder interface {
|
||||
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
||||
}
|
||||
|
||||
type middlewareChainBuilder interface {
|
||||
BuildMiddlewareChain(ctx context.Context, middlewares []string) *alice.Chain
|
||||
}
|
||||
|
||||
// Manager The service manager.
|
||||
type Manager struct {
|
||||
routinePool *safe.Pool
|
||||
@@ -56,10 +60,11 @@ type Manager struct {
|
||||
proxyBuilder ProxyBuilder
|
||||
serviceBuilders []ServiceBuilder
|
||||
|
||||
services map[string]http.Handler
|
||||
configs map[string]*runtime.ServiceInfo
|
||||
healthCheckers map[string]*healthcheck.ServiceHealthChecker
|
||||
rand *rand.Rand // For the initial shuffling of load-balancers.
|
||||
services map[string]http.Handler
|
||||
configs map[string]*runtime.ServiceInfo
|
||||
healthCheckers map[string]*healthcheck.ServiceHealthChecker
|
||||
rand *rand.Rand // For the initial shuffling of load-balancers.
|
||||
middlewareChainBuilder middlewareChainBuilder
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
|
||||
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)
|
||||
var count int
|
||||
for i := range value.NumField() {
|
||||
if !value.Field(i).IsNil() {
|
||||
if value.Type().Field(i).Name != "Middlewares" && !value.Field(i).IsNil() {
|
||||
count++
|
||||
}
|
||||
}
|
||||
@@ -173,9 +183,22 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user