Merge branch 'master' of github.com:traefik/traefik
All checks were successful
Build & Push / build-and-push (push) Successful in 12m7s
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:
commit
ff55677acd
40 changed files with 2066 additions and 1647 deletions
|
@ -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)
|
||||
|
|
43
CHANGELOG.md
43
CHANGELOG.md
|
@ -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))
|
||||
|
||||
|
|
5
Makefile
5
Makefile
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/).
|
||||
|
|
|
@ -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`.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
13
go.mod
|
@ -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
16
go.sum
|
@ -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=
|
||||
|
|
|
@ -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'
|
|
@ -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
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -64,7 +64,7 @@ export default defineComponent({
|
|||
&-focus {
|
||||
border: solid 2px $accent;
|
||||
}
|
||||
&-ex-size{
|
||||
&-ex-size {
|
||||
.text-h3 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
2799
webui/yarn.lock
2799
webui/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue