Compare commits

..

26 commits

Author SHA1 Message Date
ff55677acd
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>
2024-10-15 16:21:39 +05:30
kevinpollet
06e64af9e9
Merge branch v3.2 into master 2024-10-10 11:32:18 +02:00
Romain
be156f6071
Ignore garbage collector flaky test 2024-10-10 10:48:04 +02:00
Michel Heusschen
6f469ee1ec
Only calculate basic auth hashes once for concurrent requests 2024-10-10 10:36:04 +02:00
Kevin Pollet
b46665c620
Prepare release v3.2.0-rc2 2024-10-09 17:16:04 +02:00
kevinpollet
be13b5b55d
Merge branch v3.1 into v3.2 2024-10-09 16:47:13 +02:00
Will Da Silva
e9d677f8cb
Support http and https appProtocol for Kubernetes Service 2024-10-09 16:26:04 +02:00
Kevin Pollet
4613ddd757
Prepare release v3.1.6 2024-10-09 15:54:05 +02:00
Romain
c441d04788
Avoid updating Accepted status for routes matching no Gateways
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2024-10-09 15:50:04 +02:00
kevinpollet
5d5dd9dd30
Merge branch v2.11 into v3.1 2024-10-09 15:19:14 +02:00
Kevin Pollet
1508a2c221
Do not update gateway status when not selected by a gateway class
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2024-10-09 15:14:05 +02:00
Kevin Pollet
934ca5fd22
Prepare release v2.11.12 2024-10-09 14:32:04 +02:00
Michel Heusschen
f16d14cfa6
Reuse compression writers 2024-10-09 14:14:03 +02:00
mmatur
4625bdf5cb
Merge current v2.11 into v3.1 2024-10-08 17:54:23 +02:00
Kevin Pollet
7b477f762a
Upgrade to node 22.9 and yarn lock to fix vulnerabilities
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2024-10-08 17:52:04 +02:00
Dylan Rodgers
157cf75e38
Update business callout in docs 2024-10-08 12:06:04 +02:00
Jesper Noordsij
ab35b3266a
Ensure shellcheck failure exit code is reflected in GH job result 2024-10-08 11:58:05 +02:00
Michel Heusschen
d339bfc8d2
Use correct default weight in Accept-Encoding 2024-10-08 11:48:04 +02:00
Romain
7b08ecfa5e
Bump sigs.k8s.io/gateway-api to v1.2.0
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
2024-10-08 10:46:04 +02:00
Dmitry Romashov
0a6b8780f0
Adopt a layout for the large amount of entrypoint port numbers 2024-10-08 10:44:04 +02:00
Michel Loiseleur
45292148e7
Detail CRD update with v3.2 in the migration guide 2024-10-07 09:54:04 +02:00
Kevin Pollet
fc563d3f6e
Fix the resolved TAG_NAME for commit in multiple tags 2024-10-07 09:32:05 +02:00
ttys3
a762cce430
Close wasm middleware to prevent memory leak 2024-10-04 16:36:04 +02:00
Kevin Pollet
306d3f277d
Bump github.com/klauspost/compress to dbd6c381492a 2024-10-04 10:48:04 +02:00
Ludovic Fernandez
6f7649fccc
Bump golangci-lint to 1.61.0 2024-10-04 09:38:04 +02:00
Matt Brown
e8ab3af74d
Clarify only header fields may be redacted in access-logs 2024-10-03 16:28:04 +02:00
40 changed files with 2066 additions and 1647 deletions

View file

@ -25,7 +25,7 @@ global_job_config:
- export "PATH=${GOPATH}/bin:${PATH}" - export "PATH=${GOPATH}/bin:${PATH}"
- mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin" - mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin"
- export GOPROXY=https://proxy.golang.org,direct - 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" - curl -sSfL https://gist.githubusercontent.com/traefiker/6d7ac019c11d011e4f131bb2cca8900e/raw/goreleaser.sh | bash -s -- -b "${GOPATH}/bin"
- checkout - checkout
- cache restore traefik-$(checksum go.sum) - 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) ## [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) [All Commits](https://github.com/traefik/traefik/compare/v3.1.0-rc1...v3.2.0-rc1)
@ -32,6 +73,8 @@
**Bug fixes:** **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)) - **[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)) - **[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 ([#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)) - 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/') 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) SHA := $(shell git rev-parse HEAD)
VERSION_GIT := $(if $(TAG_NAME),$(TAG_NAME),$(SHA)) VERSION_GIT := $(if $(TAG_NAME),$(TAG_NAME),$(SHA))
VERSION := $(if $(VERSION),$(VERSION),$(VERSION_GIT)) 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 BIN_NAME := traefik
CODENAME ?= cheddar CODENAME ?= cheddar

View file

@ -5,6 +5,6 @@
Add API Gateway or API Management capabilities seamlessly to your existing Traefik deployments. Add API Gateway or API Management capabilities seamlessly to your existing Traefik deployments.
No rip and replace. No learning curve. 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/) - [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) - [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 ## 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 ### 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/). 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 - `keep` to keep the value
- `drop` to drop 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`. 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/) 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). 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). 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 ## 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. 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/). 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 ## 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-retryablehttp v0.7.7
github.com/hashicorp/go-version v1.6.0 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/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/influxdb-client-go/v2 v2.7.0
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // No tag on the repo. 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/consul v1.0.2
github.com/kvtools/etcdv3 v1.0.2 github.com/kvtools/etcdv3 v1.0.2
github.com/kvtools/redis v1.1.0 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/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 v0.32.0
github.com/testcontainers/testcontainers-go/modules/k3s 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/tidwall/gjson v1.17.0
github.com/traefik/grpc-web v0.16.0 github.com/traefik/grpc-web v0.16.0
github.com/traefik/paerser v0.2.1 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/exp v0.0.0-20240909161429-701f63a606c0 // No tag on the repo.
golang.org/x/mod v0.21.0 golang.org/x/mod v0.21.0
golang.org/x/net v0.29.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/sys v0.25.0
golang.org/x/text v0.18.0 golang.org/x/text v0.18.0
golang.org/x/time v0.5.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. k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // No tag on the repo.
mvdan.cc/xurls/v2 v2.5.0 mvdan.cc/xurls/v2 v2.5.0
sigs.k8s.io/controller-runtime v0.18.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 sigs.k8s.io/yaml v1.4.0
) )
@ -343,7 +344,6 @@ require (
golang.org/x/arch v0.4.0 // indirect golang.org/x/arch v0.4.0 // indirect
golang.org/x/crypto v0.27.0 // indirect golang.org/x/crypto v0.27.0 // indirect
golang.org/x/oauth2 v0.21.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 golang.org/x/term v0.24.0 // indirect
google.golang.org/api v0.172.0 // indirect google.golang.org/api v0.172.0 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // 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 // 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. // tencentcloud uses monorepo with multimodule but the go.mod files are incomplete.
exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible 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 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= 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/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.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 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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.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.20241004063537-dbd6c381492a h1:cwHOqPB4H4iQq8177kf2SxpjNbcjJ2m3lNwKIe28Hqg=
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/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.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 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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 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 h1:Z3DTMveNUqeGJZ+CXZhpvI7OF1BS71Ywi3SwoXLZ4Lc=
github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0/go.mod h1:SYp1WtvNc3n/cg5atO6LvaOd2aqkQYMSDCcWPOUdaZg= 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.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 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 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 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/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 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltascY=
github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4= 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 h1:LFgeak1NmjEHF53c9ENdXdL1UMkF/lD5t+7Evsz4hH4=
github.com/traefik/paerser v0.2.1/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24= github.com/traefik/paerser v0.2.1/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E= 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= 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 h1:Z7jKuX784TQSUL1TIyeuF7j8KXZ4RtSX0YgtjKcSTME=
sigs.k8s.io/controller-runtime v0.18.0/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= 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 h1:LrToiFwtqKTKZcZtoQPTuo3FxhrrhTgzQG0Te+YGSo8=
sigs.k8s.io/gateway-api v1.2.0-rc2/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0= 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 h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 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= 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 apiVersion: gateway.networking.k8s.io/v1
date: '-' date: '-'
gatewayAPIChannel: experimental gatewayAPIChannel: experimental
gatewayAPIVersion: v1.2.0-rc2 gatewayAPIVersion: v1.2.0
implementation: implementation:
contact: contact:
- '@traefik/maintainers' - '@traefik/maintainers'

View file

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

View file

@ -90,7 +90,7 @@ func (s *K8sConformanceSuite) SetupSuite() {
s.k3sContainer, err = k3s.Run(ctx, s.k3sContainer, err = k3s.Run(ctx,
k3sImage, 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/01-rbac.yml"),
k3s.WithManifest("./fixtures/k8s-conformance/02-traefik.yml"), k3s.WithManifest("./fixtures/k8s-conformance/02-traefik.yml"),
network.WithNetwork(nil, s.network), 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/accesslog"
"github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"golang.org/x/sync/singleflight"
) )
const ( const (
@ -26,6 +27,9 @@ type basicAuth struct {
headerField string headerField string
removeHeader bool removeHeader bool
name string name string
checkSecret func(password, secret string) bool
singleflightGroup *singleflight.Group
} }
// NewBasic creates a basicAuth middleware. // NewBasic creates a basicAuth middleware.
@ -43,6 +47,8 @@ func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAu
headerField: authConfig.HeaderField, headerField: authConfig.HeaderField,
removeHeader: authConfig.RemoveHeader, removeHeader: authConfig.RemoveHeader,
name: name, name: name,
checkSecret: goauth.CheckSecret,
singleflightGroup: new(singleflight.Group),
} }
realm := defaultRealm realm := defaultRealm
@ -64,10 +70,7 @@ func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
user, password, ok := req.BasicAuth() user, password, ok := req.BasicAuth()
if ok { if ok {
secret := b.auth.Secrets(user, b.auth.Realm) ok = b.checkPassword(user, password)
if secret == "" || !goauth.CheckSecret(password, secret) {
ok = false
}
} }
logData := accesslog.GetLogData(req) logData := accesslog.GetLogData(req)
@ -97,6 +100,20 @@ func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
b.next.ServeHTTP(rw, req) 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 { func (b *basicAuth) secretBasic(user, realm string) string {
if secret, ok := b.users[user]; ok { if secret, ok := b.users[user]; ok {
return secret return secret

View file

@ -7,7 +7,9 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
"sync"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -167,6 +169,50 @@ func TestBasicAuthHeaderPresent(t *testing.T) {
assert.Equal(t, "traefik\n", string(body)) 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) { func TestBasicAuthUsersFromFile(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string

View file

@ -1,6 +1,7 @@
package compress package compress
import ( import (
"cmp"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@ -19,7 +20,7 @@ const (
type Encoding struct { type Encoding struct {
Type string Type string
Weight *float64 Weight float64
} }
func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, supportedEncodings []string) string { func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, supportedEncodings []string) string {
@ -42,11 +43,11 @@ func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, sup
encoding := encodings[0] encoding := encodings[0]
if encoding.Type == identityName && encoding.Weight != nil && *encoding.Weight == 0 { if encoding.Type == identityName && encoding.Weight == 0 {
return notAcceptable return notAcceptable
} }
if encoding.Type == wildcardName && encoding.Weight != nil && *encoding.Weight == 0 { if encoding.Type == wildcardName && encoding.Weight == 0 {
return notAcceptable return notAcceptable
} }
@ -87,11 +88,13 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin
continue 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=") { if len(parsed) > 1 && strings.HasPrefix(parsed[1], "q=") {
w, _ := strconv.ParseFloat(strings.TrimPrefix(parsed[1], "q="), 64) w, _ := strconv.ParseFloat(strings.TrimPrefix(parsed[1], "q="), 64)
weight = &w weight = w
hasWeight = true 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 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}, supportedEncodings: []string{zstdName, brotliName},
expected: brotliName, expected: brotliName,
}, },
{
desc: "mixed weight",
acceptEncoding: []string{"gzip, br;q=0.9"},
supportedEncodings: []string{gzipName, brotliName},
expected: gzipName,
},
} }
for _, test := range testCases { for _, test := range testCases {
@ -116,10 +122,10 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "weight", desc: "weight",
values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"}, values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"},
expected: []Encoding{ expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)}, {Type: brotliName, Weight: 1},
{Type: zstdName, Weight: ptr(0.9)}, {Type: zstdName, Weight: 0.9},
{Type: gzipName, Weight: ptr(0.8)}, {Type: gzipName, Weight: 0.8},
{Type: wildcardName, Weight: ptr(0.1)}, {Type: wildcardName, Weight: 0.1},
}, },
assertWeight: assert.True, 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"}, values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"},
supportedEncodings: []string{brotliName, gzipName}, supportedEncodings: []string{brotliName, gzipName},
expected: []Encoding{ expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)}, {Type: brotliName, Weight: 1},
{Type: gzipName, Weight: ptr(0.8)}, {Type: gzipName, Weight: 0.8},
{Type: wildcardName, Weight: ptr(0.1)}, {Type: wildcardName, Weight: 0.1},
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
@ -138,10 +144,10 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "mixed", desc: "mixed",
values: []string{"zstd,gzip, br;q=1.0, *;q=0"}, values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
expected: []Encoding{ expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)}, {Type: zstdName, Weight: 1},
{Type: zstdName}, {Type: gzipName, Weight: 1},
{Type: gzipName}, {Type: brotliName, Weight: 1},
{Type: wildcardName, Weight: ptr[float64](0)}, {Type: wildcardName, Weight: 0},
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
@ -150,8 +156,8 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"zstd,gzip, br;q=1.0, *;q=0"}, values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
supportedEncodings: []string{zstdName}, supportedEncodings: []string{zstdName},
expected: []Encoding{ expected: []Encoding{
{Type: zstdName}, {Type: zstdName, Weight: 1},
{Type: wildcardName, Weight: ptr[float64](0)}, {Type: wildcardName, Weight: 0},
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
@ -159,10 +165,10 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "no weight", desc: "no weight",
values: []string{"zstd, gzip, br, *"}, values: []string{"zstd, gzip, br, *"},
expected: []Encoding{ expected: []Encoding{
{Type: zstdName}, {Type: zstdName, Weight: 1},
{Type: gzipName}, {Type: gzipName, Weight: 1},
{Type: brotliName}, {Type: brotliName, Weight: 1},
{Type: wildcardName}, {Type: wildcardName, Weight: 1},
}, },
assertWeight: assert.False, assertWeight: assert.False,
}, },
@ -171,8 +177,8 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"zstd, gzip, br, *"}, values: []string{"zstd, gzip, br, *"},
supportedEncodings: []string{"gzip"}, supportedEncodings: []string{"gzip"},
expected: []Encoding{ expected: []Encoding{
{Type: gzipName}, {Type: gzipName, Weight: 1},
{Type: wildcardName}, {Type: wildcardName, Weight: 1},
}, },
assertWeight: assert.False, assertWeight: assert.False,
}, },
@ -180,9 +186,9 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "weight and identity", desc: "weight and identity",
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"}, values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
expected: []Encoding{ expected: []Encoding{
{Type: gzipName, Weight: ptr[float64](1)}, {Type: gzipName, Weight: 1},
{Type: identityName, Weight: ptr(0.5)}, {Type: identityName, Weight: 0.5},
{Type: wildcardName, Weight: ptr[float64](0)}, {Type: wildcardName, Weight: 0},
}, },
assertWeight: assert.True, 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"}, values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
supportedEncodings: []string{"br"}, supportedEncodings: []string{"br"},
expected: []Encoding{ expected: []Encoding{
{Type: identityName, Weight: ptr(0.5)}, {Type: identityName, Weight: 0.5},
{Type: wildcardName, Weight: ptr[float64](0)}, {Type: wildcardName, Weight: 0},
}, },
assertWeight: assert.True, 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) 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 { testCases := []struct {
name string name string
parallel bool parallel bool
size int size int
}{ }{
{ {"2k", false, 2048},
name: "2k", {"20k", false, 20480},
size: 2048, {"100k", false, 102400},
}, {"2k parallel", true, 2048},
{ {"20k parallel", true, 20480},
name: "20k", {"100k parallel", true, 102400},
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,
},
} }
for _, test := range testCases { for _, test := range testCases {
@ -734,7 +727,7 @@ func BenchmarkCompress(b *testing.B) {
handler, _ := New(context.Background(), next, dynamic.Compress{}, "testing") handler, _ := New(context.Background(), next, dynamic.Compress{}, "testing")
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil) req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
req.Header.Set("Accept-Encoding", "gzip") req.Header.Set("Accept-Encoding", algorithm)
b.ReportAllocs() b.ReportAllocs()
b.SetBytes(int64(test.size)) b.SetBytes(int64(test.size))
@ -742,7 +735,7 @@ func BenchmarkCompress(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
runBenchmark(b, req, handler) runBenchmark(b, req, handler, algorithm)
} }
}) })
return return
@ -750,13 +743,13 @@ func BenchmarkCompress(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for range b.N { 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() b.Helper()
res := httptest.NewRecorder() 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) 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 { func generateBytes(length int) []byte {

View file

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

View file

@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime"
"strings" "strings"
"github.com/http-wasm/http-wasm-host-go/handler" "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 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. // 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 - name: web
protocol: TCP protocol: TCP
port: 8080 port: 8080
appProtocol: http
targetPort: web targetPort: web
selector: selector:
app: containous app: containous
@ -131,6 +132,8 @@ metadata:
spec: spec:
ports: ports:
- name: websecure - name: websecure
protocol: TCP
appProtocol: https
port: 443 port: 443
targetPort: websecure targetPort: websecure
selector: selector:

View file

@ -2,6 +2,7 @@ package gateway
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@ -32,6 +33,11 @@ func (p *Provider) loadGRPCRoutes(ctx context.Context, gatewayListeners []gatewa
Str("namespace", route.Namespace). Str("namespace", route.Namespace).
Logger() Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1.RouteParentStatus var parentStatuses []gatev1.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs { for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1.RouteParentStatus{ parentStatus := &gatev1.RouteParentStatus{
@ -48,11 +54,9 @@ func (p *Provider) loadGRPCRoutes(ctx context.Context, gatewayListeners []gatewa
}, },
} }
for _, listener := range gatewayListeners { for _, listener := range routeListeners {
accepted := true accepted := matchListener(listener, parentRef)
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
if accepted && !allowRoute(listener, route.Namespace, kindGRPCRoute) { if accepted && !allowRoute(listener, route.Namespace, kindGRPCRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners)) parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false 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{ return nil, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs), Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse, Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation, ObservedGeneration: route.Generation,
LastTransitionTime: metav1.Now(), LastTransitionTime: metav1.Now(),
Reason: string(gatev1.RouteReasonUnsupportedProtocol), 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 { for _, ba := range backendAddresses {
lb.Servers = append(lb.Servers, dynamic.Server{ 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 return lb, nil
@ -405,3 +410,22 @@ func buildGRPCHeaderRules(headers []gatev1.GRPCHeaderMatch) []string {
return rules 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). Str("namespace", route.Namespace).
Logger() Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1.RouteParentStatus var parentStatuses []gatev1.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs { for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1.RouteParentStatus{ parentStatus := &gatev1.RouteParentStatus{
@ -52,11 +57,9 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewa
}, },
} }
for _, listener := range gatewayListeners { for _, listener := range routeListeners {
accepted := true accepted := matchListener(listener, parentRef)
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
if accepted && !allowRoute(listener, route.Namespace, kindHTTPRoute) { if accepted && !allowRoute(listener, route.Namespace, kindHTTPRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners)) parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false 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 { if err != nil {
return nil, corev1.ServicePort{}, &metav1.Condition{ return nil, corev1.ServicePort{}, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs), Type: string(gatev1.RouteConditionResolvedRefs),
@ -718,7 +721,7 @@ func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch g
var port *string var port *string
filterScheme := ptr.Deref(filter.Scheme, "") filterScheme := ptr.Deref(filter.Scheme, "")
if filterScheme == "http" || filterScheme == "https" { if filterScheme == schemeHTTP || filterScheme == schemeHTTPS {
port = ptr.To("") port = ptr.To("")
} }
if filter.Port != nil { if filter.Port != nil {
@ -780,26 +783,26 @@ func createURLRewrite(filter *gatev1.HTTPURLRewriteFilter, pathMatch gatev1.HTTP
}, nil }, nil
} }
func getProtocol(portSpec corev1.ServicePort) (string, error) { func getHTTPServiceProtocol(portSpec corev1.ServicePort) (string, error) {
if portSpec.Protocol != corev1.ProtocolTCP { if portSpec.Protocol != corev1.ProtocolTCP {
return "", errors.New("only TCP protocol is supported") return "", errors.New("only TCP protocol is supported")
} }
if portSpec.AppProtocol == nil { if portSpec.AppProtocol == nil {
protocol := "http" protocol := schemeHTTP
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") { if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, schemeHTTPS) {
protocol = "https" protocol = schemeHTTPS
} }
return protocol, nil return protocol, nil
} }
switch ap := *portSpec.AppProtocol; ap { switch ap := *portSpec.AppProtocol; ap {
case appProtocolH2C: case appProtocolH2C:
return "h2c", nil return schemeH2C, nil
case appProtocolWS: case appProtocolHTTP, appProtocolWS:
return "http", nil return schemeHTTP, nil
case appProtocolWSS: case appProtocolHTTPS, appProtocolWSS:
return "https", nil return schemeHTTPS, nil
default: default:
return "", fmt.Errorf("unsupported application protocol %s", ap) return "", fmt.Errorf("unsupported application protocol %s", ap)
} }

View file

@ -50,9 +50,15 @@ const (
kindTLSRoute = "TLSRoute" kindTLSRoute = "TLSRoute"
kindService = "Service" kindService = "Service"
appProtocolHTTP = "http"
appProtocolHTTPS = "https"
appProtocolH2C = "kubernetes.io/h2c" appProtocolH2C = "kubernetes.io/h2c"
appProtocolWS = "kubernetes.io/ws" appProtocolWS = "kubernetes.io/ws"
appProtocolWSS = "kubernetes.io/wss" appProtocolWSS = "kubernetes.io/wss"
schemeHTTP = "http"
schemeHTTPS = "https"
schemeH2C = "h2c"
) )
// Provider holds configurations of the provider. // 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 var gatewayListeners []gatewayListener
for _, gateway := range gateways { for _, gateway := range gateways {
@ -366,10 +378,6 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C
Str("namespace", gateway.Namespace). Str("namespace", gateway.Namespace).
Logger() Logger()
if _, ok := gatewayClassNames[string(gateway.Spec.GatewayClassName)]; !ok {
continue
}
gatewayListeners = append(gatewayListeners, p.loadGatewayListeners(logger.WithContext(ctx), gateway, conf)...) 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 { 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 { if ptr.Deref(parentRef.Group, gatev1.GroupName) != gatev1.GroupName {
return false continue
} }
if ptr.Deref(parentRef.Kind, kindGateway) != kindGateway { if ptr.Deref(parentRef.Kind, kindGateway) != kindGateway {
return false continue
} }
parentRefNamespace := string(ptr.Deref(parentRef.Namespace, gatev1.Namespace(routeNamespace))) parentRefNamespace := string(ptr.Deref(parentRef.Namespace, gatev1.Namespace(routeNamespace)))
if listener.GWNamespace != parentRefNamespace { if listener.GWNamespace != parentRefNamespace {
return false continue
} }
if string(parentRef.Name) != listener.GWName { if string(parentRef.Name) != listener.GWName {
return false continue
} }
listeners = append(listeners, listener)
}
}
return listeners
}
func matchListener(listener gatewayListener, parentRef gatev1.ParentReference) bool {
sectionName := string(ptr.Deref(parentRef.SectionName, "")) sectionName := string(ptr.Deref(parentRef.SectionName, ""))
if sectionName != "" && sectionName != listener.Name { if sectionName != "" && sectionName != listener.Name {
return false 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) { func TestLoadHTTPRoutes(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
@ -6738,127 +6779,171 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) {
} }
} }
func Test_matchingGatewayListener(t *testing.T) {
testCases := []struct {
desc string
gwListeners []gatewayListener
parentRefs []gatev1.ParentReference
routeNamespace string
wantLen int
}{
{
desc: "Unsupported group",
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
}},
parentRefs: []gatev1.ParentReference{{
Group: ptr.To(gatev1.Group("foo")),
}},
wantLen: 0,
},
{
desc: "Unsupported kind",
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
}},
parentRefs: []gatev1.ParentReference{{
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("foo")),
}},
wantLen: 0,
},
{
desc: "Namespace does not match the listener",
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
}},
parentRefs: []gatev1.ParentReference{{
Namespace: ptr.To(gatev1.Namespace("foo")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
}},
wantLen: 0,
},
{
desc: "Route namespace defaulting does not match the listener",
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
}},
routeNamespace: "foo",
parentRefs: []gatev1.ParentReference{{
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
}},
wantLen: 0,
},
{
desc: "Name does not match the listener",
gwListeners: []gatewayListener{{
GWName: "gateway",
GWNamespace: "default",
}},
parentRefs: []gatev1.ParentReference{{
Namespace: ptr.To(gatev1.Namespace("default")),
Name: "foo",
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
}},
wantLen: 0,
},
{
desc: "Match",
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) { func Test_matchListener(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
gwListener gatewayListener gwListener gatewayListener
parentRef gatev1.ParentReference parentRef gatev1.ParentReference
routeNamespace string
wantMatch bool wantMatch bool
}{ }{
{ {
desc: "Unsupported group", desc: "Section do not match",
gwListener: gatewayListener{ gwListener: gatewayListener{
Name: "foo", Name: "foo",
GWName: "gateway", Port: gatev1.PortNumber(80),
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
Group: ptr.To(gatev1.Group("foo")),
},
wantMatch: false,
},
{
desc: "Unsupported kind",
gwListener: gatewayListener{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("foo")),
},
wantMatch: false,
},
{
desc: "Namespace does not match the listener",
gwListener: gatewayListener{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
Namespace: ptr.To(gatev1.Namespace("foo")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
},
{
desc: "Route namespace defaulting does not match the listener",
gwListener: gatewayListener{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
routeNamespace: "foo",
parentRef: gatev1.ParentReference{
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
},
{
desc: "Name does not match the listener",
gwListener: gatewayListener{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: 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{ parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("bar")), SectionName: ptr.To(gatev1.SectionName("bar")),
Name: "gateway", Port: ptr.To(gatev1.PortNumber(80)),
Namespace: ptr.To(gatev1.Namespace("default")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
}, },
wantMatch: false,
}, },
{ {
desc: "Match", desc: "Section matches",
gwListener: gatewayListener{ gwListener: gatewayListener{
Name: "foo", Name: "foo",
GWName: "gateway", Port: gatev1.PortNumber(80),
GWNamespace: "default",
}, },
parentRef: gatev1.ParentReference{ parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("foo")), SectionName: ptr.To(gatev1.SectionName("foo")),
Name: "gateway", Port: ptr.To(gatev1.PortNumber(80)),
Namespace: ptr.To(gatev1.Namespace("default")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
}, },
wantMatch: true, wantMatch: true,
}, },
{ {
desc: "Match with route namespace defaulting", desc: "Port do not match",
gwListener: gatewayListener{ gwListener: gatewayListener{
Name: "foo", Name: "foo",
GWName: "gateway", Port: gatev1.PortNumber(90),
GWNamespace: "default",
}, },
routeNamespace: "default",
parentRef: gatev1.ParentReference{ parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("foo")), SectionName: ptr.To(gatev1.SectionName("foo")),
Name: "gateway", Port: ptr.To(gatev1.PortNumber(80)),
Group: ptr.To(gatev1.Group(gatev1.GroupName)), },
Kind: ptr.To(gatev1.Kind("Gateway")), },
{
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, wantMatch: true,
}, },
@ -6868,7 +6953,7 @@ func Test_matchListener(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
gotMatch := matchListener(test.gwListener, test.routeNamespace, test.parentRef) gotMatch := matchListener(test.gwListener, test.parentRef)
assert.Equal(t, test.wantMatch, gotMatch) 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). Str("namespace", route.Namespace).
Logger() Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1alpha2.RouteParentStatus var parentStatuses []gatev1alpha2.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs { for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1alpha2.RouteParentStatus{ parentStatus := &gatev1alpha2.RouteParentStatus{
@ -48,11 +53,9 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, gatewayListeners []gateway
}, },
} }
for _, listener := range gatewayListeners { for _, listener := range routeListeners {
accepted := true accepted := matchListener(listener, parentRef)
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
if accepted && !allowRoute(listener, route.Namespace, kindTCPRoute) { if accepted && !allowRoute(listener, route.Namespace, kindTCPRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners)) parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false accepted = false

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,9 +15,9 @@ for os in linux darwin windows freebsd openbsd; do
go clean -cache go clean -cache
done done
cat dist/**/*_checksums.txt >> dist/traefik_${VERSION}_checksums.txt cat dist/**/*_checksums.txt >> "dist/traefik_${VERSION}_checksums.txt"
rm dist/**/*_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-vcs \
--exclude .idea \ --exclude .idea \
--exclude .travis \ --exclude .travis \
@ -25,4 +25,4 @@ tar cfz dist/traefik-${VERSION}.src.tar.gz \
--exclude .github \ --exclude .github \
--exclude dist . --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 if command -v shellcheck
then then
exit_code=0
# The list of shell script come from the (grep ...) command, feeding the loop # The list of shell script come from the (grep ...) command, feeding the loop
while IFS= read -r script_to_check while IFS= read -r script_to_check
do do
@ -18,7 +19,13 @@ then
| grep -v '.git/' | grep -v 'vendor/' | grep -v 'node_modules/' \ | grep -v '.git/' | grep -v 'vendor/' | grep -v 'node_modules/' \
| cut -d':' -f1 | 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 else
echo "== Command shellcheck not found in your PATH. No shell script checked." echo "== Command shellcheck not found in your PATH. No shell script checked."
exit 1
fi 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/) # 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 RUN mkdir -p $WEBUI_DIR
COPY package.json $WEBUI_DIR/ COPY package.json $WEBUI_DIR/

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff