Merge branch 'master' of github.com:traefik/traefik
All checks were successful
Build & Push / build-and-push (push) Successful in 12m7s

Signed-off-by: baalajimaestro <baalajimaestro@ptr.moe>
This commit is contained in:
baalajimaestro 2024-10-15 16:21:39 +05:30
commit ff55677acd
Signed by: baalajimaestro
GPG key ID: B5B69626E67EE82A
40 changed files with 2066 additions and 1647 deletions

View file

@ -25,7 +25,7 @@ global_job_config:
- export "PATH=${GOPATH}/bin:${PATH}"
- mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin"
- export GOPROXY=https://proxy.golang.org,direct
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.60.3
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.61.0
- curl -sSfL https://gist.githubusercontent.com/traefiker/6d7ac019c11d011e4f131bb2cca8900e/raw/goreleaser.sh | bash -s -- -b "${GOPATH}/bin"
- checkout
- cache restore traefik-$(checksum go.sum)

View file

@ -1,3 +1,44 @@
## [v3.2.0-rc2](https://github.com/traefik/traefik/tree/v3.2.0-rc2) (2024-10-09)
[All Commits](https://github.com/traefik/traefik/compare/v3.2.0-rc1...v3.2.0-rc2)
**Enhancements:**
- **[k8s,k8s/gatewayapi]** Bump sigs.k8s.io/gateway-api to v1.2.0 ([#11167](https://github.com/traefik/traefik/pull/11167) by [rtribotte](https://github.com/rtribotte))
**Bug fixes:**
- **[k8s,k8s/gatewayapi]** Support http and https appProtocol for Kubernetes Service ([#11176](https://github.com/traefik/traefik/pull/11176) by [WillDaSilva](https://github.com/WillDaSilva))
- **[k8s,k8s/gatewayapi]** Avoid updating Accepted status for routes matching no Gateways ([#11170](https://github.com/traefik/traefik/pull/11170) by [rtribotte](https://github.com/rtribotte))
- **[k8s,k8s/gatewayapi]** Do not update gateway status when not selected by a gateway class ([#11169](https://github.com/traefik/traefik/pull/11169) by [kevinpollet](https://github.com/kevinpollet))
**Documentation:**
- Detail CRD update with v3.2 in the migration guide ([#11164](https://github.com/traefik/traefik/pull/11164) by [mloiseleur](https://github.com/mloiseleur))
**Misc:**
- Merge branch v3.1 into v3.2 ([#11181](https://github.com/traefik/traefik/pull/11181) by [kevinpollet](https://github.com/kevinpollet))
## [v3.1.6](https://github.com/traefik/traefik/tree/v3.1.6) (2024-10-09)
[All Commits](https://github.com/traefik/traefik/compare/v3.1.5...v3.1.6)
**Bug fixes:**
- **[middleware]** Reuse compression writers ([#11168](https://github.com/traefik/traefik/pull/11168) by [michelheusschen](https://github.com/michelheusschen))
- **[middleware]** Use correct default weight in Accept-Encoding ([#11084](https://github.com/traefik/traefik/pull/11084) by [michelheusschen](https://github.com/michelheusschen))
- **[plugins]** Close wasm middleware to prevent memory leak ([#11151](https://github.com/traefik/traefik/pull/11151) by [ttys3](https://github.com/ttys3))
**Misc:**
- Merge branch v2.11 into v3.1 ([#11179](https://github.com/traefik/traefik/pull/11179) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v2.11 into v3.1 ([#11174](https://github.com/traefik/traefik/pull/11174) by [mmatur](https://github.com/mmatur))
## [v2.11.12](https://github.com/traefik/traefik/tree/v2.11.12) (2024-10-09)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.11...v2.11.12)
**Bug fixes:**
- **[middleware]** Bump github.com/klauspost/compress to dbd6c381492a ([#11162](https://github.com/traefik/traefik/pull/11162) by [kevinpollet](https://github.com/kevinpollet))
- **[webui]** Upgrade to node 22.9 and yarn lock to fix vulnerabilities ([#11173](https://github.com/traefik/traefik/pull/11173) by [kevinpollet](https://github.com/kevinpollet))
- **[webui]** Adopt a layout for the large amount of entrypoint port numbers ([#11157](https://github.com/traefik/traefik/pull/11157) by [framebassman](https://github.com/framebassman))
**Documentation:**
- **[accesslogs]** Clarify that only header fields may be redacted in access-logs ([#11139](https://github.com/traefik/traefik/pull/11139) by [mattbnz](https://github.com/mattbnz))
- Update business callout ([#11172](https://github.com/traefik/traefik/pull/11172) by [tomatokoolaid](https://github.com/tomatokoolaid))
## [v3.2.0-rc1](https://github.com/traefik/traefik/tree/v3.2.0-rc1) (2024-10-02)
[All Commits](https://github.com/traefik/traefik/compare/v3.1.0-rc1...v3.2.0-rc1)
@ -32,6 +73,8 @@
**Bug fixes:**
- **[k8s/ingress,k8s]** Disable IngressClass lookup when disableClusterScopeResources is enabled ([#11111](https://github.com/traefik/traefik/pull/11111) by [jnoordsij](https://github.com/jnoordsij))
- **[server]** Rework condition to not log on timeout ([#11132](https://github.com/traefik/traefik/pull/11132) by [rtribotte](https://github.com/rtribotte))
**Misc:**
- Merge branch v2.11 into v3.1 ([#11149](https://github.com/traefik/traefik/pull/11149) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v2.11 into v3.1 ([#11142](https://github.com/traefik/traefik/pull/11142) by [rtribotte](https://github.com/rtribotte))

View file

@ -1,13 +1,10 @@
SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/')
TAG_NAME := $(shell git tag -l --contains HEAD)
TAG_NAME := $(shell git describe --abbrev=0 --tags --exact-match)
SHA := $(shell git rev-parse HEAD)
VERSION_GIT := $(if $(TAG_NAME),$(TAG_NAME),$(SHA))
VERSION := $(if $(VERSION),$(VERSION),$(VERSION_GIT))
GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null))
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
BIN_NAME := traefik
CODENAME ?= cheddar

View file

@ -5,6 +5,6 @@
Add API Gateway or API Management capabilities seamlessly to your existing Traefik deployments.
No rip and replace. No learning curve.
- [Explore our API Gateway](https://traefik.io/traefik-hub-api-gateway/)
- [Explore our API Gateway](https://traefik.io/traefik-hub-api-gateway/) ([Watch the Demo Video](https://info.traefik.io/watch-traefik-api-gw-demo?cta=doc))
- [Explore our API Management](https://traefik.io/traefik-hub/)
- [Get 24/7/365 Commercial Support for Traefik OSS](https://info.traefik.io/request-commercial-support)

View file

@ -78,6 +78,17 @@ Please use the `disableClusterScopeResources` option instead to avoid cluster sc
## v3.1 to v3.2
### Kubernetes CRD Provider
Starting with v3.2, the CRDs has been updated on [TraefikService](../routing/services#mirroring-service) (PR [#11032](https://github.com/traefik/traefik/pull/11032)) and on [RateLimit](../middlewares/http/ratelimit) & [InFlightReq](../middlewares/http/inflightreq) middlewares (PR [#9747](https://github.com/traefik/traefik/pull/9747)).
This update adds only new optional fields.
CRDs can be updated with this command:
```shell
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.2/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
```
### Kubernetes Gateway Provider Standard Channel
Starting with v3.2, the Kubernetes Gateway Provider now supports [GRPCRoute](https://gateway-api.sigs.k8s.io/api-types/grpcroute/).

View file

@ -158,7 +158,8 @@ Each field can be set to:
- `keep` to keep the value
- `drop` to drop the value
- `redact` to replace the value with "redacted"
Header fields may also optionally be set to `redact` to replace the value with "REDACTED".
The `defaultMode` for `fields.names` is `keep`.

View file

@ -8,11 +8,11 @@ description: "Learn how to use the Kubernetes Gateway API as a provider for conf
The Kubernetes Gateway provider is a Traefik implementation of the [Gateway API](https://gateway-api.sigs.k8s.io/)
specification from the Kubernetes Special Interest Groups (SIGs).
This provider supports Standard version [v1.1.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.1.0) of the Gateway API specification.
This provider supports Standard version [v1.2.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.2.0) of the Gateway API specification.
It fully supports all HTTP core and some extended features, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels).
For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.1.0/traefik-traefik).
For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.2.0/traefik-traefik).
## Requirements

View file

@ -8,11 +8,11 @@ description: "The Kubernetes Gateway API can be used as a provider for routing a
When using the Kubernetes Gateway API provider, Traefik leverages the Gateway API Custom Resource Definitions (CRDs) to obtain its routing configuration.
For detailed information on the Gateway API concepts and resources, refer to the official [documentation](https://gateway-api.sigs.k8s.io/).
The Kubernetes Gateway API provider supports version [v1.1.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.1.0) of the specification.
The Kubernetes Gateway API provider supports version [v1.2.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.2.0) of the specification.
It fully supports all `HTTPRoute` core and some extended features, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels).
It fully supports all `HTTPRoute` core and some extended features, like `GRPCRoute`, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels).
For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.1.0/traefik-traefik).
For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.2.0/traefik-traefik).
## Deploying a Gateway

13
go.mod
View file

@ -31,10 +31,10 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/nomad/api v0.0.0-20240122103822-8a4bd61caf74 // No tag on the repo.
github.com/http-wasm/http-wasm-host-go v0.6.0
github.com/http-wasm/http-wasm-host-go v0.7.0
github.com/influxdata/influxdb-client-go/v2 v2.7.0
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // No tag on the repo.
github.com/klauspost/compress v1.17.11-0.20240927175842-8e14b1b5a913 // Required to have the content-type fix: https://github.com/klauspost/compress/pull/1011
github.com/klauspost/compress v1.17.11-0.20241004063537-dbd6c381492a // Required to have the content-type fix: https://github.com/klauspost/compress/pull/1013
github.com/kvtools/consul v1.0.2
github.com/kvtools/etcdv3 v1.0.2
github.com/kvtools/redis v1.1.0
@ -61,7 +61,7 @@ require (
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 // No tag on the repo.
github.com/testcontainers/testcontainers-go v0.32.0
github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0
github.com/tetratelabs/wazero v1.7.2
github.com/tetratelabs/wazero v1.8.0
github.com/tidwall/gjson v1.17.0
github.com/traefik/grpc-web v0.16.0
github.com/traefik/paerser v0.2.1
@ -86,6 +86,7 @@ require (
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // No tag on the repo.
golang.org/x/mod v0.21.0
golang.org/x/net v0.29.0
golang.org/x/sync v0.8.0
golang.org/x/sys v0.25.0
golang.org/x/text v0.18.0
golang.org/x/time v0.5.0
@ -100,7 +101,7 @@ require (
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // No tag on the repo.
mvdan.cc/xurls/v2 v2.5.0
sigs.k8s.io/controller-runtime v0.18.0
sigs.k8s.io/gateway-api v1.2.0-rc2
sigs.k8s.io/gateway-api v1.2.0
sigs.k8s.io/yaml v1.4.0
)
@ -343,7 +344,6 @@ require (
golang.org/x/arch v0.4.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/term v0.24.0 // indirect
google.golang.org/api v0.172.0 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
@ -374,6 +374,3 @@ replace (
// ambiguous import: found package github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http in multiple modules
// tencentcloud uses monorepo with multimodule but the go.mod files are incomplete.
exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible
// Replace to handle new wasmexport in official go and wazergo for http calls.
replace github.com/http-wasm/http-wasm-host-go => github.com/traefik/http-wasm-host-go v0.0.0-20240618100324-3c53dcaa1a70

16
go.sum
View file

@ -551,6 +551,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/http-wasm/http-wasm-host-go v0.7.0 h1:+1KrRyOO6tWiDB24QrtSYyDmzFLBBs3jioKaUT0mq1c=
github.com/http-wasm/http-wasm-host-go v0.7.0/go.mod h1:adXKcLmL7yuavH/e0kBAp7b3TgAHTo/enCduyN5bXGM=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
@ -597,8 +599,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.17.11-0.20240927175842-8e14b1b5a913 h1:7s7Xd7zVElAw1qh/eh+tXDNfDNXXj38Tpq54eeG6/BM=
github.com/klauspost/compress v1.17.11-0.20240927175842-8e14b1b5a913/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.17.11-0.20241004063537-dbd6c381492a h1:cwHOqPB4H4iQq8177kf2SxpjNbcjJ2m3lNwKIe28Hqg=
github.com/klauspost/compress v1.17.11-0.20241004063537-dbd6c381492a/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@ -995,8 +997,8 @@ github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHP
github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E=
github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0 h1:Z3DTMveNUqeGJZ+CXZhpvI7OF1BS71Ywi3SwoXLZ4Lc=
github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0/go.mod h1:SYp1WtvNc3n/cg5atO6LvaOd2aqkQYMSDCcWPOUdaZg=
github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc=
github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -1011,8 +1013,6 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltascY=
github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4=
github.com/traefik/http-wasm-host-go v0.0.0-20240618100324-3c53dcaa1a70 h1:I+oBnV0orhmasb87yaX54tOAfqrV9+yKoQ1Cum5mq8w=
github.com/traefik/http-wasm-host-go v0.0.0-20240618100324-3c53dcaa1a70/go.mod h1:zQB3w+df4hryDEqBorGyA1DwPJ86LfKIASNLFuj6CuI=
github.com/traefik/paerser v0.2.1 h1:LFgeak1NmjEHF53c9ENdXdL1UMkF/lD5t+7Evsz4hH4=
github.com/traefik/paerser v0.2.1/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
@ -1542,8 +1542,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/controller-runtime v0.18.0 h1:Z7jKuX784TQSUL1TIyeuF7j8KXZ4RtSX0YgtjKcSTME=
sigs.k8s.io/controller-runtime v0.18.0/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw=
sigs.k8s.io/gateway-api v1.2.0-rc2 h1:v7V7JzaBuzwOLWWyyqlkqiqBi3ANBuZGV+uyyKzwmE8=
sigs.k8s.io/gateway-api v1.2.0-rc2/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0=
sigs.k8s.io/gateway-api v1.2.0 h1:LrToiFwtqKTKZcZtoQPTuo3FxhrrhTgzQG0Te+YGSo8=
sigs.k8s.io/gateway-api v1.2.0/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=

View file

@ -1,7 +1,7 @@
apiVersion: gateway.networking.k8s.io/v1
date: '-'
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.2.0-rc2
gatewayAPIVersion: v1.2.0
implementation:
contact:
- '@traefik/maintainers'

View file

@ -23,8 +23,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
labels:
@ -524,8 +524,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
labels:
@ -1153,8 +1153,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
name: gatewayclasses.gateway.networking.k8s.io
@ -1673,8 +1673,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
name: gateways.gateway.networking.k8s.io
@ -4089,8 +4089,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
name: grpcroutes.gateway.networking.k8s.io
@ -6327,8 +6327,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
name: httproutes.gateway.networking.k8s.io
@ -12489,8 +12489,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
name: referencegrants.gateway.networking.k8s.io
@ -12682,8 +12682,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
name: tcproutes.gateway.networking.k8s.io
@ -13427,8 +13427,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
name: tlsroutes.gateway.networking.k8s.io
@ -14235,8 +14235,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997
gateway.networking.k8s.io/bundle-version: v1.2.0-rc2
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328
gateway.networking.k8s.io/bundle-version: v1.2.0
gateway.networking.k8s.io/channel: experimental
creationTimestamp: null
name: udproutes.gateway.networking.k8s.io

View file

@ -90,7 +90,7 @@ func (s *K8sConformanceSuite) SetupSuite() {
s.k3sContainer, err = k3s.Run(ctx,
k3sImage,
k3s.WithManifest("./fixtures/k8s-conformance/00-experimental-v1.2.0-rc2.yml"),
k3s.WithManifest("./fixtures/k8s-conformance/00-experimental-v1.2.0.yml"),
k3s.WithManifest("./fixtures/k8s-conformance/01-rbac.yml"),
k3s.WithManifest("./fixtures/k8s-conformance/02-traefik.yml"),
network.WithNetwork(nil, s.network),

View file

@ -13,6 +13,7 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/singleflight"
)
const (
@ -26,6 +27,9 @@ type basicAuth struct {
headerField string
removeHeader bool
name string
checkSecret func(password, secret string) bool
singleflightGroup *singleflight.Group
}
// NewBasic creates a basicAuth middleware.
@ -38,11 +42,13 @@ func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAu
}
ba := &basicAuth{
next: next,
users: users,
headerField: authConfig.HeaderField,
removeHeader: authConfig.RemoveHeader,
name: name,
next: next,
users: users,
headerField: authConfig.HeaderField,
removeHeader: authConfig.RemoveHeader,
name: name,
checkSecret: goauth.CheckSecret,
singleflightGroup: new(singleflight.Group),
}
realm := defaultRealm
@ -64,10 +70,7 @@ func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
user, password, ok := req.BasicAuth()
if ok {
secret := b.auth.Secrets(user, b.auth.Realm)
if secret == "" || !goauth.CheckSecret(password, secret) {
ok = false
}
ok = b.checkPassword(user, password)
}
logData := accesslog.GetLogData(req)
@ -97,6 +100,20 @@ func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
b.next.ServeHTTP(rw, req)
}
func (b *basicAuth) checkPassword(user, password string) bool {
secret := b.auth.Secrets(user, b.auth.Realm)
if secret == "" {
return false
}
key := password + secret
match, _, _ := b.singleflightGroup.Do(key, func() (any, error) {
return b.checkSecret(password, secret), nil
})
return match.(bool)
}
func (b *basicAuth) secretBasic(user, realm string) string {
if secret, ok := b.users[user]; ok {
return secret

View file

@ -7,7 +7,9 @@ import (
"net/http"
"net/http/httptest"
"os"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -167,6 +169,50 @@ func TestBasicAuthHeaderPresent(t *testing.T) {
assert.Equal(t, "traefik\n", string(body))
}
func TestBasicAuthConcurrentHashOnce(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
auth := dynamic.BasicAuth{
Users: []string{"test:$2a$04$.8sTYfcxbSplCtoxt5TdJOgpBYkarKtZYsYfYxQ1edbYRuO1DNi0e"},
}
authMiddleware, err := NewBasic(context.Background(), next, auth, "authName")
require.NoError(t, err)
hashCount := 0
ba := authMiddleware.(*basicAuth)
ba.checkSecret = func(password, secret string) bool {
hashCount++
// delay to ensure the second request arrives
time.Sleep(time.Millisecond)
return true
}
ts := httptest.NewServer(authMiddleware)
defer ts.Close()
var wg sync.WaitGroup
wg.Add(2)
for range 2 {
go func() {
defer wg.Done()
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()
assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal")
}()
}
wg.Wait()
assert.Equal(t, 1, hashCount)
}
func TestBasicAuthUsersFromFile(t *testing.T) {
testCases := []struct {
desc string

View file

@ -1,6 +1,7 @@
package compress
import (
"cmp"
"slices"
"strconv"
"strings"
@ -19,7 +20,7 @@ const (
type Encoding struct {
Type string
Weight *float64
Weight float64
}
func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, supportedEncodings []string) string {
@ -42,11 +43,11 @@ func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, sup
encoding := encodings[0]
if encoding.Type == identityName && encoding.Weight != nil && *encoding.Weight == 0 {
if encoding.Type == identityName && encoding.Weight == 0 {
return notAcceptable
}
if encoding.Type == wildcardName && encoding.Weight != nil && *encoding.Weight == 0 {
if encoding.Type == wildcardName && encoding.Weight == 0 {
return notAcceptable
}
@ -87,11 +88,13 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin
continue
}
var weight *float64
// If no "q" parameter is present, the default weight is 1.
// https://www.rfc-editor.org/rfc/rfc9110.html#name-quality-values
weight := 1.0
if len(parsed) > 1 && strings.HasPrefix(parsed[1], "q=") {
w, _ := strconv.ParseFloat(strings.TrimPrefix(parsed[1], "q="), 64)
weight = &w
weight = w
hasWeight = true
}
@ -102,41 +105,9 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin
}
}
slices.SortFunc(encodings, compareEncoding)
slices.SortFunc(encodings, func(a, b Encoding) int {
return cmp.Compare(b.Weight, a.Weight)
})
return encodings, hasWeight
}
func compareEncoding(a, b Encoding) int {
lhs, rhs := a.Weight, b.Weight
if lhs == nil && rhs == nil {
return 0
}
if lhs == nil && *rhs == 0 {
return -1
}
if lhs == nil {
return 1
}
if rhs == nil && *lhs == 0 {
return 1
}
if rhs == nil {
return -1
}
if *lhs < *rhs {
return 1
}
if *lhs > *rhs {
return -1
}
return 0
}

View file

@ -87,6 +87,12 @@ func Test_getCompressionEncoding(t *testing.T) {
supportedEncodings: []string{zstdName, brotliName},
expected: brotliName,
},
{
desc: "mixed weight",
acceptEncoding: []string{"gzip, br;q=0.9"},
supportedEncodings: []string{gzipName, brotliName},
expected: gzipName,
},
}
for _, test := range testCases {
@ -116,10 +122,10 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "weight",
values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"},
expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)},
{Type: zstdName, Weight: ptr(0.9)},
{Type: gzipName, Weight: ptr(0.8)},
{Type: wildcardName, Weight: ptr(0.1)},
{Type: brotliName, Weight: 1},
{Type: zstdName, Weight: 0.9},
{Type: gzipName, Weight: 0.8},
{Type: wildcardName, Weight: 0.1},
},
assertWeight: assert.True,
},
@ -128,9 +134,9 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"},
supportedEncodings: []string{brotliName, gzipName},
expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)},
{Type: gzipName, Weight: ptr(0.8)},
{Type: wildcardName, Weight: ptr(0.1)},
{Type: brotliName, Weight: 1},
{Type: gzipName, Weight: 0.8},
{Type: wildcardName, Weight: 0.1},
},
assertWeight: assert.True,
},
@ -138,10 +144,10 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "mixed",
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)},
{Type: zstdName},
{Type: gzipName},
{Type: wildcardName, Weight: ptr[float64](0)},
{Type: zstdName, Weight: 1},
{Type: gzipName, Weight: 1},
{Type: brotliName, Weight: 1},
{Type: wildcardName, Weight: 0},
},
assertWeight: assert.True,
},
@ -150,8 +156,8 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
supportedEncodings: []string{zstdName},
expected: []Encoding{
{Type: zstdName},
{Type: wildcardName, Weight: ptr[float64](0)},
{Type: zstdName, Weight: 1},
{Type: wildcardName, Weight: 0},
},
assertWeight: assert.True,
},
@ -159,10 +165,10 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "no weight",
values: []string{"zstd, gzip, br, *"},
expected: []Encoding{
{Type: zstdName},
{Type: gzipName},
{Type: brotliName},
{Type: wildcardName},
{Type: zstdName, Weight: 1},
{Type: gzipName, Weight: 1},
{Type: brotliName, Weight: 1},
{Type: wildcardName, Weight: 1},
},
assertWeight: assert.False,
},
@ -171,8 +177,8 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"zstd, gzip, br, *"},
supportedEncodings: []string{"gzip"},
expected: []Encoding{
{Type: gzipName},
{Type: wildcardName},
{Type: gzipName, Weight: 1},
{Type: wildcardName, Weight: 1},
},
assertWeight: assert.False,
},
@ -180,9 +186,9 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "weight and identity",
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
expected: []Encoding{
{Type: gzipName, Weight: ptr[float64](1)},
{Type: identityName, Weight: ptr(0.5)},
{Type: wildcardName, Weight: ptr[float64](0)},
{Type: gzipName, Weight: 1},
{Type: identityName, Weight: 0.5},
{Type: wildcardName, Weight: 0},
},
assertWeight: assert.True,
},
@ -191,8 +197,8 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
supportedEncodings: []string{"br"},
expected: []Encoding{
{Type: identityName, Weight: ptr(0.5)},
{Type: wildcardName, Weight: ptr[float64](0)},
{Type: identityName, Weight: 0.5},
{Type: wildcardName, Weight: 0},
},
assertWeight: assert.True,
},
@ -213,7 +219,3 @@ func Test_parseAcceptEncoding(t *testing.T) {
})
}
}
func ptr[T any](t T) *T {
return &t
}

View file

@ -688,39 +688,32 @@ func Test1xxResponses(t *testing.T) {
assert.NotEqualValues(t, body, fakeBody)
}
func BenchmarkCompress(b *testing.B) {
func BenchmarkCompressGzip(b *testing.B) {
runCompressionBenchmark(b, gzipName)
}
func BenchmarkCompressBrotli(b *testing.B) {
runCompressionBenchmark(b, brotliName)
}
func BenchmarkCompressZstandard(b *testing.B) {
runCompressionBenchmark(b, zstdName)
}
func runCompressionBenchmark(b *testing.B, algorithm string) {
b.Helper()
testCases := []struct {
name string
parallel bool
size int
}{
{
name: "2k",
size: 2048,
},
{
name: "20k",
size: 20480,
},
{
name: "100k",
size: 102400,
},
{
name: "2k parallel",
parallel: true,
size: 2048,
},
{
name: "20k parallel",
parallel: true,
size: 20480,
},
{
name: "100k parallel",
parallel: true,
size: 102400,
},
{"2k", false, 2048},
{"20k", false, 20480},
{"100k", false, 102400},
{"2k parallel", true, 2048},
{"20k parallel", true, 20480},
{"100k parallel", true, 102400},
}
for _, test := range testCases {
@ -734,7 +727,7 @@ func BenchmarkCompress(b *testing.B) {
handler, _ := New(context.Background(), next, dynamic.Compress{}, "testing")
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
req.Header.Set("Accept-Encoding", "gzip")
req.Header.Set("Accept-Encoding", algorithm)
b.ReportAllocs()
b.SetBytes(int64(test.size))
@ -742,7 +735,7 @@ func BenchmarkCompress(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
runBenchmark(b, req, handler)
runBenchmark(b, req, handler, algorithm)
}
})
return
@ -750,13 +743,13 @@ func BenchmarkCompress(b *testing.B) {
b.ResetTimer()
for range b.N {
runBenchmark(b, req, handler)
runBenchmark(b, req, handler, algorithm)
}
})
}
}
func runBenchmark(b *testing.B, req *http.Request, handler http.Handler) {
func runBenchmark(b *testing.B, req *http.Request, handler http.Handler, algorithm string) {
b.Helper()
res := httptest.NewRecorder()
@ -765,7 +758,7 @@ func runBenchmark(b *testing.B, req *http.Request, handler http.Handler) {
b.Fatalf("Expected 200 but got %d", code)
}
assert.Equal(b, gzipName, res.Header().Get(contentEncodingHeader))
assert.Equal(b, algorithm, res.Header().Get(contentEncodingHeader))
}
func generateBytes(length int) []byte {

View file

@ -8,6 +8,7 @@ import (
"mime"
"net"
"net/http"
"sync"
"github.com/andybalholm/brotli"
"github.com/klauspost/compress/zstd"
@ -45,6 +46,7 @@ type CompressionHandler struct {
excludedContentTypes []parsedContentType
includedContentTypes []parsedContentType
next http.Handler
writerPool sync.Pool
}
// NewCompressionHandler returns a new compressing handler.
@ -92,7 +94,7 @@ func NewCompressionHandler(cfg Config, next http.Handler) (http.Handler, error)
func (c *CompressionHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add(vary, acceptEncoding)
compressionWriter, err := newCompressionWriter(c.cfg.Algorithm, rw)
compressionWriter, err := c.getCompressionWriter(rw)
if err != nil {
logger := middlewares.GetLogger(r.Context(), c.cfg.MiddlewareName, typeName)
logger.Debug().Msgf("Create compression handler: %v", err)
@ -100,6 +102,7 @@ func (c *CompressionHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request)
rw.WriteHeader(http.StatusInternalServerError)
return
}
defer c.putCompressionWriter(compressionWriter)
responseWriter := &responseWriter{
rw: rw,
@ -130,6 +133,8 @@ type compression interface {
// as it would otherwise send some extra "end of compression" bytes.
// Close also makes sure to flush whatever was left to write from the buffer.
Close() error
// Reset reinitializes the state of the encoder, allowing it to be reused.
Reset(w io.Writer)
}
type compressionWriter struct {
@ -137,6 +142,19 @@ type compressionWriter struct {
alg string
}
func (c *CompressionHandler) getCompressionWriter(rw io.Writer) (*compressionWriter, error) {
if writer, ok := c.writerPool.Get().(*compressionWriter); ok {
writer.compression.Reset(rw)
return writer, nil
}
return newCompressionWriter(c.cfg.Algorithm, rw)
}
func (c *CompressionHandler) putCompressionWriter(writer *compressionWriter) {
writer.Reset(nil)
c.writerPool.Put(writer)
}
func newCompressionWriter(algo string, in io.Writer) (*compressionWriter, error) {
switch algo {
case brotliName:

View file

@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"github.com/http-wasm/http-wasm-host-go/handler"
@ -135,7 +136,19 @@ func (b *wasmMiddlewareBuilder) buildMiddleware(ctx context.Context, next http.H
return nil, nil, fmt.Errorf("creating middleware: %w", err)
}
return mw.NewHandler(ctx, next), applyCtx, nil
h := mw.NewHandler(ctx, next)
// Traefik does not Close the middleware when creating a new instance on a configuration change.
// When the middleware is marked to be GC, we need to close it so the wasm instance is properly closed.
// Reference: https://github.com/traefik/traefik/issues/11119
runtime.SetFinalizer(h, func(_ http.Handler) {
if err := mw.Close(ctx); err != nil {
logger.Err(err).Msg("[wasm] middleware Close failed")
} else {
logger.Debug().Msg("[wasm] middleware Close ok")
}
})
return h, applyCtx, nil
}
// WasmMiddleware is an HTTP handler plugin wrapper.

View file

@ -0,0 +1,51 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: traefik-internal
labels:
name: traefik-internal
spec:
controllerName: traefik.io/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: traefik-internal
namespace: default
spec:
gatewayClassName: traefik-internal
listeners:
- name: http
protocol: HTTP
port: 9080
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: traefik-external
labels:
name: traefik-external
spec:
controllerName: traefik.io/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: traefik-external
namespace: default
spec:
gatewayClassName: traefik-external
listeners:
- name: http
protocol: HTTP
port: 9080
allowedRoutes:
namespaces:
from: Same

View file

@ -96,6 +96,7 @@ spec:
- name: web
protocol: TCP
port: 8080
appProtocol: http
targetPort: web
selector:
app: containous
@ -131,6 +132,8 @@ metadata:
spec:
ports:
- name: websecure
protocol: TCP
appProtocol: https
port: 443
targetPort: websecure
selector:

View file

@ -2,6 +2,7 @@ package gateway
import (
"context"
"errors"
"fmt"
"net"
"strconv"
@ -32,6 +33,11 @@ func (p *Provider) loadGRPCRoutes(ctx context.Context, gatewayListeners []gatewa
Str("namespace", route.Namespace).
Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1.RouteParentStatus{
@ -48,11 +54,9 @@ func (p *Provider) loadGRPCRoutes(ctx context.Context, gatewayListeners []gatewa
},
}
for _, listener := range gatewayListeners {
accepted := true
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
for _, listener := range routeListeners {
accepted := matchListener(listener, parentRef)
if accepted && !allowRoute(listener, route.Namespace, kindGRPCRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false
@ -334,14 +338,15 @@ func (p *Provider) loadGRPCServers(namespace string, route *gatev1.GRPCRoute, ba
}
}
if svcPort.AppProtocol != nil && *svcPort.AppProtocol != appProtocolH2C {
protocol, err := getGRPCServiceProtocol(svcPort)
if err != nil {
return nil, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.RouteReasonUnsupportedProtocol),
Message: fmt.Sprintf("Cannot load GRPCBackendRef %s/%s: only kubernetes.io/h2c appProtocol is supported", namespace, backendRef.Name),
Message: fmt.Sprintf("Cannot load GRPCBackendRef %s/%s: only \"kubernetes.io/h2c\" and \"https\" appProtocol is supported", namespace, backendRef.Name),
}
}
@ -350,7 +355,7 @@ func (p *Provider) loadGRPCServers(namespace string, route *gatev1.GRPCRoute, ba
for _, ba := range backendAddresses {
lb.Servers = append(lb.Servers, dynamic.Server{
URL: fmt.Sprintf("h2c://%s", net.JoinHostPort(ba.IP, strconv.Itoa(int(ba.Port)))),
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(ba.IP, strconv.Itoa(int(ba.Port)))),
})
}
return lb, nil
@ -405,3 +410,22 @@ func buildGRPCHeaderRules(headers []gatev1.GRPCHeaderMatch) []string {
return rules
}
func getGRPCServiceProtocol(portSpec corev1.ServicePort) (string, error) {
if portSpec.Protocol != corev1.ProtocolTCP {
return "", errors.New("only TCP protocol is supported")
}
if portSpec.AppProtocol == nil {
return schemeH2C, nil
}
switch ap := *portSpec.AppProtocol; ap {
case appProtocolH2C:
return schemeH2C, nil
case appProtocolHTTPS:
return schemeHTTPS, nil
default:
return "", fmt.Errorf("unsupported application protocol %s", ap)
}
}

View file

@ -36,6 +36,11 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewa
Str("namespace", route.Namespace).
Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1.RouteParentStatus{
@ -52,11 +57,9 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewa
},
}
for _, listener := range gatewayListeners {
accepted := true
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
for _, listener := range routeListeners {
accepted := matchListener(listener, parentRef)
if accepted && !allowRoute(listener, route.Namespace, kindHTTPRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false
@ -465,7 +468,7 @@ func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, ba
}
}
protocol, err := getProtocol(svcPort)
protocol, err := getHTTPServiceProtocol(svcPort)
if err != nil {
return nil, corev1.ServicePort{}, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
@ -718,7 +721,7 @@ func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch g
var port *string
filterScheme := ptr.Deref(filter.Scheme, "")
if filterScheme == "http" || filterScheme == "https" {
if filterScheme == schemeHTTP || filterScheme == schemeHTTPS {
port = ptr.To("")
}
if filter.Port != nil {
@ -780,26 +783,26 @@ func createURLRewrite(filter *gatev1.HTTPURLRewriteFilter, pathMatch gatev1.HTTP
}, nil
}
func getProtocol(portSpec corev1.ServicePort) (string, error) {
func getHTTPServiceProtocol(portSpec corev1.ServicePort) (string, error) {
if portSpec.Protocol != corev1.ProtocolTCP {
return "", errors.New("only TCP protocol is supported")
}
if portSpec.AppProtocol == nil {
protocol := "http"
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") {
protocol = "https"
protocol := schemeHTTP
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, schemeHTTPS) {
protocol = schemeHTTPS
}
return protocol, nil
}
switch ap := *portSpec.AppProtocol; ap {
case appProtocolH2C:
return "h2c", nil
case appProtocolWS:
return "http", nil
case appProtocolWSS:
return "https", nil
return schemeH2C, nil
case appProtocolHTTP, appProtocolWS:
return schemeHTTP, nil
case appProtocolHTTPS, appProtocolWSS:
return schemeHTTPS, nil
default:
return "", fmt.Errorf("unsupported application protocol %s", ap)
}

View file

@ -50,9 +50,15 @@ const (
kindTLSRoute = "TLSRoute"
kindService = "Service"
appProtocolH2C = "kubernetes.io/h2c"
appProtocolWS = "kubernetes.io/ws"
appProtocolWSS = "kubernetes.io/wss"
appProtocolHTTP = "http"
appProtocolHTTPS = "https"
appProtocolH2C = "kubernetes.io/h2c"
appProtocolWS = "kubernetes.io/ws"
appProtocolWSS = "kubernetes.io/wss"
schemeHTTP = "http"
schemeHTTPS = "https"
schemeH2C = "h2c"
)
// Provider holds configurations of the provider.
@ -357,7 +363,13 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C
}
}
gateways := p.client.ListGateways()
var gateways []*gatev1.Gateway
for _, gateway := range p.client.ListGateways() {
if _, ok := gatewayClassNames[string(gateway.Spec.GatewayClassName)]; !ok {
continue
}
gateways = append(gateways, gateway)
}
var gatewayListeners []gatewayListener
for _, gateway := range gateways {
@ -366,10 +378,6 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C
Str("namespace", gateway.Namespace).
Logger()
if _, ok := gatewayClassNames[string(gateway.Spec.GatewayClassName)]; !ok {
continue
}
gatewayListeners = append(gatewayListeners, p.loadGatewayListeners(logger.WithContext(ctx), gateway, conf)...)
}
@ -1117,24 +1125,36 @@ func allowRoute(listener gatewayListener, routeNamespace, routeKind string) bool
})
}
func matchListener(listener gatewayListener, routeNamespace string, parentRef gatev1.ParentReference) bool {
if ptr.Deref(parentRef.Group, gatev1.GroupName) != gatev1.GroupName {
return false
func matchingGatewayListeners(gatewayListeners []gatewayListener, routeNamespace string, parentRefs []gatev1.ParentReference) []gatewayListener {
var listeners []gatewayListener
for _, listener := range gatewayListeners {
for _, parentRef := range parentRefs {
if ptr.Deref(parentRef.Group, gatev1.GroupName) != gatev1.GroupName {
continue
}
if ptr.Deref(parentRef.Kind, kindGateway) != kindGateway {
continue
}
parentRefNamespace := string(ptr.Deref(parentRef.Namespace, gatev1.Namespace(routeNamespace)))
if listener.GWNamespace != parentRefNamespace {
continue
}
if string(parentRef.Name) != listener.GWName {
continue
}
listeners = append(listeners, listener)
}
}
if ptr.Deref(parentRef.Kind, kindGateway) != kindGateway {
return false
}
parentRefNamespace := string(ptr.Deref(parentRef.Namespace, gatev1.Namespace(routeNamespace)))
if listener.GWNamespace != parentRefNamespace {
return false
}
if string(parentRef.Name) != listener.GWName {
return false
}
return listeners
}
func matchListener(listener gatewayListener, parentRef gatev1.ParentReference) bool {
sectionName := string(ptr.Deref(parentRef.SectionName, ""))
if sectionName != "" && sectionName != listener.Name {
return false

View file

@ -49,6 +49,47 @@ func init() {
}
}
func TestGatewayClassLabelSelector(t *testing.T) {
k8sObjects, gwObjects := readResources(t, []string{"gatewayclass_labelselector.yaml"})
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
client := newClientImpl(kubeClient, gwClient)
// This is initialized by the Provider init method but this cannot be called in a unit test.
client.labelSelector = "name=traefik-internal"
eventCh, err := client.WatchAll(nil, make(chan struct{}))
require.NoError(t, err)
if len(k8sObjects) > 0 || len(gwObjects) > 0 {
// just wait for the first event
<-eventCh
}
p := Provider{
EntryPoints: map[string]Entrypoint{"http": {Address: ":9080"}},
StatusAddress: &StatusAddress{IP: "1.2.3.4"},
client: client,
}
_ = p.loadConfigurationFromGateways(context.Background())
gw, err := gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-external", metav1.GetOptions{})
require.NoError(t, err)
assert.Empty(t, gw.Status.Addresses)
gw, err = gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-internal", metav1.GetOptions{})
require.NoError(t, err)
require.Len(t, gw.Status.Addresses, 1)
require.NotNil(t, gw.Status.Addresses[0].Type)
assert.Equal(t, gatev1.IPAddressType, *gw.Status.Addresses[0].Type)
assert.Equal(t, "1.2.3.4", gw.Status.Addresses[0].Value)
}
func TestLoadHTTPRoutes(t *testing.T) {
testCases := []struct {
desc string
@ -6738,127 +6779,171 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) {
}
}
func Test_matchListener(t *testing.T) {
func Test_matchingGatewayListener(t *testing.T) {
testCases := []struct {
desc string
gwListener gatewayListener
parentRef gatev1.ParentReference
gwListeners []gatewayListener
parentRefs []gatev1.ParentReference
routeNamespace string
wantMatch bool
wantLen int
}{
{
desc: "Unsupported group",
gwListener: gatewayListener{
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
}},
parentRefs: []gatev1.ParentReference{{
Group: ptr.To(gatev1.Group("foo")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Unsupported kind",
gwListener: gatewayListener{
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
}},
parentRefs: []gatev1.ParentReference{{
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("foo")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Namespace does not match the listener",
gwListener: gatewayListener{
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
}},
parentRefs: []gatev1.ParentReference{{
Namespace: ptr.To(gatev1.Namespace("foo")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Route namespace defaulting does not match the listener",
gwListener: gatewayListener{
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
}},
routeNamespace: "foo",
parentRef: gatev1.ParentReference{
parentRefs: []gatev1.ParentReference{{
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Name does not match the listener",
gwListener: gatewayListener{
Name: "foo",
gwListeners: []gatewayListener{{
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
}},
parentRefs: []gatev1.ParentReference{{
Namespace: ptr.To(gatev1.Namespace("default")),
Name: "foo",
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
},
{
desc: "SectionName does not match a listener",
gwListener: gatewayListener{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("bar")),
Name: "gateway",
Namespace: ptr.To(gatev1.Namespace("default")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Match",
gwListener: gatewayListener{
Name: "foo",
gwListeners: []gatewayListener{{
GWName: "gateway",
GWNamespace: "default",
}},
parentRefs: []gatev1.ParentReference{{
Name: "gateway",
Namespace: ptr.To(gatev1.Namespace("default")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
}},
wantLen: 1,
},
{
desc: "Match with route namespace defaulting",
gwListeners: []gatewayListener{{
GWName: "gateway",
GWNamespace: "default",
}},
routeNamespace: "default",
parentRefs: []gatev1.ParentReference{{
Name: "gateway",
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
}},
wantLen: 1,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
listeners := matchingGatewayListeners(test.gwListeners, test.routeNamespace, test.parentRefs)
assert.Len(t, listeners, test.wantLen)
})
}
}
func Test_matchListener(t *testing.T) {
testCases := []struct {
desc string
gwListener gatewayListener
parentRef gatev1.ParentReference
wantMatch bool
}{
{
desc: "Section do not match",
gwListener: gatewayListener{
Name: "foo",
Port: gatev1.PortNumber(80),
},
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("bar")),
Port: ptr.To(gatev1.PortNumber(80)),
},
},
{
desc: "Section matches",
gwListener: gatewayListener{
Name: "foo",
Port: gatev1.PortNumber(80),
},
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("foo")),
Name: "gateway",
Namespace: ptr.To(gatev1.Namespace("default")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
Port: ptr.To(gatev1.PortNumber(80)),
},
wantMatch: true,
},
{
desc: "Match with route namespace defaulting",
desc: "Port do not match",
gwListener: gatewayListener{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
Name: "foo",
Port: gatev1.PortNumber(90),
},
routeNamespace: "default",
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("foo")),
Name: "gateway",
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
Port: ptr.To(gatev1.PortNumber(80)),
},
},
{
desc: "Port matches",
gwListener: gatewayListener{
Name: "foo",
Port: gatev1.PortNumber(80),
},
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("foo")),
Port: ptr.To(gatev1.PortNumber(80)),
},
wantMatch: true,
},
@ -6868,7 +6953,7 @@ func Test_matchListener(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
gotMatch := matchListener(test.gwListener, test.routeNamespace, test.parentRef)
gotMatch := matchListener(test.gwListener, test.parentRef)
assert.Equal(t, test.wantMatch, gotMatch)
})
}

View file

@ -32,6 +32,11 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, gatewayListeners []gateway
Str("namespace", route.Namespace).
Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1alpha2.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1alpha2.RouteParentStatus{
@ -48,11 +53,9 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, gatewayListeners []gateway
},
}
for _, listener := range gatewayListeners {
accepted := true
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
for _, listener := range routeListeners {
accepted := matchListener(listener, parentRef)
if accepted && !allowRoute(listener, route.Namespace, kindTCPRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false

View file

@ -32,6 +32,11 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, gatewayListeners []gateway
Str("tls_route", route.Name).
Str("namespace", route.Namespace).Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1alpha2.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1alpha2.RouteParentStatus{
@ -48,11 +53,9 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, gatewayListeners []gateway
},
}
for _, listener := range gatewayListeners {
accepted := true
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
for _, listener := range routeListeners {
accepted := matchListener(listener, parentRef)
if accepted && !allowRoute(listener, route.Namespace, kindTLSRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false

View file

@ -117,6 +117,9 @@ func TestConnPool_MaxIdleConn(t *testing.T) {
}
func TestGC(t *testing.T) {
// TODO: make the test stable if possible.
t.Skip("This test is flaky")
var isDestroyed bool
pools := map[string]*connPool{}
dialer := func() (net.Conn, error) {

View file

@ -1,8 +1,8 @@
#!/usr/bin/env bash
# shellcheck disable=SC2046
set -e -o pipefail
# shellcheck disable=SC1091 # Cannot check source of this file
source /go/src/k8s.io/code-generator/kube_codegen.sh
git config --global --add safe.directory "/go/src/${PROJECT_MODULE}"

View file

@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
# example new bugfix v3.1.5
# example new bugfix v3.1.6
CurrentRef = "v3.1"
PreviousRef = "v3.1.4"
PreviousRef = "v3.1.5"
BaseBranch = "v3.1"
FutureCurrentRefName = "v3.1.5"
FutureCurrentRefName = "v3.1.6"
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10

View file

@ -10,8 +10,8 @@ PreviousRef = "v3.1.0-rc1"
BaseBranch = "master"
FutureCurrentRefName = "v3.2.0-rc1"
ThresholdPreviousRef = 10000
ThresholdCurrentRef = 10000
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10
Debug = true
DisplayLabel = true

View file

@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
# example rc3 of v3.1.0
CurrentRef = "v3.1"
PreviousRef = "v3.1.0-rc2"
BaseBranch = "v3.1"
FutureCurrentRefName = "v3.1.0-rc3"
# example rc2 of v3.2.0
CurrentRef = "v3.2"
PreviousRef = "v3.2.0-rc1"
BaseBranch = "v3.2"
FutureCurrentRefName = "v3.2.0-rc2"
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10

View file

@ -15,9 +15,9 @@ for os in linux darwin windows freebsd openbsd; do
go clean -cache
done
cat dist/**/*_checksums.txt >> dist/traefik_${VERSION}_checksums.txt
cat dist/**/*_checksums.txt >> "dist/traefik_${VERSION}_checksums.txt"
rm dist/**/*_checksums.txt
tar cfz dist/traefik-${VERSION}.src.tar.gz \
tar cfz "dist/traefik-${VERSION}.src.tar.gz" \
--exclude-vcs \
--exclude .idea \
--exclude .travis \
@ -25,4 +25,4 @@ tar cfz dist/traefik-${VERSION}.src.tar.gz \
--exclude .github \
--exclude dist .
chown -R $(id -u):$(id -g) dist/
chown -R "$(id -u)":"$(id -g)" dist/

View file

@ -6,6 +6,7 @@ script_dir="$( cd "$( dirname "${0}" )" && pwd -P)"
if command -v shellcheck
then
exit_code=0
# The list of shell script come from the (grep ...) command, feeding the loop
while IFS= read -r script_to_check
do
@ -18,7 +19,13 @@ then
| grep -v '.git/' | grep -v 'vendor/' | grep -v 'node_modules/' \
| cut -d':' -f1
)
wait # Wait for all background command to be completed
# Wait for all background command to be completed
for p in $(jobs -p)
do
wait "$p" || exit_code=$?
done
exit $exit_code
else
echo "== Command shellcheck not found in your PATH. No shell script checked."
exit 1
fi

View file

@ -1,7 +1,7 @@
FROM node:20.14
FROM node:22.9-alpine3.20
# Current Active LTS release according to (https://nodejs.org/en/about/releases/)
ENV WEBUI_DIR /src/webui
ENV WEBUI_DIR=/src/webui
RUN mkdir -p $WEBUI_DIR
COPY package.json $WEBUI_DIR/

View file

@ -50,8 +50,11 @@
"postcss": "^8.4.14",
"vitest": "^1.6.0"
},
"resolutions": {
"cookie": "^0.7.0"
},
"engines": {
"node": "^20 || ^18 || ^16",
"node": "^22 || ^20 || ^18 || ^16",
"npm": ">= 6.13.4",
"yarn": ">= 1.22.22"
},

View file

@ -64,7 +64,7 @@ export default defineComponent({
&-focus {
border: solid 2px $accent;
}
&-ex-size{
&-ex-size {
.text-h3 {
font-size: 22px;
}

View file

@ -15,7 +15,7 @@
<div
v-for="(entryItems, index) in entryAll.items"
:key="index"
class="col-12 col-sm-6 col-md-2"
class="col-12 col-sm-6 col-md-3"
>
<panel-entry
:name="entryItems.name"

File diff suppressed because it is too large Load diff