Merge branch 'v3.0' of github.com:traefik/traefik
Signed-off-by: baalajimaestro <me@baalajimaestro.me>
This commit is contained in:
commit
ca45770bbd
75 changed files with 5654 additions and 269 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,3 +19,4 @@ plugins-storage/
|
|||
plugins-local/
|
||||
traefik_changelog.md
|
||||
integration/tailscale.secret
|
||||
integration/conformance-reports/
|
||||
|
|
|
@ -280,3 +280,9 @@ issues:
|
|||
text: 'unusedwrite: unused write to field'
|
||||
linters:
|
||||
- govet
|
||||
- path: pkg/cli/deprecation.go
|
||||
linters:
|
||||
- goconst
|
||||
- path: pkg/cli/loader_file.go
|
||||
linters:
|
||||
- goconst
|
||||
|
|
11
Makefile
11
Makefile
|
@ -99,6 +99,17 @@ test-unit:
|
|||
test-integration: binary
|
||||
GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -test.timeout=20m -failfast -v $(TESTFLAGS)
|
||||
|
||||
## Run the conformance tests
|
||||
.PHONY: test-gateway-api-conformance
|
||||
test-gateway-api-conformance: binary
|
||||
GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -test.run K8sConformanceSuite -k8sConformance=true $(TESTFLAGS)
|
||||
|
||||
## TODO: Need to be fixed to work in all situations.
|
||||
## Run the conformance tests
|
||||
.PHONY: test-gateway-api-conformance-ci
|
||||
test-gateway-api-conformance-ci:
|
||||
GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -test.run K8sConformanceSuite -k8sConformance=true $(TESTFLAGS)
|
||||
|
||||
## Pull all Docker images to avoid timeout during integration tests
|
||||
.PHONY: pull-images
|
||||
pull-images:
|
||||
|
|
|
@ -53,7 +53,7 @@ func main() {
|
|||
// traefik config inits
|
||||
tConfig := cmd.NewTraefikConfiguration()
|
||||
|
||||
loaders := []cli.ResourceLoader{&tcli.FileLoader{}, &tcli.FlagLoader{}, &tcli.EnvLoader{}}
|
||||
loaders := []cli.ResourceLoader{&tcli.DeprecationLoader{}, &tcli.FileLoader{}, &tcli.FlagLoader{}, &tcli.EnvLoader{}}
|
||||
|
||||
cmdTraefik := &cli.Command{
|
||||
Name: "traefik",
|
||||
|
|
|
@ -19,7 +19,7 @@ version: '3'
|
|||
|
||||
services:
|
||||
reverse-proxy:
|
||||
# The official v2 Traefik docker image
|
||||
# The official v3 Traefik docker image
|
||||
image: traefik:v3.0
|
||||
# Enables the web UI and tells Traefik to listen to docker
|
||||
command: --api.insecure=true --providers.docker
|
||||
|
|
|
@ -19,6 +19,8 @@ and how it now looks like in v3.
|
|||
|
||||
### Docker & Docker Swarm
|
||||
|
||||
#### SwarmMode
|
||||
|
||||
In v3, the provider Docker has been split into 2 providers:
|
||||
|
||||
- Docker provider (without Swarm support)
|
||||
|
@ -43,7 +45,7 @@ In v3, the provider Docker has been split into 2 providers:
|
|||
|
||||
This configuration is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
##### Remediation
|
||||
|
||||
In v3, the `swarmMode` should not be used with the Docker provider, and, to use Swarm, the Swarm provider should be used instead.
|
||||
|
||||
|
@ -64,7 +66,35 @@ In v3, the `swarmMode` should not be used with the Docker provider, and, to use
|
|||
--providers.swarm.endpoint=tcp://127.0.0.1:2377
|
||||
```
|
||||
|
||||
### HTTP3 Experimental Configuration
|
||||
#### TLS.CAOptional
|
||||
|
||||
Docker provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the Docker provider static configuration.
|
||||
|
||||
### Experimental Configuration
|
||||
|
||||
#### HTTP3
|
||||
|
||||
In v3, HTTP/3 is no longer an experimental feature.
|
||||
It can be enabled on entry points without the associated `experimental.http3` option, which is now removed.
|
||||
|
@ -86,12 +116,14 @@ It is now unsupported and would prevent Traefik to start.
|
|||
--experimental.http3=true
|
||||
```
|
||||
|
||||
#### Remediation
|
||||
##### Remediation
|
||||
|
||||
The `http3` option should be removed from the static configuration experimental section.
|
||||
|
||||
### Consul provider
|
||||
|
||||
#### namespace
|
||||
|
||||
The Consul provider `namespace` option was deprecated in v2 and is now removed in v3.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
|
@ -111,7 +143,7 @@ It is now unsupported and would prevent Traefik to start.
|
|||
--consul.namespace=foobar
|
||||
```
|
||||
|
||||
#### Remediation
|
||||
##### Remediation
|
||||
|
||||
In v3, the `namespaces` option should be used instead of the `namespace` option.
|
||||
|
||||
|
@ -132,8 +164,36 @@ In v3, the `namespaces` option should be used instead of the `namespace` option.
|
|||
--consul.namespaces=foobar
|
||||
```
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
Consul provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
consul:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consul.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.consul.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the Consul provider static configuration.
|
||||
|
||||
### ConsulCatalog provider
|
||||
|
||||
#### namespace
|
||||
|
||||
The ConsulCatalog provider `namespace` option was deprecated in v2 and is now removed in v3.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
|
@ -153,7 +213,7 @@ It is now unsupported and would prevent Traefik to start.
|
|||
--consulCatalog.namespace=foobar
|
||||
```
|
||||
|
||||
#### Remediation
|
||||
##### Remediation
|
||||
|
||||
In v3, the `namespaces` option should be used instead of the `namespace` option.
|
||||
|
||||
|
@ -174,8 +234,37 @@ In v3, the `namespaces` option should be used instead of the `namespace` option.
|
|||
--consulCatalog.namespaces=foobar
|
||||
```
|
||||
|
||||
#### Endpoint.TLS.CAOptional
|
||||
|
||||
ConsulCatalog provider `endpoint.tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the Endpoint.TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
consulCatalog:
|
||||
endpoint:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consulCatalog.endpoint.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.consulCatalog.endpoint.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `endpoint.tls.caOptional` option should be removed from the ConsulCatalog provider static configuration.
|
||||
|
||||
### Nomad provider
|
||||
|
||||
#### namespace
|
||||
|
||||
The Nomad provider `namespace` option was deprecated in v2 and is now removed in v3.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
|
@ -195,7 +284,7 @@ It is now unsupported and would prevent Traefik to start.
|
|||
--nomad.namespace=foobar
|
||||
```
|
||||
|
||||
#### Remediation
|
||||
##### Remediation
|
||||
|
||||
In v3, the `namespaces` option should be used instead of the `namespace` option.
|
||||
|
||||
|
@ -216,6 +305,33 @@ In v3, the `namespaces` option should be used instead of the `namespace` option.
|
|||
--nomad.namespaces=foobar
|
||||
```
|
||||
|
||||
#### Endpoint.TLS.CAOptional
|
||||
|
||||
Nomad provider `endpoint.tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the Endpoint.TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
nomad:
|
||||
endpoint:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.nomad.endpoint.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.nomad.endpoint.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `endpoint.tls.caOptional` option should be removed from the Nomad provider static configuration.
|
||||
|
||||
### Rancher v1 Provider
|
||||
|
||||
In v3, the Rancher v1 provider has been removed because Rancher v1 is [no longer actively maintaned](https://rancher.com/docs/os/v1.x/en/support/),
|
||||
|
@ -271,6 +387,90 @@ This configuration is now unsupported and would prevent Traefik to start.
|
|||
|
||||
All Marathon provider related configuration should be removed from the static configuration.
|
||||
|
||||
### HTTP Provider
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
HTTP provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the HTTP provider static configuration.
|
||||
|
||||
### ETCD Provider
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
ETCD provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
etcd:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.etcd.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.etcd.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the ETCD provider static configuration.
|
||||
|
||||
### Redis Provider
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
Redis provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
redis:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.redis.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.redis.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the Redis provider static configuration.
|
||||
|
||||
### InfluxDB v1
|
||||
|
||||
InfluxDB v1.x maintenance [ended in 2021](https://www.influxdata.com/blog/influxdb-oss-and-enterprise-roadmap-update-from-influxdays-emea/).
|
||||
|
@ -326,21 +526,16 @@ All Pilot related configuration should be removed from the static configuration.
|
|||
|
||||
## Dynamic configuration
|
||||
|
||||
### IPWhiteList
|
||||
### Router Rule Matchers
|
||||
|
||||
In v3, we renamed the `IPWhiteList` middleware to `IPAllowList` without changing anything to the configuration.
|
||||
In v3, a new rule matchers syntax has been introduced for HTTP and TCP routers.
|
||||
The default rule matchers syntax is now the v3 one, but for backward compatibility this can be configured.
|
||||
The v2 rule matchers syntax is deprecated and its support will be removed in the next major version.
|
||||
For this reason, we encourage migrating to the new syntax.
|
||||
|
||||
### Deprecated Options Removal
|
||||
#### New V3 Syntax Notable Changes
|
||||
|
||||
- The `tracing.datadog.globaltag` option has been removed.
|
||||
- The `tls.caOptional` option has been removed from the ForwardAuth middleware, as well as from the HTTP, Consul, Etcd, Redis, ZooKeeper, Consul Catalog, and Docker providers.
|
||||
- `sslRedirect`, `sslTemporaryRedirect`, `sslHost`, `sslForceHost` and `featurePolicy` options of the Headers middleware have been removed.
|
||||
- The `forceSlash` option of the StripPrefix middleware has been removed.
|
||||
- The `preferServerCipherSuites` option has been removed.
|
||||
|
||||
### Matchers
|
||||
|
||||
In v3, the `Headers` and `HeadersRegexp` matchers have been renamed to `Header` and `HeaderRegexp` respectively.
|
||||
The `Headers` and `HeadersRegexp` matchers have been renamed to `Header` and `HeaderRegexp` respectively.
|
||||
|
||||
`PathPrefix` no longer uses regular expressions to match path prefixes.
|
||||
|
||||
|
@ -355,6 +550,87 @@ and should be explicitly combined using logical operators to mimic previous beha
|
|||
|
||||
`HostHeader` has been removed, use `Host` instead.
|
||||
|
||||
#### Remediation
|
||||
|
||||
##### Configure the Default Syntax In Static Configuration
|
||||
|
||||
The default rule matchers syntax is the expected syntax for any router that is not self opt-out from this default value.
|
||||
It can be configured in the static configuration.
|
||||
|
||||
??? example "An example configuration for the default rule matchers syntax"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
# static configuration
|
||||
core:
|
||||
defaultRuleSyntax: v2
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
# static configuration
|
||||
[core]
|
||||
defaultRuleSyntax="v2"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# static configuration
|
||||
--core.defaultRuleSyntax=v2
|
||||
```
|
||||
|
||||
##### Configure the Syntax Per Router
|
||||
|
||||
The rule syntax can also be configured on a per-router basis.
|
||||
This allows to have heterogeneous router configurations and ease migration.
|
||||
|
||||
??? example "An example router with syntax configuration"
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.routers.test.ruleSyntax=v2"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
routes:
|
||||
- match: PathPrefix(`/foo`, `/bar`)
|
||||
syntax: v2
|
||||
kind: Rule
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.routers.test.ruleSyntax=v2"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
routers:
|
||||
test:
|
||||
ruleSyntax: v2
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.routers]
|
||||
[http.routers.test]
|
||||
ruleSyntax = "v2"
|
||||
```
|
||||
|
||||
### IPWhiteList
|
||||
|
||||
In v3, we renamed the `IPWhiteList` middleware to `IPAllowList` without changing anything to the configuration.
|
||||
|
||||
### Deprecated Options Removal
|
||||
|
||||
- The `tracing.datadog.globaltag` option has been removed.
|
||||
- The `tls.caOptional` option has been removed from the ForwardAuth middleware, as well as from the HTTP, Consul, Etcd, Redis, ZooKeeper, Consul Catalog, and Docker providers.
|
||||
- `sslRedirect`, `sslTemporaryRedirect`, `sslHost`, `sslForceHost` and `featurePolicy` options of the Headers middleware have been removed.
|
||||
- The `forceSlash` option of the StripPrefix middleware has been removed.
|
||||
- The `preferServerCipherSuites` option has been removed.
|
||||
|
||||
### TCP LoadBalancer `terminationDelay` option
|
||||
|
||||
The TCP LoadBalancer `terminationDelay` option has been removed.
|
||||
|
@ -415,3 +691,5 @@ Here are two possible transition strategies:
|
|||
For legacy stacks that cannot immediately upgrade to the latest vendor agents supporting OTLP ingestion,
|
||||
using OpenTelemetry (OTel) collectors with appropriate exporters configuration is a viable solution.
|
||||
This allows continued compatibility with the existing infrastructure.
|
||||
|
||||
Please check the [OpenTelemetry Tracing provider documention](../observability/tracing/opentelemetry.md) for more information.
|
||||
|
|
|
@ -132,6 +132,7 @@
|
|||
- "traefik.http.routers.router0.middlewares=foobar, foobar"
|
||||
- "traefik.http.routers.router0.priority=42"
|
||||
- "traefik.http.routers.router0.rule=foobar"
|
||||
- "traefik.http.routers.router0.rulesyntax=foobar"
|
||||
- "traefik.http.routers.router0.service=foobar"
|
||||
- "traefik.http.routers.router0.tls=true"
|
||||
- "traefik.http.routers.router0.tls.certresolver=foobar"
|
||||
|
@ -144,6 +145,7 @@
|
|||
- "traefik.http.routers.router1.middlewares=foobar, foobar"
|
||||
- "traefik.http.routers.router1.priority=42"
|
||||
- "traefik.http.routers.router1.rule=foobar"
|
||||
- "traefik.http.routers.router1.rulesyntax=foobar"
|
||||
- "traefik.http.routers.router1.service=foobar"
|
||||
- "traefik.http.routers.router1.tls=true"
|
||||
- "traefik.http.routers.router1.tls.certresolver=foobar"
|
||||
|
@ -176,6 +178,7 @@
|
|||
- "traefik.http.services.service02.loadbalancer.sticky.cookie.secure=true"
|
||||
- "traefik.http.services.service02.loadbalancer.server.port=foobar"
|
||||
- "traefik.http.services.service02.loadbalancer.server.scheme=foobar"
|
||||
- "traefik.http.services.service02.loadbalancer.server.weight=42"
|
||||
- "traefik.tcp.middlewares.tcpmiddleware01.ipallowlist.sourcerange=foobar, foobar"
|
||||
- "traefik.tcp.middlewares.tcpmiddleware02.ipwhitelist.sourcerange=foobar, foobar"
|
||||
- "traefik.tcp.middlewares.tcpmiddleware03.inflightconn.amount=42"
|
||||
|
@ -183,6 +186,7 @@
|
|||
- "traefik.tcp.routers.tcprouter0.middlewares=foobar, foobar"
|
||||
- "traefik.tcp.routers.tcprouter0.priority=42"
|
||||
- "traefik.tcp.routers.tcprouter0.rule=foobar"
|
||||
- "traefik.tcp.routers.tcprouter0.rulesyntax=foobar"
|
||||
- "traefik.tcp.routers.tcprouter0.service=foobar"
|
||||
- "traefik.tcp.routers.tcprouter0.tls=true"
|
||||
- "traefik.tcp.routers.tcprouter0.tls.certresolver=foobar"
|
||||
|
@ -196,6 +200,7 @@
|
|||
- "traefik.tcp.routers.tcprouter1.middlewares=foobar, foobar"
|
||||
- "traefik.tcp.routers.tcprouter1.priority=42"
|
||||
- "traefik.tcp.routers.tcprouter1.rule=foobar"
|
||||
- "traefik.tcp.routers.tcprouter1.rulesyntax=foobar"
|
||||
- "traefik.tcp.routers.tcprouter1.service=foobar"
|
||||
- "traefik.tcp.routers.tcprouter1.tls=true"
|
||||
- "traefik.tcp.routers.tcprouter1.tls.certresolver=foobar"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
middlewares = ["foobar", "foobar"]
|
||||
service = "foobar"
|
||||
rule = "foobar"
|
||||
ruleSyntax = "foobar"
|
||||
priority = 42
|
||||
[http.routers.Router0.tls]
|
||||
options = "foobar"
|
||||
|
@ -24,6 +25,7 @@
|
|||
middlewares = ["foobar", "foobar"]
|
||||
service = "foobar"
|
||||
rule = "foobar"
|
||||
ruleSyntax = "foobar"
|
||||
priority = 42
|
||||
[http.routers.Router1.tls]
|
||||
options = "foobar"
|
||||
|
@ -56,9 +58,11 @@
|
|||
|
||||
[[http.services.Service02.loadBalancer.servers]]
|
||||
url = "foobar"
|
||||
weight = 42
|
||||
|
||||
[[http.services.Service02.loadBalancer.servers]]
|
||||
url = "foobar"
|
||||
weight = 42
|
||||
[http.services.Service02.loadBalancer.healthCheck]
|
||||
scheme = "foobar"
|
||||
mode = "foobar"
|
||||
|
@ -353,6 +357,7 @@
|
|||
middlewares = ["foobar", "foobar"]
|
||||
service = "foobar"
|
||||
rule = "foobar"
|
||||
ruleSyntax = "foobar"
|
||||
priority = 42
|
||||
[tcp.routers.TCPRouter0.tls]
|
||||
passthrough = true
|
||||
|
@ -371,6 +376,7 @@
|
|||
middlewares = ["foobar", "foobar"]
|
||||
service = "foobar"
|
||||
rule = "foobar"
|
||||
ruleSyntax = "foobar"
|
||||
priority = 42
|
||||
[tcp.routers.TCPRouter1.tls]
|
||||
passthrough = true
|
||||
|
|
|
@ -11,6 +11,7 @@ http:
|
|||
- foobar
|
||||
service: foobar
|
||||
rule: foobar
|
||||
ruleSyntax: foobar
|
||||
priority: 42
|
||||
tls:
|
||||
options: foobar
|
||||
|
@ -33,6 +34,7 @@ http:
|
|||
- foobar
|
||||
service: foobar
|
||||
rule: foobar
|
||||
ruleSyntax: foobar
|
||||
priority: 42
|
||||
tls:
|
||||
options: foobar
|
||||
|
@ -63,7 +65,9 @@ http:
|
|||
maxAge: 42
|
||||
servers:
|
||||
- url: foobar
|
||||
weight: 42
|
||||
- url: foobar
|
||||
weight: 42
|
||||
healthCheck:
|
||||
scheme: foobar
|
||||
mode: foobar
|
||||
|
@ -409,6 +413,7 @@ tcp:
|
|||
- foobar
|
||||
service: foobar
|
||||
rule: foobar
|
||||
ruleSyntax: foobar
|
||||
priority: 42
|
||||
tls:
|
||||
passthrough: true
|
||||
|
@ -432,6 +437,7 @@ tcp:
|
|||
- foobar
|
||||
service: foobar
|
||||
rule: foobar
|
||||
ruleSyntax: foobar
|
||||
priority: 42
|
||||
tls:
|
||||
passthrough: true
|
||||
|
|
|
@ -195,6 +195,10 @@ spec:
|
|||
- name
|
||||
type: object
|
||||
type: array
|
||||
syntax:
|
||||
description: 'Syntax defines the router''s rule syntax. More
|
||||
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax'
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- match
|
||||
|
@ -402,6 +406,10 @@ spec:
|
|||
- port
|
||||
type: object
|
||||
type: array
|
||||
syntax:
|
||||
description: 'Syntax defines the router''s rule syntax. More
|
||||
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax_1'
|
||||
type: string
|
||||
required:
|
||||
- match
|
||||
type: object
|
||||
|
|
|
@ -158,6 +158,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/routers/Router0/middlewares/1` | `foobar` |
|
||||
| `traefik/http/routers/Router0/priority` | `42` |
|
||||
| `traefik/http/routers/Router0/rule` | `foobar` |
|
||||
| `traefik/http/routers/Router0/ruleSyntax` | `foobar` |
|
||||
| `traefik/http/routers/Router0/service` | `foobar` |
|
||||
| `traefik/http/routers/Router0/tls/certResolver` | `foobar` |
|
||||
| `traefik/http/routers/Router0/tls/domains/0/main` | `foobar` |
|
||||
|
@ -173,6 +174,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/routers/Router1/middlewares/1` | `foobar` |
|
||||
| `traefik/http/routers/Router1/priority` | `42` |
|
||||
| `traefik/http/routers/Router1/rule` | `foobar` |
|
||||
| `traefik/http/routers/Router1/ruleSyntax` | `foobar` |
|
||||
| `traefik/http/routers/Router1/service` | `foobar` |
|
||||
| `traefik/http/routers/Router1/tls/certResolver` | `foobar` |
|
||||
| `traefik/http/routers/Router1/tls/domains/0/main` | `foobar` |
|
||||
|
@ -238,7 +240,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/services/Service02/loadBalancer/passHostHeader` | `true` |
|
||||
| `traefik/http/services/Service02/loadBalancer/responseForwarding/flushInterval` | `42s` |
|
||||
| `traefik/http/services/Service02/loadBalancer/servers/0/url` | `foobar` |
|
||||
| `traefik/http/services/Service02/loadBalancer/servers/0/weight` | `42` |
|
||||
| `traefik/http/services/Service02/loadBalancer/servers/1/url` | `foobar` |
|
||||
| `traefik/http/services/Service02/loadBalancer/servers/1/weight` | `42` |
|
||||
| `traefik/http/services/Service02/loadBalancer/serversTransport` | `foobar` |
|
||||
| `traefik/http/services/Service02/loadBalancer/sticky/cookie/httpOnly` | `true` |
|
||||
| `traefik/http/services/Service02/loadBalancer/sticky/cookie/maxAge` | `42` |
|
||||
|
@ -273,6 +277,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/tcp/routers/TCPRouter0/middlewares/1` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/priority` | `42` |
|
||||
| `traefik/tcp/routers/TCPRouter0/rule` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/ruleSyntax` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/service` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/tls/certResolver` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter0/tls/domains/0/main` | `foobar` |
|
||||
|
@ -289,6 +294,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/tcp/routers/TCPRouter1/middlewares/1` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/priority` | `42` |
|
||||
| `traefik/tcp/routers/TCPRouter1/rule` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/ruleSyntax` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/service` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/tls/certResolver` | `foobar` |
|
||||
| `traefik/tcp/routers/TCPRouter1/tls/domains/0/main` | `foobar` |
|
||||
|
|
|
@ -195,6 +195,10 @@ spec:
|
|||
- name
|
||||
type: object
|
||||
type: array
|
||||
syntax:
|
||||
description: 'Syntax defines the router''s rule syntax. More
|
||||
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax'
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- match
|
||||
|
|
|
@ -129,6 +129,10 @@ spec:
|
|||
- port
|
||||
type: object
|
||||
type: array
|
||||
syntax:
|
||||
description: 'Syntax defines the router''s rule syntax. More
|
||||
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax_1'
|
||||
type: string
|
||||
required:
|
||||
- match
|
||||
type: object
|
||||
|
|
|
@ -105,6 +105,9 @@ Activate TLS-ALPN-01 Challenge. (Default: ```true```)
|
|||
`--certificatesresolvers.<name>.tailscale`:
|
||||
Enables Tailscale certificate resolution. (Default: ```true```)
|
||||
|
||||
`--core.defaultrulesyntax`:
|
||||
Defines the rule parser default syntax (v2 or v3) (Default: ```v3```)
|
||||
|
||||
`--entrypoints.<name>`:
|
||||
Entry points definition. (Default: ```false```)
|
||||
|
||||
|
|
|
@ -105,6 +105,9 @@ Activate TLS-ALPN-01 Challenge. (Default: ```true```)
|
|||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_TAILSCALE`:
|
||||
Enables Tailscale certificate resolution. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_CORE_DEFAULTRULESYNTAX`:
|
||||
Defines the rule parser default syntax (v2 or v3) (Default: ```v3```)
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>`:
|
||||
Entry points definition. (Default: ```false```)
|
||||
|
||||
|
|
|
@ -453,5 +453,8 @@
|
|||
[experimental.localPlugins.LocalDescriptor1]
|
||||
moduleName = "foobar"
|
||||
|
||||
[core]
|
||||
defaultRuleSyntax = "foobar"
|
||||
|
||||
[spiffe]
|
||||
workloadAPIAddr = "foobar"
|
||||
|
|
|
@ -486,5 +486,7 @@ experimental:
|
|||
LocalDescriptor1:
|
||||
moduleName: foobar
|
||||
kubernetesGateway: true
|
||||
core:
|
||||
defaultRuleSyntax: foobar
|
||||
spiffe:
|
||||
workloadAPIAddr: foobar
|
||||
|
|
|
@ -515,6 +515,60 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul
|
|||
|
||||
In this configuration, the priority is configured to allow `Router-2` to handle requests with the `foobar.traefik.com` host.
|
||||
|
||||
### RuleSyntax
|
||||
|
||||
In Traefik v3 a new rule syntax has been introduced ([migration guide](../../migration/v2-to-v3.md#router-rule-matchers)).
|
||||
`ruleSyntax` option allows to configure the rule syntax to be used for parsing the rule on a per-router basis.
|
||||
This allows to have heterogeneous router configurations and ease migration.
|
||||
|
||||
??? example "Set rule syntax -- using the [File Provider](../../providers/file.md)"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
## Dynamic configuration
|
||||
http:
|
||||
routers:
|
||||
Router-v3:
|
||||
rule: HostRegexp(`[a-z]+\\.traefik\\.com`)
|
||||
ruleSyntax: v3
|
||||
Router-v2:
|
||||
rule: HostRegexp(`{subdomain:[a-z]+}.traefik.com`)
|
||||
ruleSyntax: v2
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
## Dynamic configuration
|
||||
[http.routers]
|
||||
[http.routers.Router-v3]
|
||||
rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)"
|
||||
ruleSyntax = v3
|
||||
[http.routers.Router-v2]
|
||||
rule = "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)"
|
||||
ruleSyntax = v2
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes traefik.io/v1alpha1"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
routes:
|
||||
# route v3
|
||||
- match: HostRegexp(`[a-z]+\\.traefik\\.com`)
|
||||
syntax: v3
|
||||
kind: Rule
|
||||
|
||||
# route v2
|
||||
- match: HostRegexp(`{subdomain:[a-z]+}.traefik.com`)
|
||||
syntax: v2
|
||||
kind: Rule
|
||||
```
|
||||
|
||||
In this configuration, the ruleSyntax is configured to allow `Router-v2` to use v2 syntax,
|
||||
while for `Router-v3` it is configured to use v3 syntax.
|
||||
|
||||
### Middlewares
|
||||
|
||||
You can attach a list of [middlewares](../../middlewares/overview.md) to each HTTP router.
|
||||
|
@ -1161,6 +1215,60 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul
|
|||
|
||||
In this configuration, the priority is configured so that `Router-1` will handle requests from `192.168.0.12`.
|
||||
|
||||
### RuleSyntax
|
||||
|
||||
In Traefik v3 a new rule syntax has been introduced ([migration guide](../../migration/v2-to-v3.md#router-rule-matchers)).
|
||||
`ruleSyntax` option allows to configure the rule syntax to be used for parsing the rule on a per-router basis.
|
||||
This allows to have heterogeneous router configurations and ease migration.
|
||||
|
||||
??? example "Set rule syntax -- using the [File Provider](../../providers/file.md)"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
## Dynamic configuration
|
||||
tcp:
|
||||
routers:
|
||||
Router-v3:
|
||||
rule: ClientIP(`192.168.0.11`) || ClientIP(`192.168.0.12`)
|
||||
ruleSyntax: v3
|
||||
Router-v2:
|
||||
rule: ClientIP(`192.168.0.11`, `192.168.0.12`)
|
||||
ruleSyntax: v2
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
## Dynamic configuration
|
||||
[tcp.routers]
|
||||
[tcp.routers.Router-v3]
|
||||
rule = "ClientIP(`192.168.0.11`) || ClientIP(`192.168.0.12`)"
|
||||
ruleSyntax = v3
|
||||
[tcp.routers.Router-v2]
|
||||
rule = "ClientIP(`192.168.0.11`, `192.168.0.12`)"
|
||||
ruleSyntax = v2
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes traefik.io/v1alpha1"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRouteTCP
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
routes:
|
||||
# route v3
|
||||
- match: ClientIP(`192.168.0.11`) || ClientIP(`192.168.0.12`)
|
||||
syntax: v3
|
||||
kind: Rule
|
||||
|
||||
# route v2
|
||||
- match: ClientIP(`192.168.0.11`, `192.168.0.12`)
|
||||
syntax: v2
|
||||
kind: Rule
|
||||
```
|
||||
|
||||
In this configuration, the ruleSyntax is configured to allow `Router-v2` to use v2 syntax,
|
||||
while for `Router-v3` it is configured to use v3 syntax.
|
||||
|
||||
### Middlewares
|
||||
|
||||
You can attach a list of [middlewares](../../middlewares/overview.md) to each TCP router.
|
||||
|
|
|
@ -143,6 +143,36 @@ The `url` option point to a specific instance.
|
|||
url = "http://private-ip-server-1/"
|
||||
```
|
||||
|
||||
The `weight` option allows for weighted load balancing on the servers.
|
||||
|
||||
??? example "A Service with Two Servers with Weight -- Using the [File Provider](../../providers/file.md)"
|
||||
|
||||
```yaml tab="YAML"
|
||||
## Dynamic configuration
|
||||
http:
|
||||
services:
|
||||
my-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://private-ip-server-1/"
|
||||
weight: 2
|
||||
- url: "http://private-ip-server-2/"
|
||||
weight: 1
|
||||
|
||||
```
|
||||
|
||||
```toml tab="TOML"
|
||||
## Dynamic configuration
|
||||
[http.services]
|
||||
[http.services.my-service.loadBalancer]
|
||||
[[http.services.my-service.loadBalancer.servers]]
|
||||
url = "http://private-ip-server-1/"
|
||||
weight = 2
|
||||
[[http.services.my-service.loadBalancer.servers]]
|
||||
url = "http://private-ip-server-2/"
|
||||
weight = 1
|
||||
```
|
||||
|
||||
#### Load-balancing
|
||||
|
||||
For now, only round robin load balancing is supported:
|
||||
|
|
12
go.mod
12
go.mod
|
@ -28,7 +28,7 @@ require (
|
|||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b
|
||||
github.com/hashicorp/nomad/api v0.0.0-20240122103822-8a4bd61caf74
|
||||
github.com/http-wasm/http-wasm-host-go v0.5.2
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.7.0
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
|
||||
|
@ -85,12 +85,13 @@ require (
|
|||
golang.org/x/tools v0.14.0
|
||||
google.golang.org/grpc v1.59.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.28.3
|
||||
k8s.io/api v0.28.4
|
||||
k8s.io/apiextensions-apiserver v0.28.3
|
||||
k8s.io/apimachinery v0.28.3
|
||||
k8s.io/client-go v0.28.3
|
||||
k8s.io/apimachinery v0.28.4
|
||||
k8s.io/client-go v0.28.4
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
sigs.k8s.io/controller-runtime v0.16.3
|
||||
sigs.k8s.io/gateway-api v1.0.0
|
||||
)
|
||||
|
||||
|
@ -162,6 +163,7 @@ require (
|
|||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
|
||||
github.com/exoscale/egoscale v0.100.1 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
|
@ -236,6 +238,7 @@ require (
|
|||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
@ -254,7 +257,6 @@ require (
|
|||
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/onsi/gomega v1.27.10 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
|
|
28
go.sum
28
go.sum
|
@ -114,6 +114,8 @@ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJ
|
|||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
|
@ -302,6 +304,8 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
|
|||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
|
||||
github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/exoscale/egoscale v0.100.1 h1:iXsV1Ei7daqe/6FYSCSDyrFs1iUG1l1X9qNh2uMw6z0=
|
||||
github.com/exoscale/egoscale v0.100.1/go.mod h1:BAb9p4rmyU+Wl400CJZO5270H2sXtdsZjLcm5xMKkz4=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
|
@ -358,6 +362,8 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
|||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
|
||||
github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
|
@ -592,8 +598,8 @@ github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/
|
|||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
|
||||
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b h1:R1UDhkwGltpSPY9bCBBxIMQd+NY9BkN0vFHnJo/8o8w=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b/go.mod h1:ijDwa6o1uG1jFSq6kERiX2PamKGpZzTmo0XOFNeFZgw=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20240122103822-8a4bd61caf74 h1:Q+WuGTnZkL2cJ7yNsg4Go4GNnRkcahGLiQP/WD41TTA=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20240122103822-8a4bd61caf74/go.mod h1:ijDwa6o1uG1jFSq6kERiX2PamKGpZzTmo0XOFNeFZgw=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
|
||||
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
||||
|
@ -796,6 +802,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
|||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
|
@ -1485,6 +1493,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
|
@ -1619,14 +1629,14 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
|
||||
k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc=
|
||||
k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY=
|
||||
k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0=
|
||||
k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08=
|
||||
k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc=
|
||||
k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A=
|
||||
k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8=
|
||||
k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4=
|
||||
k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo=
|
||||
k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=
|
||||
k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=
|
||||
k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=
|
||||
k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=
|
||||
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
||||
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
|
||||
|
@ -1640,6 +1650,8 @@ nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0
|
|||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
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.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
|
||||
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
|
||||
sigs.k8s.io/gateway-api v1.0.0 h1:iPTStSv41+d9p0xFydll6d7f7MOBGuqXM6p2/zVYMAs=
|
||||
sigs.k8s.io/gateway-api v1.0.0/go.mod h1:4cUgr0Lnp5FZ0Cdq8FdRwCvpiWws7LVhLHGIudLlf4c=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -55,6 +56,56 @@ func (s *DockerSuite) TestSimpleConfiguration() {
|
|||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestWRRServer() {
|
||||
tempObjects := struct {
|
||||
DockerHost string
|
||||
DefaultRule string
|
||||
}{
|
||||
DockerHost: s.getDockerHost(),
|
||||
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
|
||||
}
|
||||
|
||||
file := s.adaptFile("fixtures/docker/simple.toml", tempObjects)
|
||||
|
||||
s.composeUp()
|
||||
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
whoami1IP := s.getComposeServiceIP("wrr-server")
|
||||
whoami2IP := s.getComposeServiceIP("wrr-server2")
|
||||
|
||||
// Expected a 404 as we did not configure anything
|
||||
err := try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("wrr-server"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
repartition := map[string]int{}
|
||||
for i := 0; i < 4; i++ {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
|
||||
req.Host = "my.wrr.host"
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(s.T(), err)
|
||||
assert.Equal(s.T(), http.StatusOK, response.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
if strings.Contains(string(body), whoami1IP) {
|
||||
repartition[whoami1IP]++
|
||||
}
|
||||
if strings.Contains(string(body), whoami2IP) {
|
||||
repartition[whoami2IP]++
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(s.T(), 3, repartition[whoami1IP])
|
||||
assert.Equal(s.T(), 1, repartition[whoami2IP])
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestDefaultDockerContainers() {
|
||||
tempObjects := struct {
|
||||
DockerHost string
|
||||
|
|
|
@ -195,6 +195,10 @@ spec:
|
|||
- name
|
||||
type: object
|
||||
type: array
|
||||
syntax:
|
||||
description: 'Syntax defines the router''s rule syntax. More
|
||||
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax'
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- match
|
||||
|
@ -402,6 +406,10 @@ spec:
|
|||
- port
|
||||
type: object
|
||||
type: array
|
||||
syntax:
|
||||
description: 'Syntax defines the router''s rule syntax. More
|
||||
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax_1'
|
||||
type: string
|
||||
required:
|
||||
- match
|
||||
type: object
|
||||
|
|
21
integration/fixtures/k8s_gateway_conformance.toml
Normal file
21
integration/fixtures/k8s_gateway_conformance.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
noColor = true
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[experimental]
|
||||
kubernetesGateway = true
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":80"
|
||||
[entryPoints.websecure]
|
||||
address = ":443"
|
||||
|
||||
[providers.kubernetesGateway]
|
41
integration/fixtures/with_default_rule_syntax.toml
Normal file
41
integration/fixtures/with_default_rule_syntax.toml
Normal file
|
@ -0,0 +1,41 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[core]
|
||||
defaultRuleSyntax = "v2"
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
noColor = true
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.router1]
|
||||
service = "service1"
|
||||
rule = "PathPrefix(`/foo`, `/bar`)"
|
||||
|
||||
[http.routers.router2]
|
||||
service = "service1"
|
||||
rule = "QueryRegexp(`foo`, `bar`)"
|
||||
|
||||
[http.routers.router3]
|
||||
service = "service1"
|
||||
rule = "PathPrefix(`/foo`, `/bar`)"
|
||||
ruleSyntax = "v3"
|
||||
|
||||
[http.services]
|
||||
[http.services.service1]
|
||||
[http.services.service1.loadBalancer]
|
||||
[http.services.service1.loadBalancer.servers]
|
38
integration/fixtures/without_default_rule_syntax.toml
Normal file
38
integration/fixtures/without_default_rule_syntax.toml
Normal file
|
@ -0,0 +1,38 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
noColor = true
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.router1]
|
||||
service = "service1"
|
||||
rule = "PathPrefix(`/foo`) || PathPrefix(`/bar`)"
|
||||
|
||||
[http.routers.router2]
|
||||
service = "service1"
|
||||
rule = "PathPrefix(`/foo`, `/bar`)"
|
||||
|
||||
[http.routers.router3]
|
||||
service = "service1"
|
||||
rule = "QueryRegexp(`foo`, `bar`)"
|
||||
ruleSyntax = "v2"
|
||||
|
||||
[http.services]
|
||||
[http.services.service1]
|
||||
[http.services.service1.loadBalancer]
|
||||
[http.services.service1.loadBalancer.servers]
|
35
integration/fixtures/wrr_server.toml
Normal file
35
integration/fixtures/wrr_server.toml
Normal file
|
@ -0,0 +1,35 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
noColor = true
|
||||
|
||||
[entryPoints]
|
||||
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.router]
|
||||
service = "service1"
|
||||
rule = "Path(`/whoami`)"
|
||||
|
||||
[http.services]
|
||||
|
||||
[http.services.service1.loadBalancer]
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "{{ .Server1 }}"
|
||||
weight = 3
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "{{ .Server2 }}"
|
||||
|
|
@ -35,7 +35,13 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var showLog = flag.Bool("tlog", false, "always show Traefik logs")
|
||||
var (
|
||||
showLog = flag.Bool("tlog", false, "always show Traefik logs")
|
||||
k8sConformance = flag.Bool("k8sConformance", false, "run K8s Gateway API conformance test")
|
||||
k8sConformanceRunTest = flag.String("k8sConformanceRunTest", "", "run a specific K8s Gateway API conformance test")
|
||||
)
|
||||
|
||||
const tailscaleSecretFilePath = "tailscale.secret"
|
||||
|
||||
type composeConfig struct {
|
||||
Services map[string]composeService `yaml:"services"`
|
||||
|
@ -99,6 +105,11 @@ func (s *BaseSuite) displayTraefikLogFile(path string) {
|
|||
}
|
||||
|
||||
func (s *BaseSuite) SetupSuite() {
|
||||
if isDockerDesktop(context.Background(), s.T()) {
|
||||
_, err := os.Stat(tailscaleSecretFilePath)
|
||||
require.NoError(s.T(), err, "Tailscale need to be configured when running integration tests with Docker Desktop: (https://doc.traefik.io/traefik/v2.11/contributing/building-testing/#testing)")
|
||||
}
|
||||
|
||||
// configure default standard log.
|
||||
stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags)
|
||||
// TODO
|
||||
|
@ -124,7 +135,7 @@ func (s *BaseSuite) SetupSuite() {
|
|||
s.hostIP = "172.31.42.1"
|
||||
if isDockerDesktop(ctx, s.T()) {
|
||||
s.hostIP = getDockerDesktopHostIP(ctx, s.T())
|
||||
s.setupVPN("tailscale.secret")
|
||||
s.setupVPN(tailscaleSecretFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
229
integration/k8s_conformance_test.go
Normal file
229
integration/k8s_conformance_test.go
Normal file
|
@ -0,0 +1,229 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/traefik/traefik/v3/integration/try"
|
||||
"github.com/traefik/traefik/v3/pkg/version"
|
||||
"gopkg.in/yaml.v3"
|
||||
ktypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
kclientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||
conformanceV1alpha1 "sigs.k8s.io/gateway-api/conformance/apis/v1alpha1"
|
||||
"sigs.k8s.io/gateway-api/conformance/tests"
|
||||
"sigs.k8s.io/gateway-api/conformance/utils/config"
|
||||
ksuite "sigs.k8s.io/gateway-api/conformance/utils/suite"
|
||||
)
|
||||
|
||||
// K8sConformanceSuite tests suite.
|
||||
type K8sConformanceSuite struct{ BaseSuite }
|
||||
|
||||
func TestK8sConformanceSuite(t *testing.T) {
|
||||
suite.Run(t, new(K8sConformanceSuite))
|
||||
}
|
||||
|
||||
func (s *K8sConformanceSuite) SetupSuite() {
|
||||
s.BaseSuite.SetupSuite()
|
||||
|
||||
s.createComposeProject("k8s")
|
||||
s.composeUp()
|
||||
|
||||
abs, err := filepath.Abs("./fixtures/k8s/config.skip/kubeconfig.yaml")
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.Do(60*time.Second, func() error {
|
||||
_, err := os.Stat(abs)
|
||||
return err
|
||||
})
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
data, err := os.ReadFile(abs)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
content := strings.ReplaceAll(string(data), "https://server:6443", fmt.Sprintf("https://%s", net.JoinHostPort(s.getComposeServiceIP("server"), "6443")))
|
||||
|
||||
err = os.WriteFile(abs, []byte(content), 0o644)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = os.Setenv("KUBECONFIG", abs)
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *K8sConformanceSuite) TearDownSuite() {
|
||||
s.BaseSuite.TearDownSuite()
|
||||
|
||||
generatedFiles := []string{
|
||||
"./fixtures/k8s/config.skip/kubeconfig.yaml",
|
||||
"./fixtures/k8s/config.skip/k3s.log",
|
||||
"./fixtures/k8s/rolebindings.yaml",
|
||||
"./fixtures/k8s/ccm.yaml",
|
||||
}
|
||||
|
||||
for _, filename := range generatedFiles {
|
||||
if err := os.Remove(filename); err != nil {
|
||||
log.Warn().Err(err).Send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
||||
if !*k8sConformance {
|
||||
s.T().Skip("Skip because it can take a long time to execute. To enable pass the `k8sConformance` flag.")
|
||||
}
|
||||
|
||||
configFromFlags, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG"))
|
||||
if err != nil {
|
||||
s.T().Fatal(err)
|
||||
}
|
||||
|
||||
kClient, err := client.New(configFromFlags, client.Options{})
|
||||
if err != nil {
|
||||
s.T().Fatalf("Error initializing Kubernetes client: %v", err)
|
||||
}
|
||||
|
||||
kClientSet, err := kclientset.NewForConfig(configFromFlags)
|
||||
if err != nil {
|
||||
s.T().Fatal(err)
|
||||
}
|
||||
|
||||
err = gatev1alpha2.AddToScheme(kClient.Scheme())
|
||||
require.NoError(s.T(), err)
|
||||
err = gatev1beta1.AddToScheme(kClient.Scheme())
|
||||
require.NoError(s.T(), err)
|
||||
err = gatev1.AddToScheme(kClient.Scheme())
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
s.traefikCmd(withConfigFile("fixtures/k8s_gateway_conformance.toml"))
|
||||
|
||||
// Wait for traefik to start
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/entrypoints", 10*time.Second, try.BodyContains(`"name":"web"`))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.Do(10*time.Second, func() error {
|
||||
gwc := &gatev1.GatewayClass{}
|
||||
err := kClient.Get(context.Background(), ktypes.NamespacedName{Name: "my-gateway-class"}, gwc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetching GatewayClass: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
opts := ksuite.Options{
|
||||
Client: kClient,
|
||||
RestConfig: configFromFlags,
|
||||
Clientset: kClientSet,
|
||||
GatewayClassName: "my-gateway-class",
|
||||
Debug: true,
|
||||
CleanupBaseResources: true,
|
||||
TimeoutConfig: config.TimeoutConfig{
|
||||
CreateTimeout: 5 * time.Second,
|
||||
DeleteTimeout: 5 * time.Second,
|
||||
GetTimeout: 5 * time.Second,
|
||||
GatewayMustHaveAddress: 5 * time.Second,
|
||||
GatewayMustHaveCondition: 5 * time.Second,
|
||||
GatewayStatusMustHaveListeners: 10 * time.Second,
|
||||
GatewayListenersMustHaveCondition: 5 * time.Second,
|
||||
GWCMustBeAccepted: 60 * time.Second, // Pod creation in k3s cluster can be long.
|
||||
HTTPRouteMustNotHaveParents: 5 * time.Second,
|
||||
HTTPRouteMustHaveCondition: 5 * time.Second,
|
||||
TLSRouteMustHaveCondition: 5 * time.Second,
|
||||
RouteMustHaveParents: 5 * time.Second,
|
||||
ManifestFetchTimeout: 5 * time.Second,
|
||||
MaxTimeToConsistency: 5 * time.Second,
|
||||
NamespacesMustBeReady: 60 * time.Second, // Pod creation in k3s cluster can be long.
|
||||
RequestTimeout: 5 * time.Second,
|
||||
LatestObservedGenerationSet: 5 * time.Second,
|
||||
RequiredConsecutiveSuccesses: 0,
|
||||
},
|
||||
SupportedFeatures: sets.New[ksuite.SupportedFeature]().
|
||||
Insert(ksuite.GatewayCoreFeatures.UnsortedList()...),
|
||||
EnableAllSupportedFeatures: false,
|
||||
RunTest: *k8sConformanceRunTest,
|
||||
// Until the feature are all supported, following tests are skipped.
|
||||
SkipTests: []string{
|
||||
"HTTPExactPathMatching",
|
||||
"HTTPRouteHostnameIntersection",
|
||||
"GatewaySecretReferenceGrantAllInNamespace",
|
||||
"HTTPRouteListenerHostnameMatching",
|
||||
"HTTPRouteRequestHeaderModifier",
|
||||
"GatewaySecretInvalidReferenceGrant",
|
||||
"GatewayClassObservedGenerationBump",
|
||||
"HTTPRouteInvalidNonExistentBackendRef",
|
||||
"GatewayWithAttachedRoutes",
|
||||
"HTTPRouteCrossNamespace",
|
||||
"HTTPRouteDisallowedKind",
|
||||
"HTTPRouteInvalidReferenceGrant",
|
||||
"HTTPRouteObservedGenerationBump",
|
||||
"GatewayInvalidRouteKind",
|
||||
"TLSRouteSimpleSameNamespace",
|
||||
"TLSRouteInvalidReferenceGrant",
|
||||
"HTTPRouteInvalidCrossNamespaceParentRef",
|
||||
"HTTPRouteInvalidParentRefNotMatchingSectionName",
|
||||
"GatewaySecretReferenceGrantSpecific",
|
||||
"GatewayModifyListeners",
|
||||
"GatewaySecretMissingReferenceGrant",
|
||||
"GatewayInvalidTLSConfiguration",
|
||||
"HTTPRouteInvalidCrossNamespaceBackendRef",
|
||||
"HTTPRouteMatchingAcrossRoutes",
|
||||
"HTTPRoutePartiallyInvalidViaInvalidReferenceGrant",
|
||||
"HTTPRouteRedirectHostAndStatus",
|
||||
"HTTPRouteInvalidBackendRefUnknownKind",
|
||||
"HTTPRoutePathMatchOrder",
|
||||
"HTTPRouteSimpleSameNamespace",
|
||||
"HTTPRouteMatching",
|
||||
"HTTPRouteHeaderMatching",
|
||||
"HTTPRouteReferenceGrant",
|
||||
},
|
||||
}
|
||||
|
||||
cSuite, err := ksuite.NewExperimentalConformanceTestSuite(ksuite.ExperimentalConformanceOptions{
|
||||
Options: opts,
|
||||
Implementation: conformanceV1alpha1.Implementation{
|
||||
Organization: "traefik",
|
||||
Project: "traefik",
|
||||
URL: "https://traefik.io/",
|
||||
Version: version.Version,
|
||||
Contact: []string{"@traefik/maintainers"},
|
||||
},
|
||||
ConformanceProfiles: sets.New[ksuite.ConformanceProfileName](
|
||||
ksuite.HTTPConformanceProfileName,
|
||||
ksuite.TLSConformanceProfileName,
|
||||
),
|
||||
})
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
cSuite.Setup(s.T())
|
||||
err = cSuite.Run(s.T(), tests.ConformanceTests)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
report, err := cSuite.Report()
|
||||
require.NoError(s.T(), err, "failed generating conformance report")
|
||||
|
||||
report.GatewayAPIVersion = "1.0.0"
|
||||
|
||||
rawReport, err := yaml.Marshal(report)
|
||||
require.NoError(s.T(), err)
|
||||
s.T().Logf("Conformance report:\n%s", string(rawReport))
|
||||
|
||||
require.NoError(s.T(), os.MkdirAll("./conformance-reports", 0o755))
|
||||
outFile := filepath.Join("conformance-reports", fmt.Sprintf("traefik-traefik-%d.yaml", time.Now().UnixNano()))
|
||||
require.NoError(s.T(), os.WriteFile(outFile, rawReport, 0o600))
|
||||
s.T().Logf("Report written to: %s", outFile)
|
||||
}
|
|
@ -36,3 +36,14 @@ services:
|
|||
labels:
|
||||
traefik.http.Routers.Super.Rule: Host(`my.super.host`)
|
||||
traefik.http.Services.powpow.LoadBalancer.server.Port: 2375
|
||||
|
||||
wrr-server:
|
||||
image: traefik/whoami
|
||||
labels:
|
||||
traefik.http.Routers.wrr-server.Rule: Host(`my.wrr.host`)
|
||||
traefik.http.Services.wrr-server.LoadBalancer.server.Weight: 4
|
||||
wrr-server2:
|
||||
image: traefik/whoami
|
||||
labels:
|
||||
traefik.http.Routers.wrr-server.Rule: Host(`my.wrr.host`)
|
||||
traefik.http.Services.wrr-server.LoadBalancer.server.Weight: 1
|
||||
|
|
|
@ -659,6 +659,66 @@ func (s *SimpleSuite) TestSimpleConfigurationHostRequestTrailingPeriod() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestWithDefaultRuleSyntax() {
|
||||
file := s.adaptFile("fixtures/with_default_rule_syntax.toml", struct{}{})
|
||||
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// router1 has no error
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router1@file", 1*time.Second, try.BodyContains(`"status":"enabled"`))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/notfound", 1*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/foo", 1*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/bar", 1*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// router2 has an error because it uses the wrong rule syntax (v3 instead of v2)
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router2@file", 1*time.Second, try.BodyContains("error while parsing rule QueryRegexp(`foo`, `bar`): unsupported function: QueryRegexp"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// router3 has an error because it uses the wrong rule syntax (v2 instead of v3)
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1*time.Second, try.BodyContains("error while adding rule PathPrefix: unexpected number of parameters; got 2, expected one of [1]"))
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestWithoutDefaultRuleSyntax() {
|
||||
file := s.adaptFile("fixtures/without_default_rule_syntax.toml", struct{}{})
|
||||
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// router1 has no error
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router1@file", 1*time.Second, try.BodyContains(`"status":"enabled"`))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/notfound", 1*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/foo", 1*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/bar", 1*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// router2 has an error because it uses the wrong rule syntax (v3 instead of v2)
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router2@file", 1*time.Second, try.BodyContains("error while adding rule PathPrefix: unexpected number of parameters; got 2, expected one of [1]"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// router2 has an error because it uses the wrong rule syntax (v2 instead of v3)
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1*time.Second, try.BodyContains("error while parsing rule QueryRegexp(`foo`, `bar`): unsupported function: QueryRegexp"))
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestRouterConfigErrors() {
|
||||
file := s.adaptFile("fixtures/router_errors.toml", struct{}{})
|
||||
|
||||
|
@ -749,6 +809,49 @@ func (s *SimpleSuite) TestUDPServiceConfigErrors() {
|
|||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestWRRServer() {
|
||||
s.createComposeProject("base")
|
||||
|
||||
s.composeUp()
|
||||
defer s.composeDown()
|
||||
|
||||
whoami1IP := s.getComposeServiceIP("whoami1")
|
||||
whoami2IP := s.getComposeServiceIP("whoami2")
|
||||
|
||||
file := s.adaptFile("fixtures/wrr_server.toml", struct {
|
||||
Server1 string
|
||||
Server2 string
|
||||
}{Server1: "http://" + whoami1IP, Server2: "http://" + whoami2IP})
|
||||
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
repartition := map[string]int{}
|
||||
for i := 0; i < 4; i++ {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(s.T(), err)
|
||||
assert.Equal(s.T(), http.StatusOK, response.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
if strings.Contains(string(body), whoami1IP) {
|
||||
repartition[whoami1IP]++
|
||||
}
|
||||
if strings.Contains(string(body), whoami2IP) {
|
||||
repartition[whoami2IP]++
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(s.T(), 3, repartition[whoami1IP])
|
||||
assert.Equal(s.T(), 1, repartition[whoami2IP])
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestWRR() {
|
||||
s.createComposeProject("base")
|
||||
|
||||
|
|
5
integration/testdata/rawdata-gateway.json
vendored
5
integration/testdata/rawdata-gateway.json
vendored
|
@ -34,6 +34,7 @@
|
|||
],
|
||||
"service": "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": 31,
|
||||
"status": "enabled",
|
||||
"using": [
|
||||
|
@ -46,6 +47,7 @@
|
|||
],
|
||||
"service": "default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06-wrr",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": 31,
|
||||
"tls": {},
|
||||
"status": "enabled",
|
||||
|
@ -152,6 +154,7 @@
|
|||
],
|
||||
"service": "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
"rule": "HostSNI(`*`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": -1,
|
||||
"status": "enabled",
|
||||
"using": [
|
||||
|
@ -164,6 +167,7 @@
|
|||
],
|
||||
"service": "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr-0",
|
||||
"rule": "HostSNI(`*`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": -1,
|
||||
"tls": {
|
||||
"passthrough": false
|
||||
|
@ -179,6 +183,7 @@
|
|||
],
|
||||
"service": "default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr-0",
|
||||
"rule": "HostSNI(`foo.bar`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": 18,
|
||||
"tls": {
|
||||
"passthrough": true
|
||||
|
|
541
pkg/cli/deprecation.go
Normal file
541
pkg/cli/deprecation.go
Normal file
|
@ -0,0 +1,541 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/paerser/cli"
|
||||
"github.com/traefik/paerser/flag"
|
||||
"github.com/traefik/paerser/parser"
|
||||
)
|
||||
|
||||
type DeprecationLoader struct{}
|
||||
|
||||
func (d DeprecationLoader) Load(args []string, cmd *cli.Command) (bool, error) {
|
||||
if logDeprecation(cmd.Configuration, args) {
|
||||
return true, errors.New("incompatible deprecated static option found")
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// logDeprecation prints deprecation hints and returns whether incompatible deprecated options need to be removed.
|
||||
func logDeprecation(traefikConfiguration interface{}, args []string) bool {
|
||||
// This part doesn't handle properly a flag defined like this:
|
||||
// --accesslog true
|
||||
// where `true` could be considered as a new argument.
|
||||
// This is not really an issue with the deprecation loader since it will filter the unknown nodes later in this
|
||||
// function.
|
||||
for i, arg := range args {
|
||||
if !strings.Contains(arg, "=") {
|
||||
args[i] = arg + "=true"
|
||||
}
|
||||
}
|
||||
|
||||
labels, err := flag.Parse(args, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("deprecated static options analysis failed")
|
||||
return false
|
||||
}
|
||||
|
||||
node, err := parser.DecodeToNode(labels, "traefik")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("deprecated static options analysis failed")
|
||||
return false
|
||||
}
|
||||
|
||||
if node != nil && len(node.Children) > 0 {
|
||||
config := &configuration{}
|
||||
filterUnknownNodes(reflect.TypeOf(config), node)
|
||||
|
||||
if len(node.Children) > 0 {
|
||||
// Telling parser to look for the label struct tag to allow empty values.
|
||||
err = parser.AddMetadata(config, node, parser.MetadataOpts{TagName: "label"})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("deprecated static options analysis failed")
|
||||
return false
|
||||
}
|
||||
|
||||
err = parser.Fill(config, node, parser.FillerOpts{})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("deprecated static options analysis failed")
|
||||
return false
|
||||
}
|
||||
|
||||
if config.deprecationNotice(log.With().Str("loader", "FLAG").Logger()) {
|
||||
return true
|
||||
}
|
||||
|
||||
// No further deprecation parsing and logging,
|
||||
// as args configuration contains at least one deprecated option.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// FILE
|
||||
ref, err := flag.Parse(args, traefikConfiguration)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("deprecated static options analysis failed")
|
||||
return false
|
||||
}
|
||||
|
||||
configFileFlag := "traefik.configfile"
|
||||
if _, ok := ref["traefik.configFile"]; ok {
|
||||
configFileFlag = "traefik.configFile"
|
||||
}
|
||||
|
||||
config := &configuration{}
|
||||
_, err = loadConfigFiles(ref[configFileFlag], config)
|
||||
|
||||
if err == nil {
|
||||
if config.deprecationNotice(log.With().Str("loader", "FILE").Logger()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
config = &configuration{}
|
||||
l := EnvLoader{}
|
||||
_, err = l.Load(os.Args, &cli.Command{
|
||||
Configuration: config,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
if config.deprecationNotice(log.With().Str("loader", "ENV").Logger()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func filterUnknownNodes(fType reflect.Type, node *parser.Node) bool {
|
||||
var children []*parser.Node
|
||||
for _, child := range node.Children {
|
||||
if hasKnownNodes(fType, child) {
|
||||
children = append(children, child)
|
||||
}
|
||||
}
|
||||
|
||||
node.Children = children
|
||||
return len(node.Children) > 0
|
||||
}
|
||||
|
||||
func hasKnownNodes(rootType reflect.Type, node *parser.Node) bool {
|
||||
rType := rootType
|
||||
if rootType.Kind() == reflect.Pointer {
|
||||
rType = rootType.Elem()
|
||||
}
|
||||
|
||||
// unstructured type fitting anything, considering the current node as known.
|
||||
if rType.Kind() == reflect.Map && rType.Elem().Kind() == reflect.Interface {
|
||||
return true
|
||||
}
|
||||
|
||||
// unstructured type fitting anything, considering the current node as known.
|
||||
if rType.Kind() == reflect.Interface {
|
||||
return true
|
||||
}
|
||||
|
||||
// find matching field in struct type.
|
||||
field, b := findTypedField(rType, node)
|
||||
if !b {
|
||||
return b
|
||||
}
|
||||
|
||||
if len(node.Children) > 0 {
|
||||
return filterUnknownNodes(field.Type, node)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func findTypedField(rType reflect.Type, node *parser.Node) (reflect.StructField, bool) {
|
||||
// avoid panicking.
|
||||
if rType.Kind() != reflect.Struct {
|
||||
return reflect.StructField{}, false
|
||||
}
|
||||
|
||||
for i := 0; i < rType.NumField(); i++ {
|
||||
cField := rType.Field(i)
|
||||
|
||||
// ignore unexported fields.
|
||||
if cField.PkgPath == "" {
|
||||
if strings.EqualFold(cField.Name, node.Name) {
|
||||
node.FieldName = cField.Name
|
||||
return cField, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.StructField{}, false
|
||||
}
|
||||
|
||||
// configuration holds the static configuration removed/deprecated options.
|
||||
type configuration struct {
|
||||
Experimental *experimental `json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Pilot map[string]any `json:"pilot,omitempty" toml:"pilot,omitempty" yaml:"pilot,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Providers *providers `json:"providers,omitempty" toml:"providers,omitempty" yaml:"providers,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Tracing *tracing `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (c *configuration) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
if c.Pilot != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Pilot configuration has been removed in v3, please remove all Pilot-related static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#pilot")
|
||||
}
|
||||
|
||||
incompatibleExperimental := c.Experimental.deprecationNotice(logger)
|
||||
incompatibleProviders := c.Providers.deprecationNotice(logger)
|
||||
incompatibleTracing := c.Tracing.deprecationNotice(logger)
|
||||
return incompatible || incompatibleExperimental || incompatibleProviders || incompatibleTracing
|
||||
}
|
||||
|
||||
type providers struct {
|
||||
Docker *docker `json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Swarm *swarm `json:"swarm,omitempty" toml:"swarm,omitempty" yaml:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Consul *consul `json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
ConsulCatalog *consulCatalog `json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Nomad *nomad `json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Marathon map[string]any `json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Rancher map[string]any `json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
ETCD *etcd `json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Redis *redis `json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
HTTP *http `json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (p *providers) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
|
||||
if p.Marathon != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Marathon provider has been removed in v3, please remove all Marathon-related static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#marathon-provider")
|
||||
}
|
||||
|
||||
if p.Rancher != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Rancher provider has been removed in v3, please remove all Rancher-related static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#rancher-v1-provider")
|
||||
}
|
||||
|
||||
dockerIncompatible := p.Docker.deprecationNotice(logger)
|
||||
consulIncompatible := p.Consul.deprecationNotice(logger)
|
||||
consulCatalogIncompatible := p.ConsulCatalog.deprecationNotice(logger)
|
||||
nomadIncompatible := p.Nomad.deprecationNotice(logger)
|
||||
swarmIncompatible := p.Swarm.deprecationNotice(logger)
|
||||
etcdIncompatible := p.ETCD.deprecationNotice(logger)
|
||||
redisIncompatible := p.Redis.deprecationNotice(logger)
|
||||
httpIncompatible := p.HTTP.deprecationNotice(logger)
|
||||
return incompatible ||
|
||||
dockerIncompatible ||
|
||||
consulIncompatible ||
|
||||
consulCatalogIncompatible ||
|
||||
nomadIncompatible ||
|
||||
swarmIncompatible ||
|
||||
etcdIncompatible ||
|
||||
redisIncompatible ||
|
||||
httpIncompatible
|
||||
}
|
||||
|
||||
type tls struct {
|
||||
CAOptional *bool `json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty"`
|
||||
}
|
||||
|
||||
type docker struct {
|
||||
SwarmMode *bool `json:"swarmMode,omitempty" toml:"swarmMode,omitempty" yaml:"swarmMode,omitempty"`
|
||||
TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (d *docker) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if d == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
|
||||
if d.SwarmMode != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Docker provider `swarmMode` option has been removed in v3, please use the Swarm Provider instead." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#docker-docker-swarm")
|
||||
}
|
||||
|
||||
if d.TLS != nil && d.TLS.CAOptional != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Docker provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." +
|
||||
"Please remove all occurrences from the static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional")
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
||||
|
||||
type swarm struct {
|
||||
TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (s *swarm) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
|
||||
if s.TLS != nil && s.TLS.CAOptional != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Swarm provider `tls.CAOptional` option does not exist, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." +
|
||||
"Please remove all occurrences from the static configuration for Traefik to start.")
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
||||
|
||||
type etcd struct {
|
||||
TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (e *etcd) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if e == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
|
||||
if e.TLS != nil && e.TLS.CAOptional != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("ETCD provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." +
|
||||
"Please remove all occurrences from the static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional_3")
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
||||
|
||||
type redis struct {
|
||||
TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (r *redis) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
|
||||
if r.TLS != nil && r.TLS.CAOptional != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Redis provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." +
|
||||
"Please remove all occurrences from the static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional_4")
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
||||
|
||||
type consul struct {
|
||||
Namespace *string `json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (c *consul) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
|
||||
if c.Namespace != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Consul provider `namespace` option has been removed, please use the `namespaces` option instead." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#consul-provider")
|
||||
}
|
||||
|
||||
if c.TLS != nil && c.TLS.CAOptional != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Consul provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." +
|
||||
"Please remove all occurrences from the static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional_1")
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
||||
|
||||
type consulCatalog struct {
|
||||
Namespace *string `json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
Endpoint *endpointConfig `json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
type endpointConfig struct {
|
||||
TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty"`
|
||||
}
|
||||
|
||||
func (c *consulCatalog) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
|
||||
if c.Namespace != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("ConsulCatalog provider `namespace` option has been removed, please use the `namespaces` option instead." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#consulcatalog-provider")
|
||||
}
|
||||
|
||||
if c.Endpoint != nil && c.Endpoint.TLS != nil && c.Endpoint.TLS.CAOptional != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("ConsulCatalog provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." +
|
||||
"Please remove all occurrences from the static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#endpointtlscaoptional")
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
||||
|
||||
type nomad struct {
|
||||
Namespace *string `json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
Endpoint *endpointConfig `json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (n *nomad) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
|
||||
if n.Namespace != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Nomad provider `namespace` option has been removed, please use the `namespaces` option instead." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#nomad-provider")
|
||||
}
|
||||
|
||||
if n.Endpoint != nil && n.Endpoint.TLS != nil && n.Endpoint.TLS.CAOptional != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Nomad provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." +
|
||||
"Please remove all occurrences from the static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#endpointtlscaoptional_1")
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
||||
|
||||
type http struct {
|
||||
TLS *tls `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (h *http) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if h == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var incompatible bool
|
||||
|
||||
if h.TLS != nil && h.TLS.CAOptional != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("HTTP provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." +
|
||||
"Please remove all occurrences from the static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tlscaoptional_2")
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
||||
|
||||
type experimental struct {
|
||||
HTTP3 *bool `json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty"`
|
||||
}
|
||||
|
||||
func (e *experimental) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if e == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if e.HTTP3 != nil {
|
||||
logger.Error().Msg("HTTP3 is not an experimental feature in v3 and the associated enablement has been removed." +
|
||||
"Please remove its usage from the static configuration for Traefik to start." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#http3-experimental-configuration")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type tracing struct {
|
||||
SpanNameLimit *int `json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty"`
|
||||
Jaeger map[string]any `json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Zipkin map[string]any `json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Datadog map[string]any `json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Instana map[string]any `json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Haystack map[string]any `json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
Elastic map[string]any `json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
}
|
||||
|
||||
func (t *tracing) deprecationNotice(logger zerolog.Logger) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
var incompatible bool
|
||||
if t.SpanNameLimit != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("SpanNameLimit option for Tracing has been removed in v3, as Span names are now of a fixed length." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing")
|
||||
}
|
||||
|
||||
if t.Jaeger != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Jaeger Tracing backend has been removed in v3, please remove all Jaeger-related Tracing static configuration for Traefik to start." +
|
||||
"In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing")
|
||||
}
|
||||
|
||||
if t.Zipkin != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Zipkin Tracing backend has been removed in v3, please remove all Zipkin-related Tracing static configuration for Traefik to start." +
|
||||
"In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing")
|
||||
}
|
||||
|
||||
if t.Datadog != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Datadog Tracing backend has been removed in v3, please remove all Datadog-related Tracing static configuration for Traefik to start." +
|
||||
"In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing")
|
||||
}
|
||||
|
||||
if t.Instana != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Instana Tracing backend has been removed in v3, please remove all Instana-related Tracing static configuration for Traefik to start." +
|
||||
"In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing")
|
||||
}
|
||||
|
||||
if t.Haystack != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Haystack Tracing backend has been removed in v3, please remove all Haystack-related Tracing static configuration for Traefik to start." +
|
||||
"In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing")
|
||||
}
|
||||
|
||||
if t.Elastic != nil {
|
||||
incompatible = true
|
||||
logger.Error().Msg("Elastic Tracing backend has been removed in v3, please remove all Elastic-related Tracing static configuration for Traefik to start." +
|
||||
"In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." +
|
||||
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#tracing")
|
||||
}
|
||||
|
||||
return incompatible
|
||||
}
|
404
pkg/cli/deprecation_test.go
Normal file
404
pkg/cli/deprecation_test.go
Normal file
|
@ -0,0 +1,404 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/paerser/cli"
|
||||
"github.com/traefik/traefik/v3/cmd"
|
||||
)
|
||||
|
||||
func ptr[T any](t T) *T {
|
||||
return &t
|
||||
}
|
||||
|
||||
func TestDeprecationNotice(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
config configuration
|
||||
}{
|
||||
{
|
||||
desc: "Docker provider swarmMode option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Docker: &docker{
|
||||
SwarmMode: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Docker provider tls.CAOptional option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Docker: &docker{
|
||||
TLS: &tls{
|
||||
CAOptional: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Swarm provider tls.CAOptional option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Swarm: &swarm{
|
||||
TLS: &tls{
|
||||
CAOptional: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Consul provider namespace option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Consul: &consul{
|
||||
Namespace: ptr("foobar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Consul provider tls.CAOptional option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Consul: &consul{
|
||||
TLS: &tls{
|
||||
CAOptional: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "ConsulCatalog provider namespace option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
ConsulCatalog: &consulCatalog{
|
||||
Namespace: ptr("foobar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "ConsulCatalog provider tls.CAOptional option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
ConsulCatalog: &consulCatalog{
|
||||
Endpoint: &endpointConfig{
|
||||
TLS: &tls{
|
||||
CAOptional: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Nomad provider namespace option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Nomad: &nomad{
|
||||
Namespace: ptr("foobar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Nomad provider tls.CAOptional option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Nomad: &nomad{
|
||||
Endpoint: &endpointConfig{
|
||||
TLS: &tls{
|
||||
CAOptional: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Marathon configuration is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Marathon: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Rancher configuration is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Rancher: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "ETCD provider tls.CAOptional option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
ETCD: &etcd{
|
||||
TLS: &tls{
|
||||
CAOptional: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Redis provider tls.CAOptional option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
Redis: &redis{
|
||||
TLS: &tls{
|
||||
CAOptional: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP provider tls.CAOptional option is incompatible",
|
||||
config: configuration{
|
||||
Providers: &providers{
|
||||
HTTP: &http{
|
||||
TLS: &tls{
|
||||
CAOptional: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Pilot configuration is incompatible",
|
||||
config: configuration{
|
||||
Pilot: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Experimental HTTP3 enablement configuration is incompatible",
|
||||
config: configuration{
|
||||
Experimental: &experimental{
|
||||
HTTP3: ptr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Tracing SpanNameLimit option is incompatible",
|
||||
config: configuration{
|
||||
Tracing: &tracing{
|
||||
SpanNameLimit: ptr(42),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Tracing Jaeger configuration is incompatible",
|
||||
config: configuration{
|
||||
Tracing: &tracing{
|
||||
Jaeger: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Tracing Zipkin configuration is incompatible",
|
||||
config: configuration{
|
||||
Tracing: &tracing{
|
||||
Zipkin: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Tracing Datadog configuration is incompatible",
|
||||
config: configuration{
|
||||
Tracing: &tracing{
|
||||
Datadog: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Tracing Instana configuration is incompatible",
|
||||
config: configuration{
|
||||
Tracing: &tracing{
|
||||
Instana: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Tracing Haystack configuration is incompatible",
|
||||
config: configuration{
|
||||
Tracing: &tracing{
|
||||
Haystack: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Tracing Elastic configuration is incompatible",
|
||||
config: configuration{
|
||||
Tracing: &tracing{
|
||||
Elastic: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var gotLog bool
|
||||
var gotLevel zerolog.Level
|
||||
testHook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, message string) {
|
||||
gotLog = true
|
||||
gotLevel = level
|
||||
})
|
||||
|
||||
logger := log.With().Logger().Hook(testHook)
|
||||
assert.True(t, test.config.deprecationNotice(logger))
|
||||
assert.True(t, gotLog)
|
||||
assert.Equal(t, zerolog.ErrorLevel, gotLevel)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
env map[string]string
|
||||
wantDeprecated bool
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
args: []string{},
|
||||
wantDeprecated: false,
|
||||
},
|
||||
{
|
||||
desc: "[FLAG] providers.marathon is deprecated",
|
||||
args: []string{
|
||||
"--access-log",
|
||||
"--log.level=DEBUG",
|
||||
"--entrypoints.test.http.tls",
|
||||
"--providers.nomad.endpoint.tls.insecureskipverify=true",
|
||||
"--providers.marathon",
|
||||
},
|
||||
wantDeprecated: true,
|
||||
},
|
||||
{
|
||||
desc: "[FLAG] multiple deprecated",
|
||||
args: []string{
|
||||
"--access-log",
|
||||
"--log.level=DEBUG",
|
||||
"--entrypoints.test.http.tls",
|
||||
"--providers.marathon",
|
||||
"--pilot.token=XXX",
|
||||
},
|
||||
wantDeprecated: true,
|
||||
},
|
||||
{
|
||||
desc: "[FLAG] no deprecated",
|
||||
args: []string{
|
||||
"--access-log",
|
||||
"--log.level=DEBUG",
|
||||
"--entrypoints.test.http.tls",
|
||||
"--providers.docker",
|
||||
},
|
||||
wantDeprecated: false,
|
||||
},
|
||||
{
|
||||
desc: "[ENV] providers.marathon is deprecated",
|
||||
env: map[string]string{
|
||||
"TRAEFIK_ACCESS_LOG": "",
|
||||
"TRAEFIK_LOG_LEVEL": "DEBUG",
|
||||
"TRAEFIK_ENTRYPOINT_TEST_HTTP_TLS": "true",
|
||||
"TRAEFIK_PROVIDERS_MARATHON": "true",
|
||||
},
|
||||
wantDeprecated: true,
|
||||
},
|
||||
{
|
||||
desc: "[ENV] multiple deprecated",
|
||||
env: map[string]string{
|
||||
"TRAEFIK_ACCESS_LOG": "true",
|
||||
"TRAEFIK_LOG_LEVEL": "DEBUG",
|
||||
"TRAEFIK_ENTRYPOINT_TEST_HTTP_TLS": "true",
|
||||
"TRAEFIK_PROVIDERS_MARATHON": "true",
|
||||
"TRAEFIK_PILOT_TOKEN": "xxx",
|
||||
},
|
||||
wantDeprecated: true,
|
||||
},
|
||||
{
|
||||
desc: "[ENV] no deprecated",
|
||||
env: map[string]string{
|
||||
"TRAEFIK_ACCESS_LOG": "true",
|
||||
"TRAEFIK_LOG_LEVEL": "DEBUG",
|
||||
"TRAEFIK_ENTRYPOINT_TEST_HTTP_TLS": "true",
|
||||
},
|
||||
|
||||
wantDeprecated: false,
|
||||
},
|
||||
{
|
||||
desc: "[FILE] providers.marathon is deprecated",
|
||||
args: []string{
|
||||
"--configfile=./fixtures/traefik_deprecated.toml",
|
||||
},
|
||||
wantDeprecated: true,
|
||||
},
|
||||
{
|
||||
desc: "[FILE] multiple deprecated",
|
||||
args: []string{
|
||||
"--configfile=./fixtures/traefik_multiple_deprecated.toml",
|
||||
},
|
||||
wantDeprecated: true,
|
||||
},
|
||||
{
|
||||
desc: "[FILE] no deprecated",
|
||||
args: []string{
|
||||
"--configfile=./fixtures/traefik_no_deprecated.toml",
|
||||
},
|
||||
wantDeprecated: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
tconfig := cmd.NewTraefikConfiguration()
|
||||
c := &cli.Command{Configuration: tconfig}
|
||||
l := DeprecationLoader{}
|
||||
|
||||
for name, val := range test.env {
|
||||
t.Setenv(name, val)
|
||||
}
|
||||
deprecated, err := l.Load(test.args, c)
|
||||
assert.Equal(t, test.wantDeprecated, deprecated)
|
||||
if !test.wantDeprecated {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
5
pkg/cli/fixtures/traefik_deprecated.toml
Normal file
5
pkg/cli/fixtures/traefik_deprecated.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
[accesslog]
|
||||
|
||||
[entrypoints.test.http.tls]
|
||||
|
||||
[providers.marathon]
|
8
pkg/cli/fixtures/traefik_multiple_deprecated.toml
Normal file
8
pkg/cli/fixtures/traefik_multiple_deprecated.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[accesslog]
|
||||
|
||||
[entrypoints.test.http.tls]
|
||||
|
||||
[providers.marathon]
|
||||
|
||||
[pilot]
|
||||
token="xxx"
|
3
pkg/cli/fixtures/traefik_no_deprecated.toml
Normal file
3
pkg/cli/fixtures/traefik_no_deprecated.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[accesslog]
|
||||
|
||||
[entrypoints.test.http.tls]
|
|
@ -37,8 +37,9 @@ type HTTPConfiguration struct {
|
|||
|
||||
// Model is a set of default router's values.
|
||||
type Model struct {
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -59,6 +60,7 @@ type Router struct {
|
|||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"`
|
||||
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||
|
@ -225,6 +227,7 @@ func (r *ResponseForwarding) SetDefaults() {
|
|||
// Server holds the server configuration.
|
||||
type Server struct {
|
||||
URL string `json:"url,omitempty" toml:"url,omitempty" yaml:"url,omitempty" label:"-"`
|
||||
Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty" label:"weight"`
|
||||
Scheme string `json:"-" toml:"-" yaml:"-" file:"-"`
|
||||
Port string `json:"-" toml:"-" yaml:"-" file:"-"`
|
||||
}
|
||||
|
|
|
@ -16,11 +16,19 @@ type TCPConfiguration struct {
|
|||
Routers map[string]*TCPRouter `json:"routers,omitempty" toml:"routers,omitempty" yaml:"routers,omitempty" export:"true"`
|
||||
Services map[string]*TCPService `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty" export:"true"`
|
||||
Middlewares map[string]*TCPMiddleware `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
Models map[string]*TCPModel `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
ServersTransports map[string]*TCPServersTransport `json:"serversTransports,omitempty" toml:"serversTransports,omitempty" yaml:"serversTransports,omitempty" label:"-" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// TCPModel is a set of default router's values.
|
||||
type TCPModel struct {
|
||||
DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// TCPService holds a tcp service configuration (can only be of one type at the same time).
|
||||
type TCPService struct {
|
||||
LoadBalancer *TCPServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty" export:"true"`
|
||||
|
@ -56,6 +64,7 @@ type TCPRouter struct {
|
|||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"`
|
||||
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
|
||||
TLS *RouterTCPTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
}
|
||||
|
|
|
@ -1128,6 +1128,11 @@ func (in *RouterTLSConfig) DeepCopy() *RouterTLSConfig {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Server) DeepCopyInto(out *Server) {
|
||||
*out = *in
|
||||
if in.Weight != nil {
|
||||
in, out := &in.Weight, &out.Weight
|
||||
*out = new(int)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1180,7 +1185,9 @@ func (in *ServersLoadBalancer) DeepCopyInto(out *ServersLoadBalancer) {
|
|||
if in.Servers != nil {
|
||||
in, out := &in.Servers, &out.Servers
|
||||
*out = make([]Server, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.HealthCheck != nil {
|
||||
in, out := &in.HealthCheck, &out.HealthCheck
|
||||
|
@ -1437,6 +1444,21 @@ func (in *TCPConfiguration) DeepCopyInto(out *TCPConfiguration) {
|
|||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
if in.Models != nil {
|
||||
in, out := &in.Models, &out.Models
|
||||
*out = make(map[string]*TCPModel, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal *TCPModel
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = new(TCPModel)
|
||||
**out = **in
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
if in.ServersTransports != nil {
|
||||
in, out := &in.ServersTransports, &out.ServersTransports
|
||||
*out = make(map[string]*TCPServersTransport, len(*in))
|
||||
|
@ -1554,6 +1576,22 @@ func (in *TCPMiddleware) DeepCopy() *TCPMiddleware {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TCPModel) DeepCopyInto(out *TCPModel) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPModel.
|
||||
func (in *TCPModel) DeepCopy() *TCPModel {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TCPModel)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TCPRouter) DeepCopyInto(out *TCPRouter) {
|
||||
*out = *in
|
||||
|
|
|
@ -71,11 +71,23 @@ type Configuration struct {
|
|||
|
||||
CertificatesResolvers map[string]CertificateResolver `description:"Certificates resolvers configuration." json:"certificatesResolvers,omitempty" toml:"certificatesResolvers,omitempty" yaml:"certificatesResolvers,omitempty" export:"true"`
|
||||
|
||||
Experimental *Experimental `description:"experimental features." json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" export:"true"`
|
||||
Experimental *Experimental `description:"Experimental features." json:"experimental,omitempty" toml:"experimental,omitempty" yaml:"experimental,omitempty" export:"true"`
|
||||
|
||||
Core *Core `description:"Core controls." json:"core,omitempty" toml:"core,omitempty" yaml:"core,omitempty" export:"true"`
|
||||
|
||||
Spiffe *SpiffeClientConfig `description:"SPIFFE integration configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// Core configures Traefik core behavior.
|
||||
type Core struct {
|
||||
DefaultRuleSyntax string `description:"Defines the rule parser default syntax (v2 or v3)" json:"defaultRuleSyntax,omitempty" toml:"defaultRuleSyntax,omitempty" yaml:"defaultRuleSyntax,omitempty"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Core) SetDefaults() {
|
||||
c.DefaultRuleSyntax = "v3"
|
||||
}
|
||||
|
||||
// SpiffeClientConfig defines the SPIFFE client configuration.
|
||||
type SpiffeClientConfig struct {
|
||||
WorkloadAPIAddr string `description:"Defines the workload API address." json:"workloadAPIAddr,omitempty" toml:"workloadAPIAddr,omitempty" yaml:"workloadAPIAddr,omitempty"`
|
||||
|
@ -317,6 +329,17 @@ func (c *Configuration) ValidateConfiguration() error {
|
|||
acmeEmail = resolver.ACME.Email
|
||||
}
|
||||
|
||||
if c.Core != nil {
|
||||
switch c.Core.DefaultRuleSyntax {
|
||||
case "v3": // NOOP
|
||||
case "v2":
|
||||
// TODO: point to migration guide.
|
||||
log.Warn().Msgf("v2 rules syntax is now deprecated, please use v3 instead...")
|
||||
default:
|
||||
return fmt.Errorf("unsupported default rule syntax configuration: %q", c.Core.DefaultRuleSyntax)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Tracing != nil && c.Tracing.OTLP != nil {
|
||||
if c.Tracing.OTLP.HTTP == nil && c.Tracing.OTLP.GRPC == nil {
|
||||
return errors.New("tracing OTLP: at least one of HTTP and gRPC options should be defined")
|
||||
|
|
|
@ -13,12 +13,13 @@ import (
|
|||
)
|
||||
|
||||
func TestDatadog(t *testing.T) {
|
||||
t.Cleanup(StopDatadog)
|
||||
|
||||
udp.SetAddr(":18125")
|
||||
// This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond
|
||||
udp.Timeout = 5 * time.Second
|
||||
|
||||
datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true})
|
||||
defer StopDatadog()
|
||||
|
||||
if !datadogRegistry.IsEpEnabled() || !datadogRegistry.IsRouterEnabled() || !datadogRegistry.IsSvcEnabled() {
|
||||
t.Errorf("DatadogRegistry should return true for IsEnabled(), IsRouterEnabled() and IsSvcEnabled()")
|
||||
|
@ -27,9 +28,7 @@ func TestDatadog(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDatadogWithPrefix(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
StopDatadog()
|
||||
})
|
||||
t.Cleanup(StopDatadog)
|
||||
|
||||
udp.SetAddr(":18125")
|
||||
// This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -26,7 +25,6 @@ func TestInfluxDB2(t *testing.T) {
|
|||
c <- &bodyStr
|
||||
_, _ = fmt.Fprintln(w, "ok")
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
influxDB2Registry := RegisterInfluxDB2(context.Background(),
|
||||
&types.InfluxDB2{
|
||||
|
@ -39,7 +37,11 @@ func TestInfluxDB2(t *testing.T) {
|
|||
AddRoutersLabels: true,
|
||||
AddServicesLabels: true,
|
||||
})
|
||||
defer StopInfluxDB2()
|
||||
|
||||
t.Cleanup(func() {
|
||||
StopInfluxDB2()
|
||||
ts.Close()
|
||||
})
|
||||
|
||||
if !influxDB2Registry.IsEpEnabled() || !influxDB2Registry.IsRouterEnabled() || !influxDB2Registry.IsSvcEnabled() {
|
||||
t.Fatalf("InfluxDB2Registry should return true for IsEnabled(), IsRouterEnabled() and IsSvcEnabled()")
|
||||
|
@ -137,14 +139,3 @@ func TestInfluxDB2(t *testing.T) {
|
|||
|
||||
assertMessage(t, *msgServiceRetries, expectedServiceRetries)
|
||||
}
|
||||
|
||||
func assertMessage(t *testing.T, msg string, patterns []string) {
|
||||
t.Helper()
|
||||
for _, pattern := range patterns {
|
||||
re := regexp.MustCompile(pattern)
|
||||
match := re.FindStringSubmatch(msg)
|
||||
if len(match) != 2 {
|
||||
t.Errorf("Got %q %v, want %q", msg, match, pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ import (
|
|||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -287,10 +289,8 @@ func TestOpenTelemetry_GaugeCollectorSet(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOpenTelemetry(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := make(chan *string, 5)
|
||||
|
||||
c := make(chan *string)
|
||||
defer close(c)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gzr, err := gzip.NewReader(r.Body)
|
||||
require.NoError(t, err)
|
||||
|
@ -310,7 +310,11 @@ func TestOpenTelemetry(t *testing.T) {
|
|||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
t.Cleanup(func() {
|
||||
StopOpenTelemetry()
|
||||
ts.Close()
|
||||
})
|
||||
|
||||
sURL, err := url.Parse(ts.URL)
|
||||
require.NoError(t, err)
|
||||
|
@ -333,57 +337,53 @@ func TestOpenTelemetry(t *testing.T) {
|
|||
`({"key":"service.name","value":{"stringValue":"traefik"}})`,
|
||||
`({"key":"service.version","value":{"stringValue":"` + version.Version + `"}})`,
|
||||
}
|
||||
msgMisc := <-c
|
||||
|
||||
assertMessage(t, *msgMisc, expected)
|
||||
tryAssertMessage(t, c, expected)
|
||||
|
||||
// TODO: the len of startUnixNano is no supposed to be 20, it should be 19
|
||||
expected = append(expected,
|
||||
expectedConfig := []string{
|
||||
`({"name":"traefik_config_reloads_total","description":"Config reloads","unit":"1","sum":{"dataPoints":\[{"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_config_last_reload_success","description":"Last config reload success","unit":"ms","gauge":{"dataPoints":\[{"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
`({"name":"traefik_open_connections","description":"How many open connections exist, by entryPoint and protocol","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test"}},{"key":"protocol","value":{"stringValue":"TCP"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
)
|
||||
}
|
||||
|
||||
registry.ConfigReloadsCounter().Add(1)
|
||||
registry.LastConfigReloadSuccessGauge().Set(1)
|
||||
registry.OpenConnectionsGauge().With("entrypoint", "test", "protocol", "TCP").Set(1)
|
||||
msgServer := <-c
|
||||
|
||||
assertMessage(t, *msgServer, expected)
|
||||
tryAssertMessage(t, c, expectedConfig)
|
||||
|
||||
expected = append(expected,
|
||||
expectedTLSCerts := []string{
|
||||
`({"name":"traefik_tls_certs_not_after","description":"Certificate expiration timestamp","unit":"ms","gauge":{"dataPoints":\[{"attributes":\[{"key":"key","value":{"stringValue":"value"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
)
|
||||
}
|
||||
|
||||
registry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
|
||||
msgTLS := <-c
|
||||
|
||||
assertMessage(t, *msgTLS, expected)
|
||||
tryAssertMessage(t, c, expectedTLSCerts)
|
||||
|
||||
expected = append(expected,
|
||||
expectedEntryPoints := []string{
|
||||
`({"name":"traefik_entrypoint_requests_total","description":"How many HTTP requests processed on an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_entrypoint_requests_tls_total","description":"How many HTTP requests with TLS processed on an entrypoint, partitioned by TLS Version and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test2"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_entrypoint_request_duration_seconds","description":"How long it took to process the request on an entrypoint, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test3"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
|
||||
`({"name":"traefik_entrypoint_requests_bytes_total","description":"The total size of requests in bytes handled by an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_entrypoint_responses_bytes_total","description":"The total size of responses in bytes handled by an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
)
|
||||
}
|
||||
|
||||
registry.EntryPointReqsCounter().With(nil, "entrypoint", "test1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
|
||||
registry.EntryPointReqsTLSCounter().With("entrypoint", "test2", "tls_version", "foo", "tls_cipher", "bar").Add(1)
|
||||
registry.EntryPointReqDurationHistogram().With("entrypoint", "test3").Observe(10000)
|
||||
registry.EntryPointReqsBytesCounter().With("entrypoint", "test1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
|
||||
registry.EntryPointRespsBytesCounter().With("entrypoint", "test1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
|
||||
msgEntrypoint := <-c
|
||||
|
||||
assertMessage(t, *msgEntrypoint, expected)
|
||||
tryAssertMessage(t, c, expectedEntryPoints)
|
||||
|
||||
expected = append(expected,
|
||||
expectedRouters := []string{
|
||||
`({"name":"traefik_router_requests_total","description":"How many HTTP requests are processed on a router, partitioned by service, status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_router_requests_tls_total","description":"How many HTTP requests with TLS are processed on a router, partitioned by service, TLS Version, and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_router_request_duration_seconds","description":"How long it took to process the request on a router, partitioned by service, status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
|
||||
`({"name":"traefik_router_requests_bytes_total","description":"The total size of requests in bytes handled by a router, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_router_responses_bytes_total","description":"The total size of responses in bytes handled by a router, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
)
|
||||
}
|
||||
|
||||
registry.RouterReqsCounter().With(nil, "router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
|
||||
registry.RouterReqsCounter().With(nil, "router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
|
||||
|
@ -391,18 +391,17 @@ func TestOpenTelemetry(t *testing.T) {
|
|||
registry.RouterReqDurationHistogram().With("router", "demo", "service", "test", "code", strconv.Itoa(http.StatusOK)).Observe(10000)
|
||||
registry.RouterReqsBytesCounter().With("router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
|
||||
registry.RouterRespsBytesCounter().With("router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
|
||||
msgRouter := <-c
|
||||
|
||||
assertMessage(t, *msgRouter, expected)
|
||||
tryAssertMessage(t, c, expectedRouters)
|
||||
|
||||
expected = append(expected,
|
||||
expectedServices := []string{
|
||||
`({"name":"traefik_service_requests_total","description":"How many HTTP requests processed on a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_service_requests_tls_total","description":"How many HTTP requests with TLS processed on a service, partitioned by TLS version and TLS cipher.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_service_request_duration_seconds","description":"How long it took to process the request on a service, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
|
||||
`({"name":"traefik_service_server_up","description":"service server is up, described by gauge value of 0 or 1.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"url","value":{"stringValue":"http://127.0.0.1"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
`({"name":"traefik_service_requests_bytes_total","description":"The total size of requests in bytes received by a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_service_responses_bytes_total","description":"The total size of responses in bytes returned by a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
)
|
||||
}
|
||||
|
||||
registry.ServiceReqsCounter().With(nil, "service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
|
||||
registry.ServiceReqsCounter().With(nil, "service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
|
||||
|
@ -411,21 +410,19 @@ func TestOpenTelemetry(t *testing.T) {
|
|||
registry.ServiceServerUpGauge().With("service", "test", "url", "http://127.0.0.1").Set(1)
|
||||
registry.ServiceReqsBytesCounter().With("service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
|
||||
registry.ServiceRespsBytesCounter().With("service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
|
||||
msgService := <-c
|
||||
|
||||
assertMessage(t, *msgService, expected)
|
||||
tryAssertMessage(t, c, expectedServices)
|
||||
|
||||
expected = append(expected,
|
||||
expectedServicesRetries := []string{
|
||||
`({"attributes":\[{"key":"service","value":{"stringValue":"foobar"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1})`,
|
||||
`({"attributes":\[{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":2})`,
|
||||
)
|
||||
}
|
||||
|
||||
registry.ServiceRetriesCounter().With("service", "test").Add(1)
|
||||
registry.ServiceRetriesCounter().With("service", "test").Add(1)
|
||||
registry.ServiceRetriesCounter().With("service", "foobar").Add(1)
|
||||
msgServiceRetries := <-c
|
||||
|
||||
assertMessage(t, *msgServiceRetries, expected)
|
||||
tryAssertMessage(t, c, expectedServicesRetries)
|
||||
|
||||
// We cannot rely on the previous expected pattern,
|
||||
// because this pattern was for matching only one dataPoint in the histogram,
|
||||
|
@ -437,14 +434,46 @@ func TestOpenTelemetry(t *testing.T) {
|
|||
|
||||
registry.EntryPointReqDurationHistogram().With("entrypoint", "myEntrypoint").Observe(10000)
|
||||
registry.EntryPointReqDurationHistogram().With("entrypoint", "myEntrypoint").Observe(20000)
|
||||
msgEntryPointReqDurationHistogram := <-c
|
||||
|
||||
assertMessage(t, *msgEntryPointReqDurationHistogram, expectedEntryPointReqDuration)
|
||||
|
||||
// We need to unlock the HTTP Server for the last export call when stopping
|
||||
// OpenTelemetry.
|
||||
go func() {
|
||||
<-c
|
||||
}()
|
||||
StopOpenTelemetry()
|
||||
tryAssertMessage(t, c, expectedEntryPointReqDuration)
|
||||
}
|
||||
|
||||
func assertMessage(t *testing.T, msg string, expected []string) {
|
||||
t.Helper()
|
||||
errs := verifyMessage(msg, expected)
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func tryAssertMessage(t *testing.T, c chan *string, expected []string) {
|
||||
t.Helper()
|
||||
|
||||
var errs []error
|
||||
timeout := time.After(1 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
case msg := <-c:
|
||||
errs = verifyMessage(*msg, expected)
|
||||
if len(errs) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyMessage(msg string, expected []string) []error {
|
||||
var errs []error
|
||||
for _, pattern := range expected {
|
||||
re := regexp.MustCompile(pattern)
|
||||
match := re.FindStringSubmatch(msg)
|
||||
if len(match) != 2 {
|
||||
errs = append(errs, fmt.Errorf("Got %q %v, want %q", msg, match, pattern))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ func TestClientIPMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -147,7 +147,7 @@ func TestMethodMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -265,7 +265,7 @@ func TestHostMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -365,7 +365,7 @@ func TestHostRegexpMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -439,7 +439,7 @@ func TestPathMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -532,7 +532,7 @@ func TestPathRegexpMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -604,7 +604,7 @@ func TestPathPrefixMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -692,7 +692,7 @@ func TestHeaderMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -800,7 +800,7 @@ func TestHeaderRegexpMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -889,7 +889,7 @@ func TestQueryMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -1003,7 +1003,7 @@ func TestQueryRegexpMatcher(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
|
226
pkg/muxer/http/matcher_v2.go
Normal file
226
pkg/muxer/http/matcher_v2.go
Normal file
|
@ -0,0 +1,226 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/ip"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
|
||||
)
|
||||
|
||||
var httpFuncsV2 = map[string]func(*matchersTree, ...string) error{
|
||||
"Host": hostV2,
|
||||
"HostHeader": hostV2,
|
||||
"HostRegexp": hostRegexpV2,
|
||||
"ClientIP": clientIPV2,
|
||||
"Path": pathV2,
|
||||
"PathPrefix": pathPrefixV2,
|
||||
"Method": methodsV2,
|
||||
"Headers": headersV2,
|
||||
"HeadersRegexp": headersRegexpV2,
|
||||
"Query": queryV2,
|
||||
}
|
||||
|
||||
func pathV2(tree *matchersTree, paths ...string) error {
|
||||
for _, path := range paths {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
return fmt.Errorf("path %q does not start with a '/'", path)
|
||||
}
|
||||
}
|
||||
|
||||
tree.matcher = func(req *http.Request) bool {
|
||||
for _, path := range paths {
|
||||
if req.URL.Path == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pathPrefixV2(tree *matchersTree, paths ...string) error {
|
||||
for _, path := range paths {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
return fmt.Errorf("path %q does not start with a '/'", path)
|
||||
}
|
||||
}
|
||||
|
||||
tree.matcher = func(req *http.Request) bool {
|
||||
for _, path := range paths {
|
||||
if strings.HasPrefix(req.URL.Path, path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hostV2(tree *matchersTree, hosts ...string) error {
|
||||
for i, host := range hosts {
|
||||
if !IsASCII(host) {
|
||||
return fmt.Errorf("invalid value %q for \"Host\" matcher, non-ASCII characters are not allowed", host)
|
||||
}
|
||||
|
||||
hosts[i] = strings.ToLower(host)
|
||||
}
|
||||
|
||||
tree.matcher = func(req *http.Request) bool {
|
||||
reqHost := requestdecorator.GetCanonizedHost(req.Context())
|
||||
if len(reqHost) == 0 {
|
||||
// If the request is an HTTP/1.0 request, then a Host may not be defined.
|
||||
if req.ProtoAtLeast(1, 1) {
|
||||
log.Ctx(req.Context()).Warn().Msgf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
flatH := requestdecorator.GetCNAMEFlatten(req.Context())
|
||||
if len(flatH) > 0 {
|
||||
for _, host := range hosts {
|
||||
if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) {
|
||||
return true
|
||||
}
|
||||
log.Ctx(req.Context()).Debug().Msgf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if reqHost == host {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for match on trailing period on host
|
||||
if last := len(host) - 1; last >= 0 && host[last] == '.' {
|
||||
h := host[:last]
|
||||
if reqHost == h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for match on trailing period on request
|
||||
if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' {
|
||||
h := reqHost[:last]
|
||||
if h == host {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func clientIPV2(tree *matchersTree, clientIPs ...string) error {
|
||||
checker, err := ip.NewChecker(clientIPs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err)
|
||||
}
|
||||
|
||||
strategy := ip.RemoteAddrStrategy{}
|
||||
|
||||
tree.matcher = func(req *http.Request) bool {
|
||||
ok, err := checker.Contains(strategy.GetIP(req))
|
||||
if err != nil {
|
||||
log.Ctx(req.Context()).Warn().Err(err).Msg("\"ClientIP\" matcher: could not match remote address")
|
||||
return false
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func methodsV2(tree *matchersTree, methods ...string) error {
|
||||
route := mux.NewRouter().NewRoute()
|
||||
route.Methods(methods...)
|
||||
if err := route.GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tree.matcher = func(req *http.Request) bool {
|
||||
return route.Match(req, &mux.RouteMatch{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func headersV2(tree *matchersTree, headers ...string) error {
|
||||
route := mux.NewRouter().NewRoute()
|
||||
route.Headers(headers...)
|
||||
if err := route.GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tree.matcher = func(req *http.Request) bool {
|
||||
return route.Match(req, &mux.RouteMatch{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func queryV2(tree *matchersTree, query ...string) error {
|
||||
var queries []string
|
||||
for _, elem := range query {
|
||||
queries = append(queries, strings.SplitN(elem, "=", 2)...)
|
||||
}
|
||||
|
||||
route := mux.NewRouter().NewRoute()
|
||||
route.Queries(queries...)
|
||||
if err := route.GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tree.matcher = func(req *http.Request) bool {
|
||||
return route.Match(req, &mux.RouteMatch{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hostRegexpV2(tree *matchersTree, hosts ...string) error {
|
||||
router := mux.NewRouter()
|
||||
|
||||
for _, host := range hosts {
|
||||
if !IsASCII(host) {
|
||||
return fmt.Errorf("invalid value %q for HostRegexp matcher, non-ASCII characters are not allowed", host)
|
||||
}
|
||||
|
||||
tmpRt := router.Host(host)
|
||||
if tmpRt.GetError() != nil {
|
||||
return tmpRt.GetError()
|
||||
}
|
||||
}
|
||||
|
||||
tree.matcher = func(req *http.Request) bool {
|
||||
return router.Match(req, &mux.RouteMatch{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func headersRegexpV2(tree *matchersTree, headers ...string) error {
|
||||
route := mux.NewRouter().NewRoute()
|
||||
route.HeadersRegexp(headers...)
|
||||
if err := route.GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tree.matcher = func(req *http.Request) bool {
|
||||
return route.Match(req, &mux.RouteMatch{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
1535
pkg/muxer/http/matcher_v2_test.go
Normal file
1535
pkg/muxer/http/matcher_v2_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -12,8 +12,9 @@ import (
|
|||
|
||||
// Muxer handles routing with rules.
|
||||
type Muxer struct {
|
||||
routes routes
|
||||
parser predicate.Parser
|
||||
routes routes
|
||||
parser predicate.Parser
|
||||
parserV2 predicate.Parser
|
||||
}
|
||||
|
||||
// NewMuxer returns a new muxer instance.
|
||||
|
@ -28,8 +29,19 @@ func NewMuxer() (*Muxer, error) {
|
|||
return nil, fmt.Errorf("error while creating parser: %w", err)
|
||||
}
|
||||
|
||||
var matchersV2 []string
|
||||
for matcher := range httpFuncsV2 {
|
||||
matchersV2 = append(matchersV2, matcher)
|
||||
}
|
||||
|
||||
parserV2, err := rules.NewParser(matchersV2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating v2 parser: %w", err)
|
||||
}
|
||||
|
||||
return &Muxer{
|
||||
parser: parser,
|
||||
parser: parser,
|
||||
parserV2: parserV2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -53,10 +65,26 @@ func GetRulePriority(rule string) int {
|
|||
}
|
||||
|
||||
// AddRoute add a new route to the router.
|
||||
func (m *Muxer) AddRoute(rule string, priority int, handler http.Handler) error {
|
||||
parse, err := m.parser.Parse(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
|
||||
func (m *Muxer) AddRoute(rule string, syntax string, priority int, handler http.Handler) error {
|
||||
var parse interface{}
|
||||
var err error
|
||||
var matcherFuncs map[string]func(*matchersTree, ...string) error
|
||||
|
||||
switch syntax {
|
||||
case "v2":
|
||||
parse, err = m.parserV2.Parse(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
|
||||
}
|
||||
|
||||
matcherFuncs = httpFuncsV2
|
||||
default:
|
||||
parse, err = m.parser.Parse(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
|
||||
}
|
||||
|
||||
matcherFuncs = httpFuncs
|
||||
}
|
||||
|
||||
buildTree, ok := parse.(rules.TreeBuilder)
|
||||
|
@ -65,7 +93,7 @@ func (m *Muxer) AddRoute(rule string, priority int, handler http.Handler) error
|
|||
}
|
||||
|
||||
var matchers matchersTree
|
||||
err = matchers.addRule(buildTree())
|
||||
err = matchers.addRule(buildTree(), matcherFuncs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while adding rule %s: %w", rule, err)
|
||||
}
|
||||
|
@ -87,6 +115,9 @@ func ParseDomains(rule string) ([]string, error) {
|
|||
for matcher := range httpFuncs {
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
for matcher := range httpFuncsV2 {
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
|
||||
parser, err := rules.NewParser(matchers)
|
||||
if err != nil {
|
||||
|
@ -166,25 +197,27 @@ func (m *matchersTree) match(req *http.Request) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *matchersTree) addRule(rule *rules.Tree) error {
|
||||
type matcherFuncs map[string]func(*matchersTree, ...string) error
|
||||
|
||||
func (m *matchersTree) addRule(rule *rules.Tree, funcs matcherFuncs) error {
|
||||
switch rule.Matcher {
|
||||
case "and", "or":
|
||||
m.operator = rule.Matcher
|
||||
m.left = &matchersTree{}
|
||||
err := m.left.addRule(rule.RuleLeft)
|
||||
err := m.left.addRule(rule.RuleLeft, funcs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while adding rule %s: %w", rule.Matcher, err)
|
||||
}
|
||||
|
||||
m.right = &matchersTree{}
|
||||
return m.right.addRule(rule.RuleRight)
|
||||
return m.right.addRule(rule.RuleRight, funcs)
|
||||
default:
|
||||
err := rules.CheckRule(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while checking rule %s: %w", rule.Matcher, err)
|
||||
}
|
||||
|
||||
err = httpFuncs[rule.Matcher](m, rule.Value...)
|
||||
err = funcs[rule.Matcher](m, rule.Value...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while adding rule %s: %w", rule.Matcher, err)
|
||||
}
|
||||
|
|
|
@ -231,7 +231,7 @@ func TestMuxer(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -394,7 +394,7 @@ func Test_addRoutePriority(t *testing.T) {
|
|||
route.priority = GetRulePriority(route.rule)
|
||||
}
|
||||
|
||||
err := muxer.AddRoute(route.rule, route.priority, handler)
|
||||
err := muxer.AddRoute(route.rule, "", route.priority, handler)
|
||||
require.NoError(t, err, route.rule)
|
||||
}
|
||||
|
||||
|
@ -519,7 +519,7 @@ func TestEmptyHost(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
err = muxer.AddRoute(test.rule, "", 0, handler)
|
||||
require.NoError(t, err)
|
||||
|
||||
// RequestDecorator is necessary for the host rule
|
||||
|
|
|
@ -38,7 +38,7 @@ func Test_HostSNICatchAll(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
require.NoError(t, err)
|
||||
|
||||
handler, catchAll := muxer.Match(ConnData{
|
||||
|
@ -144,7 +144,7 @@ func Test_HostSNI(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
if test.buildErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -227,7 +227,7 @@ func Test_HostSNIRegexp(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
if test.buildErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -299,7 +299,7 @@ func Test_ClientIP(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
if test.buildErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -363,7 +363,7 @@ func Test_ALPN(t *testing.T) {
|
|||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
err = muxer.AddRoute(test.rule, "", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
|
||||
if test.buildErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
|
240
pkg/muxer/tcp/matcher_v2.go
Normal file
240
pkg/muxer/tcp/matcher_v2.go
Normal file
|
@ -0,0 +1,240 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/ip"
|
||||
)
|
||||
|
||||
var tcpFuncsV2 = map[string]func(*matchersTree, ...string) error{
|
||||
"ALPN": alpnV2,
|
||||
"ClientIP": clientIPV2,
|
||||
"HostSNI": hostSNIV2,
|
||||
"HostSNIRegexp": hostSNIRegexpV2,
|
||||
}
|
||||
|
||||
func clientIPV2(tree *matchersTree, clientIPs ...string) error {
|
||||
checker, err := ip.NewChecker(clientIPs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err)
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
if meta.remoteIP == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
ok, err := checker.Contains(meta.remoteIP)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("ClientIP matcher: could not match remote address")
|
||||
return false
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// alpnV2 checks if any of the connection ALPN protocols matches one of the matcher protocols.
|
||||
func alpnV2(tree *matchersTree, protos ...string) error {
|
||||
if len(protos) == 0 {
|
||||
return errors.New("empty value for \"ALPN\" matcher is not allowed")
|
||||
}
|
||||
|
||||
for _, proto := range protos {
|
||||
if proto == tlsalpn01.ACMETLS1Protocol {
|
||||
return fmt.Errorf("invalid protocol value for \"ALPN\" matcher, %q is not allowed", proto)
|
||||
}
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
for _, proto := range meta.alpnProtos {
|
||||
for _, filter := range protos {
|
||||
if proto == filter {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hostSNIV2 checks if the SNI Host of the connection match the matcher host.
|
||||
func hostSNIV2(tree *matchersTree, hosts ...string) error {
|
||||
if len(hosts) == 0 {
|
||||
return errors.New("empty value for \"HostSNI\" matcher is not allowed")
|
||||
}
|
||||
|
||||
for i, host := range hosts {
|
||||
// Special case to allow global wildcard
|
||||
if host == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !hostOrIP.MatchString(host) {
|
||||
return fmt.Errorf("invalid value for \"HostSNI\" matcher, %q is not a valid hostname or IP", host)
|
||||
}
|
||||
|
||||
hosts[i] = strings.ToLower(host)
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
// Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP,
|
||||
// it allows matching with an empty serverName.
|
||||
// Which is why we make sure to take that case into account before
|
||||
// checking meta.serverName.
|
||||
if hosts[0] == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
if meta.serverName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if host == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
if host == meta.serverName {
|
||||
return true
|
||||
}
|
||||
|
||||
// trim trailing period in case of FQDN
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
if host == meta.serverName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hostSNIRegexpV2 checks if the SNI Host of the connection matches the matcher host regexp.
|
||||
func hostSNIRegexpV2(tree *matchersTree, templates ...string) error {
|
||||
if len(templates) == 0 {
|
||||
return fmt.Errorf("empty value for \"HostSNIRegexp\" matcher is not allowed")
|
||||
}
|
||||
|
||||
var regexps []*regexp.Regexp
|
||||
|
||||
for _, template := range templates {
|
||||
preparedPattern, err := preparePattern(template)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pattern value for \"HostSNIRegexp\" matcher, %q is not a valid pattern: %w", template, err)
|
||||
}
|
||||
|
||||
regexp, err := regexp.Compile(preparedPattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
regexps = append(regexps, regexp)
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
for _, regexp := range regexps {
|
||||
if regexp.MatchString(meta.serverName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// preparePattern builds a regexp pattern from the initial user defined expression.
|
||||
// This function reuses the code dedicated to host matching of the newRouteRegexp func from the gorilla/mux library.
|
||||
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
|
||||
func preparePattern(template string) (string, error) {
|
||||
// Check if it is well-formed.
|
||||
idxs, errBraces := braceIndices(template)
|
||||
if errBraces != nil {
|
||||
return "", errBraces
|
||||
}
|
||||
|
||||
defaultPattern := "[^.]+"
|
||||
pattern := bytes.NewBufferString("")
|
||||
|
||||
// Host SNI matching is case-insensitive
|
||||
_, _ = fmt.Fprint(pattern, "(?i)")
|
||||
|
||||
pattern.WriteByte('^')
|
||||
var end int
|
||||
for i := 0; i < len(idxs); i += 2 {
|
||||
// Set all values we are interested in.
|
||||
raw := template[end:idxs[i]]
|
||||
end = idxs[i+1]
|
||||
parts := strings.SplitN(template[idxs[i]+1:end-1], ":", 2)
|
||||
name := parts[0]
|
||||
|
||||
patt := defaultPattern
|
||||
if len(parts) == 2 {
|
||||
patt = parts[1]
|
||||
}
|
||||
|
||||
// Name or pattern can't be empty.
|
||||
if name == "" || patt == "" {
|
||||
return "", fmt.Errorf("mux: missing name or pattern in %q",
|
||||
template[idxs[i]:end])
|
||||
}
|
||||
|
||||
// Build the regexp pattern.
|
||||
_, _ = fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
|
||||
}
|
||||
|
||||
// Add the remaining.
|
||||
raw := template[end:]
|
||||
pattern.WriteString(regexp.QuoteMeta(raw))
|
||||
pattern.WriteByte('$')
|
||||
|
||||
return pattern.String(), nil
|
||||
}
|
||||
|
||||
// varGroupName builds a capturing group name for the indexed variable.
|
||||
// This function is a copy of varGroupName func from the gorilla/mux library.
|
||||
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
|
||||
func varGroupName(idx int) string {
|
||||
return "v" + strconv.Itoa(idx)
|
||||
}
|
||||
|
||||
// braceIndices returns the first level curly brace indices from a string.
|
||||
// This function is a copy of braceIndices func from the gorilla/mux library.
|
||||
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
|
||||
func braceIndices(s string) ([]int, error) {
|
||||
var level, idx int
|
||||
var idxs []int
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '{':
|
||||
if level++; level == 1 {
|
||||
idx = i
|
||||
}
|
||||
case '}':
|
||||
if level--; level == 0 {
|
||||
idxs = append(idxs, idx, i+1)
|
||||
} else if level < 0 {
|
||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if level != 0 {
|
||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||
}
|
||||
return idxs, nil
|
||||
}
|
1008
pkg/muxer/tcp/matcher_v2_test.go
Normal file
1008
pkg/muxer/tcp/matcher_v2_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -41,8 +41,9 @@ func NewConnData(serverName string, conn tcp.WriteCloser, alpnProtos []string) (
|
|||
|
||||
// Muxer defines a muxer that handles TCP routing with rules.
|
||||
type Muxer struct {
|
||||
routes routes
|
||||
parser predicate.Parser
|
||||
routes routes
|
||||
parser predicate.Parser
|
||||
parserV2 predicate.Parser
|
||||
}
|
||||
|
||||
// NewMuxer returns a TCP muxer.
|
||||
|
@ -57,7 +58,20 @@ func NewMuxer() (*Muxer, error) {
|
|||
return nil, fmt.Errorf("error while creating rules parser: %w", err)
|
||||
}
|
||||
|
||||
return &Muxer{parser: parser}, nil
|
||||
var matchersV2 []string
|
||||
for matcher := range tcpFuncsV2 {
|
||||
matchersV2 = append(matchersV2, matcher)
|
||||
}
|
||||
|
||||
parserV2, err := rules.NewParser(matchersV2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating v2 rules parser: %w", err)
|
||||
}
|
||||
|
||||
return &Muxer{
|
||||
parser: parser,
|
||||
parserV2: parserV2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Match returns the handler of the first route matching the connection metadata,
|
||||
|
@ -106,10 +120,26 @@ func GetRulePriority(rule string) int {
|
|||
|
||||
// AddRoute adds a new route, associated to the given handler, at the given
|
||||
// priority, to the muxer.
|
||||
func (m *Muxer) AddRoute(rule string, priority int, handler tcp.Handler) error {
|
||||
parse, err := m.parser.Parse(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
|
||||
func (m *Muxer) AddRoute(rule string, syntax string, priority int, handler tcp.Handler) error {
|
||||
var parse interface{}
|
||||
var err error
|
||||
var matcherFuncs map[string]func(*matchersTree, ...string) error
|
||||
|
||||
switch syntax {
|
||||
case "v2":
|
||||
parse, err = m.parserV2.Parse(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
|
||||
}
|
||||
|
||||
matcherFuncs = tcpFuncsV2
|
||||
default:
|
||||
parse, err = m.parser.Parse(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
|
||||
}
|
||||
|
||||
matcherFuncs = tcpFuncs
|
||||
}
|
||||
|
||||
buildTree, ok := parse.(rules.TreeBuilder)
|
||||
|
@ -120,7 +150,7 @@ func (m *Muxer) AddRoute(rule string, priority int, handler tcp.Handler) error {
|
|||
ruleTree := buildTree()
|
||||
|
||||
var matchers matchersTree
|
||||
err = matchers.addRule(ruleTree)
|
||||
err = matchers.addRule(ruleTree, matcherFuncs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while adding rule %s: %w", rule, err)
|
||||
}
|
||||
|
@ -155,6 +185,9 @@ func ParseHostSNI(rule string) ([]string, error) {
|
|||
for matcher := range tcpFuncs {
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
for matcher := range tcpFuncsV2 {
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
|
||||
parser, err := rules.NewParser(matchers)
|
||||
if err != nil {
|
||||
|
@ -237,25 +270,27 @@ func (m *matchersTree) match(meta ConnData) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *matchersTree) addRule(rule *rules.Tree) error {
|
||||
type matcherFuncs map[string]func(*matchersTree, ...string) error
|
||||
|
||||
func (m *matchersTree) addRule(rule *rules.Tree, funcs matcherFuncs) error {
|
||||
switch rule.Matcher {
|
||||
case "and", "or":
|
||||
m.operator = rule.Matcher
|
||||
m.left = &matchersTree{}
|
||||
err := m.left.addRule(rule.RuleLeft)
|
||||
err := m.left.addRule(rule.RuleLeft, funcs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.right = &matchersTree{}
|
||||
return m.right.addRule(rule.RuleRight)
|
||||
return m.right.addRule(rule.RuleRight, funcs)
|
||||
default:
|
||||
err := rules.CheckRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tcpFuncs[rule.Matcher](m, rule.Value...)
|
||||
err = funcs[rule.Matcher](m, rule.Value...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -277,7 +277,7 @@ func Test_addTCPRoute(t *testing.T) {
|
|||
router, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.AddRoute(test.rule, 0, handler)
|
||||
err = router.AddRoute(test.rule, "", 0, handler)
|
||||
if test.routeErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
|
@ -447,7 +447,7 @@ func Test_Priority(t *testing.T) {
|
|||
matchedRule := ""
|
||||
for rule, priority := range test.rules {
|
||||
rule := rule
|
||||
err := muxer.AddRoute(rule, priority, tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
||||
err := muxer.AddRoute(rule, "", priority, tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
||||
matchedRule = rule
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -112,6 +112,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
r := &dynamic.Router{
|
||||
Middlewares: mds,
|
||||
Priority: route.Priority,
|
||||
RuleSyntax: route.Syntax,
|
||||
EntryPoints: ingressRoute.Spec.EntryPoints,
|
||||
Rule: route.Match,
|
||||
Service: serviceName,
|
||||
|
|
|
@ -102,6 +102,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
|
|||
Middlewares: mds,
|
||||
Rule: route.Match,
|
||||
Priority: route.Priority,
|
||||
RuleSyntax: route.Syntax,
|
||||
Service: serviceName,
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ type Route struct {
|
|||
// Priority defines the router's priority.
|
||||
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority
|
||||
Priority int `json:"priority,omitempty"`
|
||||
// Syntax defines the router's rule syntax.
|
||||
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax
|
||||
Syntax string `json:"syntax,omitempty"`
|
||||
// Services defines the list of Service.
|
||||
// It can contain any combination of TraefikService and/or reference to a Kubernetes Service.
|
||||
Services []Service `json:"services,omitempty"`
|
||||
|
|
|
@ -29,6 +29,9 @@ type RouteTCP struct {
|
|||
// Priority defines the router's priority.
|
||||
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1
|
||||
Priority int `json:"priority,omitempty"`
|
||||
// Syntax defines the router's rule syntax.
|
||||
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax_1
|
||||
Syntax string `json:"syntax,omitempty"`
|
||||
// Services defines the list of TCP services.
|
||||
Services []ServiceTCP `json:"services,omitempty"`
|
||||
// Middlewares defines the list of references to MiddlewareTCP resources.
|
||||
|
|
|
@ -230,6 +230,7 @@ func (p *Provider) loadConfigurationFromGateway(ctx context.Context, client Clie
|
|||
err := client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{
|
||||
Type: string(gatev1.GatewayClassConditionStatusAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: gatewayClass.Generation,
|
||||
Reason: "Handled",
|
||||
Message: "Handled by Traefik controller",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
|
@ -587,7 +588,16 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [
|
|||
Type: string(gatev1.GatewayConditionAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
Reason: string(gatev1.GatewayConditionAccepted),
|
||||
Reason: string(gatev1.GatewayReasonAccepted),
|
||||
Message: "Gateway successfully scheduled",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
},
|
||||
// update "Programmed" status with "Programmed" reason
|
||||
metav1.Condition{
|
||||
Type: string(gatev1.GatewayConditionProgrammed),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
Reason: string(gatev1.GatewayReasonProgrammed),
|
||||
Message: "Gateway successfully scheduled",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
},
|
||||
|
@ -760,6 +770,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li
|
|||
|
||||
router := dynamic.Router{
|
||||
Rule: rule,
|
||||
RuleSyntax: "v3",
|
||||
EntryPoints: []string{ep},
|
||||
}
|
||||
|
||||
|
@ -898,6 +909,7 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li
|
|||
router := dynamic.TCPRouter{
|
||||
Rule: "HostSNI(`*`)",
|
||||
EntryPoints: []string{ep},
|
||||
RuleSyntax: "v3",
|
||||
}
|
||||
|
||||
if listener.Protocol == gatev1.TLSProtocolType && listener.TLS != nil {
|
||||
|
@ -1062,6 +1074,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li
|
|||
|
||||
router := dynamic.TCPRouter{
|
||||
Rule: rule,
|
||||
RuleSyntax: "v3",
|
||||
EntryPoints: []string{ep},
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1.TLSModePassthrough,
|
||||
|
@ -1385,7 +1398,7 @@ func extractHeaderRules(headers []gatev1.HTTPHeaderMatch) ([]string, error) {
|
|||
|
||||
switch *header.Type {
|
||||
case gatev1.HeaderMatchExact:
|
||||
headerRules = append(headerRules, fmt.Sprintf("Headers(`%s`,`%s`)", header.Name, header.Value))
|
||||
headerRules = append(headerRules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value))
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported header match type %s", *header.Type)
|
||||
}
|
||||
|
|
|
@ -550,6 +550,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -609,6 +610,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "api@internal",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -641,6 +643,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -704,6 +707,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"websecure"},
|
||||
Service: "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -773,6 +777,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-66e726cd8903b49727ae-wrr",
|
||||
Rule: "(Host(`foo.com`) || Host(`bar.com`)) && PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -832,6 +837,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-3b78e2feb3295ddd87f0-wrr",
|
||||
Rule: "(Host(`foo.com`) || HostRegexp(`^[a-zA-Z0-9-]+\\.bar\\.com$`)) && PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -891,6 +897,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-b0521a61fb43068694b4-wrr",
|
||||
Rule: "(Host(`foo.com`) || HostRegexp(`^[a-zA-Z0-9-]+\\.foo\\.com$`)) && PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -949,11 +956,13 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||
},
|
||||
"default-http-app-1-my-gateway-web-d737b4933fa88e68ab8a": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`foo.com`) && Path(`/bir`)",
|
||||
RuleSyntax: "v3",
|
||||
Service: "default-http-app-1-my-gateway-web-d737b4933fa88e68ab8a-wrr",
|
||||
},
|
||||
},
|
||||
|
@ -1039,6 +1048,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||
},
|
||||
},
|
||||
|
@ -1124,11 +1134,13 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-http-web-1c0cf64bde37d9d0df06-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-http-app-1-my-gateway-https-websecure-1c0cf64bde37d9d0df06": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "default-http-app-1-my-gateway-https-websecure-1c0cf64bde37d9d0df06-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -1213,11 +1225,13 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "default-http-app-1-my-gateway-websecure-1c0cf64bde37d9d0df06-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -1293,20 +1307,22 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-http-app-1-my-gateway-web-330d644a7f2079e8f454": {
|
||||
"default-http-app-1-my-gateway-web-4a1b73e6f83804949a37": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-330d644a7f2079e8f454-wrr",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`) && Headers(`my-header`,`foo`) && Headers(`my-header2`,`bar`)",
|
||||
Service: "default-http-app-1-my-gateway-web-4a1b73e6f83804949a37-wrr",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`) && Header(`my-header`,`foo`) && Header(`my-header2`,`bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-http-app-1-my-gateway-web-fe80e69a38713941ea22": {
|
||||
"default-http-app-1-my-gateway-web-aaba0f24fd26e1ca2276": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-fe80e69a38713941ea22-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`) && Headers(`my-header`,`bar`)",
|
||||
Service: "default-http-app-1-my-gateway-web-aaba0f24fd26e1ca2276-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/bar`) && Header(`my-header`,`bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-http-app-1-my-gateway-web-330d644a7f2079e8f454-wrr": {
|
||||
"default-http-app-1-my-gateway-web-4a1b73e6f83804949a37-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
|
@ -1316,7 +1332,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"default-http-app-1-my-gateway-web-fe80e69a38713941ea22-wrr": {
|
||||
"default-http-app-1-my-gateway-web-aaba0f24fd26e1ca2276-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
|
@ -1371,6 +1387,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-default-my-gateway-web-efde1997778109a1f6eb-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/foo`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -1430,11 +1447,13 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-default-my-gateway-web-efde1997778109a1f6eb-wrr",
|
||||
Rule: "Host(`foo.com`) && Path(`/foo`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597-wrr",
|
||||
Rule: "Host(`bar.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -1520,6 +1539,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "bar-http-app-bar-my-gateway-web-66f5c78d03d948e36597-wrr",
|
||||
Rule: "Host(`bar.com`) && Path(`/bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -1579,6 +1599,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-wrr",
|
||||
Rule: "Host(`example.org`) && PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
Middlewares: []string{"default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-requestredirect-0"},
|
||||
},
|
||||
},
|
||||
|
@ -1647,6 +1668,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-wrr",
|
||||
Rule: "Host(`example.org`) && PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
Middlewares: []string{"default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-requestredirect-0"},
|
||||
},
|
||||
},
|
||||
|
@ -1912,6 +1934,7 @@ func TestLoadTCPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-1-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
|
@ -1969,11 +1992,13 @@ func TestLoadTCPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp-1"},
|
||||
Service: "default-tcp-app-1-my-tcp-gateway-tcp-1-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-tcp-app-2-my-tcp-gateway-tcp-2-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tcp-2"},
|
||||
Service: "default-tcp-app-2-my-tcp-gateway-tcp-2-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
|
@ -2053,6 +2078,7 @@ func TestLoadTCPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp-1"},
|
||||
Service: "default-tcp-app-my-tcp-gateway-tcp-1-e3b0c44298fc1c149afb-wrr",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
|
@ -2144,6 +2170,7 @@ func TestLoadTCPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
|
@ -2203,6 +2230,7 @@ func TestLoadTCPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -2266,6 +2294,7 @@ func TestLoadTCPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
|
@ -2321,11 +2350,13 @@ func TestLoadTCPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-default-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tcp"},
|
||||
Service: "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
|
@ -2403,6 +2434,7 @@ func TestLoadTCPRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "bar-tcp-app-bar-my-tcp-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
|
@ -2696,6 +2728,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-1-my-tls-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -2761,6 +2794,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-1-my-tls-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -2819,6 +2853,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tls-app-1-my-tls-gateway-tcp-f0dd0dd89f82eae1c270-wrr-0",
|
||||
Rule: "HostSNI(`foo.example.com`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -2878,12 +2913,14 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tcp-app-1-my-tls-gateway-tls-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
"default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a": {
|
||||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tls-app-1-my-tls-gateway-tcp-673acf455cb2dab0b43a-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -2973,6 +3010,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -3042,6 +3080,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tls-app-1-my-gateway-tls-f0dd0dd89f82eae1c270-wrr-0",
|
||||
Rule: "HostSNI(`foo.example.com`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3100,6 +3139,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tls-app-1-my-gateway-tls-f0dd0dd89f82eae1c270-wrr-0",
|
||||
Rule: "HostSNI(`foo.example.com`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3158,6 +3198,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tls-app-1-my-gateway-tls-f0dd0dd89f82eae1c270-wrr-0",
|
||||
Rule: "HostSNI(`foo.example.com`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3216,6 +3257,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tls-app-1-my-gateway-tls-d5342d75658583f03593-wrr-0",
|
||||
Rule: "HostSNI(`foo.example.com`) || HostSNI(`bar.example.com`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3274,6 +3316,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5-wrr-0",
|
||||
Rule: "HostSNI(`foo.default`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3332,6 +3375,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tls-app-default-my-gateway-tls-06ae57dcf13ab4c60ee5-wrr-0",
|
||||
Rule: "HostSNI(`foo.default`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3340,6 +3384,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26-wrr-0",
|
||||
Rule: "HostSNI(`foo.bar`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3420,6 +3465,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tls"},
|
||||
Service: "bar-tls-app-bar-my-gateway-tls-2279fe75c5156dc5eb26-wrr-0",
|
||||
Rule: "HostSNI(`foo.bar`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3478,6 +3524,7 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp-1"},
|
||||
Service: "default-tls-app-my-gateway-tcp-1-673acf455cb2dab0b43a-wrr",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3702,17 +3749,20 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-1-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-tcp-app-1-my-gateway-tls-1-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tls-1"},
|
||||
Service: "default-tcp-app-1-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
"default-tls-app-1-my-gateway-tls-2-59130f7db6718b7700c1": {
|
||||
EntryPoints: []string{"tls-2"},
|
||||
Service: "default-tls-app-1-my-gateway-tls-2-59130f7db6718b7700c1-wrr-0",
|
||||
Rule: "HostSNI(`pass.tls.foo.example.com`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3771,11 +3821,13 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-http-app-1-my-gateway-websecure-a431b128267aabc954fd": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "default-http-app-1-my-gateway-websecure-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -3881,17 +3933,20 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tls-1"},
|
||||
Service: "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
"default-tls-app-default-my-gateway-tls-2-59130f7db6718b7700c1": {
|
||||
EntryPoints: []string{"tls-2"},
|
||||
Service: "default-tls-app-default-my-gateway-tls-2-59130f7db6718b7700c1-wrr-0",
|
||||
Rule: "HostSNI(`pass.tls.foo.example.com`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -3950,11 +4005,13 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-http-app-default-my-gateway-websecure-a431b128267aabc954fd": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -4032,17 +4089,20 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tls-1"},
|
||||
Service: "default-tcp-app-default-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
"default-tls-app-default-my-gateway-tls-2-59130f7db6718b7700c1": {
|
||||
EntryPoints: []string{"tls-2"},
|
||||
Service: "default-tls-app-default-my-gateway-tls-2-59130f7db6718b7700c1-wrr-0",
|
||||
Rule: "HostSNI(`pass.tls.foo.example.com`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -4051,11 +4111,13 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tls-1"},
|
||||
Service: "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -4144,22 +4206,26 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-http-app-default-my-gateway-websecure-a431b128267aabc954fd": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
"bar-http-app-bar-my-gateway-web-a431b128267aabc954fd": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -4273,17 +4339,20 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "bar-tcp-app-bar-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tls-1"},
|
||||
Service: "bar-tcp-app-bar-my-gateway-tls-1-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
"bar-tls-app-bar-my-gateway-tls-2-59130f7db6718b7700c1": {
|
||||
EntryPoints: []string{"tls-2"},
|
||||
Service: "bar-tls-app-bar-my-gateway-tls-2-59130f7db6718b7700c1-wrr-0",
|
||||
Rule: "HostSNI(`pass.tls.foo.example.com`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
|
@ -4342,11 +4411,13 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "bar-http-app-bar-my-gateway-websecure-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -4423,11 +4494,13 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"tcp"},
|
||||
Service: "default-tcp-app-default-my-gateway-tcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-tcp-app-default-my-gateway-tls-e3b0c44298fc1c149afb": {
|
||||
EntryPoints: []string{"tls"},
|
||||
Service: "default-tcp-app-default-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0",
|
||||
Rule: "HostSNI(`*`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -4474,11 +4547,13 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-default-my-gateway-web-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-http-app-default-my-gateway-websecure-a431b128267aabc954fd": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "default-http-app-default-my-gateway-websecure-a431b128267aabc954fd-wrr",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
RuleSyntax: "v3",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
|
@ -4807,7 +4882,7 @@ func Test_extractRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedRule: "Path(`/foo/`) || Headers(`my-header`,`foo`)",
|
||||
expectedRule: "Path(`/foo/`) || Header(`my-header`,`foo`)",
|
||||
},
|
||||
{
|
||||
desc: "Path && Header rules",
|
||||
|
@ -4828,7 +4903,7 @@ func Test_extractRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedRule: "Path(`/foo/`) && Headers(`my-header`,`foo`)",
|
||||
expectedRule: "Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||
},
|
||||
{
|
||||
desc: "Host && Path && Header rules",
|
||||
|
@ -4850,7 +4925,7 @@ func Test_extractRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Headers(`my-header`,`foo`)",
|
||||
expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||
},
|
||||
{
|
||||
desc: "Host && (Path || Header) rules",
|
||||
|
@ -4874,7 +4949,7 @@ func Test_extractRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedRule: "Host(`foo.com`) && (Path(`/foo/`) || Headers(`my-header`,`foo`))",
|
||||
expectedRule: "Host(`foo.com`) && (Path(`/foo/`) || Header(`my-header`,`foo`))",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
"priority": 2147483645
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"api": {},
|
||||
"dashboard": {},
|
||||
"noop": {}
|
||||
},
|
||||
"middlewares": {
|
||||
"dashboard_redirect": {
|
||||
"redirectRegex": {
|
||||
|
@ -38,11 +43,6 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"api": {},
|
||||
"dashboard": {},
|
||||
"noop": {}
|
||||
}
|
||||
},
|
||||
"tcp": {},
|
||||
|
|
|
@ -54,6 +54,14 @@
|
|||
"priority": 2147483647
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"api": {},
|
||||
"dashboard": {},
|
||||
"noop": {},
|
||||
"ping": {},
|
||||
"prometheus": {},
|
||||
"rest": {}
|
||||
},
|
||||
"middlewares": {
|
||||
"dashboard_redirect": {
|
||||
"redirectRegex": {
|
||||
|
@ -70,14 +78,6 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"api": {},
|
||||
"dashboard": {},
|
||||
"noop": {},
|
||||
"ping": {},
|
||||
"prometheus": {},
|
||||
"rest": {}
|
||||
}
|
||||
},
|
||||
"tcp": {},
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
"rule": "HostRegexp(`^.+$`)"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"noop": {}
|
||||
},
|
||||
"middlewares": {
|
||||
"redirect-web-to-websecure": {
|
||||
"redirectScheme": {
|
||||
|
@ -20,11 +23,8 @@
|
|||
"permanent": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"noop": {}
|
||||
}
|
||||
},
|
||||
"tcp": {},
|
||||
"tls": {}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,9 @@
|
|||
"rule": "HostRegexp(`^.+$`)"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"noop": {}
|
||||
},
|
||||
"middlewares": {
|
||||
"redirect-web-to-443": {
|
||||
"redirectScheme": {
|
||||
|
@ -20,11 +23,8 @@
|
|||
"permanent": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"noop": {}
|
||||
}
|
||||
},
|
||||
"tcp": {},
|
||||
"tls": {}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,9 @@
|
|||
"rule": "HostRegexp(`^.+$`)"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"noop": {}
|
||||
},
|
||||
"middlewares": {
|
||||
"redirect-web-to-websecure": {
|
||||
"redirectScheme": {
|
||||
|
@ -20,11 +23,8 @@
|
|||
"permanent": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"noop": {}
|
||||
}
|
||||
},
|
||||
"tcp": {},
|
||||
"tls": {}
|
||||
}
|
||||
}
|
|
@ -65,6 +65,7 @@ func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configurati
|
|||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
Models: make(map[string]*dynamic.TCPModel),
|
||||
ServersTransports: make(map[string]*dynamic.TCPServersTransport),
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
|
@ -191,8 +192,13 @@ func (i *Provider) getEntryPointPort(name string, def *static.Redirections) (str
|
|||
}
|
||||
|
||||
func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
|
||||
defaultRuleSyntax := ""
|
||||
if i.staticCfg.Core != nil && i.staticCfg.Core.DefaultRuleSyntax != "" {
|
||||
defaultRuleSyntax = i.staticCfg.Core.DefaultRuleSyntax
|
||||
}
|
||||
|
||||
for name, ep := range i.staticCfg.EntryPoints {
|
||||
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil {
|
||||
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil && defaultRuleSyntax == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -208,7 +214,19 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
|
|||
}
|
||||
}
|
||||
|
||||
m.DefaultRuleSyntax = defaultRuleSyntax
|
||||
|
||||
cfg.HTTP.Models[name] = m
|
||||
|
||||
if cfg.TCP == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
mTCP := &dynamic.TCPModel{
|
||||
DefaultRuleSyntax: defaultRuleSyntax,
|
||||
}
|
||||
|
||||
cfg.TCP.Models[name] = mTCP
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
|
|||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
||||
Models: make(map[string]*dynamic.TCPModel),
|
||||
ServersTransports: make(map[string]*dynamic.TCPServersTransport),
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
|
@ -152,6 +153,13 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
|||
for name, rt := range cfg.HTTP.Routers {
|
||||
router := rt.DeepCopy()
|
||||
|
||||
if !router.DefaultRule && router.RuleSyntax == "" {
|
||||
for _, model := range cfg.HTTP.Models {
|
||||
router.RuleSyntax = model.DefaultRuleSyntax
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
eps := router.EntryPoints
|
||||
router.EntryPoints = nil
|
||||
|
||||
|
@ -183,6 +191,25 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
|||
|
||||
cfg.HTTP.Routers = rts
|
||||
|
||||
if cfg.TCP == nil || len(cfg.TCP.Models) == 0 {
|
||||
return cfg
|
||||
}
|
||||
|
||||
tcpRouters := make(map[string]*dynamic.TCPRouter)
|
||||
|
||||
for _, rt := range cfg.TCP.Routers {
|
||||
router := rt.DeepCopy()
|
||||
|
||||
if router.RuleSyntax == "" {
|
||||
for _, model := range cfg.TCP.Models {
|
||||
router.RuleSyntax = model.DefaultRuleSyntax
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.TCP.Routers = tcpRouters
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
|
|
@ -473,6 +473,7 @@ func Test_mergeConfiguration_defaultTCPEntryPoint(t *testing.T) {
|
|||
Services: map[string]*dynamic.TCPService{
|
||||
"service-1@provider-1": {},
|
||||
},
|
||||
Models: map[string]*dynamic.TCPModel{},
|
||||
ServersTransports: make(map[string]*dynamic.TCPServersTransport),
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ type mockProvider struct {
|
|||
throttleDuration time.Duration
|
||||
}
|
||||
|
||||
func (p *mockProvider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
func (p *mockProvider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
|
||||
wait := p.wait
|
||||
if wait == 0 {
|
||||
wait = 20 * time.Millisecond
|
||||
|
@ -48,7 +48,7 @@ func (p *mockProvider) Provide(configurationChan chan<- dynamic.Message, pool *s
|
|||
}
|
||||
|
||||
// ThrottleDuration returns the throttle duration.
|
||||
func (p mockProvider) ThrottleDuration() time.Duration {
|
||||
func (p *mockProvider) ThrottleDuration() time.Duration {
|
||||
return p.throttleDuration
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,7 @@ func TestNewConfigurationWatcher(t *testing.T) {
|
|||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
Models: map[string]*dynamic.TCPModel{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
|
@ -123,7 +124,7 @@ func TestWaitForRequiredProvider(t *testing.T) {
|
|||
|
||||
config := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo")),
|
||||
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar")),
|
||||
),
|
||||
}
|
||||
|
@ -167,60 +168,14 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
|
|||
|
||||
config := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo")),
|
||||
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar")),
|
||||
),
|
||||
}
|
||||
|
||||
config2 := &dynamic.Configuration{
|
||||
expectedConfig := dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("baz")),
|
||||
th.WithLoadBalancerServices(th.WithService("toto")),
|
||||
),
|
||||
}
|
||||
|
||||
watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{"defaultEP"}, "")
|
||||
|
||||
publishedConfigCount := 0
|
||||
var lastConfig dynamic.Configuration
|
||||
blockConfConsumer := make(chan struct{})
|
||||
watcher.AddListener(func(config dynamic.Configuration) {
|
||||
publishedConfigCount++
|
||||
lastConfig = config
|
||||
<-blockConfConsumer
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
watcher.allProvidersConfigs <- dynamic.Message{
|
||||
ProviderName: "mock",
|
||||
Configuration: config,
|
||||
}
|
||||
|
||||
watcher.allProvidersConfigs <- dynamic.Message{
|
||||
ProviderName: "mock",
|
||||
Configuration: config2,
|
||||
}
|
||||
|
||||
watcher.allProvidersConfigs <- dynamic.Message{
|
||||
ProviderName: "mock",
|
||||
Configuration: config,
|
||||
}
|
||||
|
||||
close(blockConfConsumer)
|
||||
|
||||
// give some time so that the configuration can be processed
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
|
||||
// after 20 milliseconds we should have 1 configs published
|
||||
assert.Equal(t, 1, publishedConfigCount, "times configs were published")
|
||||
|
||||
expected := dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP"))),
|
||||
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar@mock")),
|
||||
th.WithMiddlewares(),
|
||||
),
|
||||
|
@ -228,6 +183,7 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
|
|||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
Models: map[string]*dynamic.TCPModel{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
|
@ -242,7 +198,109 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, lastConfig)
|
||||
expectedConfig3 := dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar-config3@mock")),
|
||||
th.WithMiddlewares(),
|
||||
),
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
Models: map[string]*dynamic.TCPModel{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: map[string]tls.Options{
|
||||
"default": tls.DefaultTLSOptions,
|
||||
},
|
||||
Stores: map[string]tls.Store{},
|
||||
},
|
||||
}
|
||||
|
||||
config2 := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("baz", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("toto")),
|
||||
),
|
||||
}
|
||||
|
||||
config3 := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar-config3")),
|
||||
),
|
||||
}
|
||||
watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "")
|
||||
|
||||
// To be able to "block" the writes, we change the chan to remove buffering.
|
||||
watcher.allProvidersConfigs = make(chan dynamic.Message)
|
||||
|
||||
publishedConfigCount := 0
|
||||
|
||||
firstConfigHandled := make(chan struct{})
|
||||
blockConfConsumer := make(chan struct{})
|
||||
blockConfConsumerAssert := make(chan struct{})
|
||||
watcher.AddListener(func(config dynamic.Configuration) {
|
||||
publishedConfigCount++
|
||||
|
||||
if publishedConfigCount > 2 {
|
||||
t.Fatal("More than 2 published configuration")
|
||||
}
|
||||
|
||||
if publishedConfigCount == 1 {
|
||||
assert.Equal(t, expectedConfig, config)
|
||||
close(firstConfigHandled)
|
||||
|
||||
<-blockConfConsumer
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
if publishedConfigCount == 2 {
|
||||
assert.Equal(t, expectedConfig3, config)
|
||||
close(blockConfConsumerAssert)
|
||||
}
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
watcher.allProvidersConfigs <- dynamic.Message{
|
||||
ProviderName: "mock",
|
||||
Configuration: config,
|
||||
}
|
||||
|
||||
<-firstConfigHandled
|
||||
|
||||
watcher.allProvidersConfigs <- dynamic.Message{
|
||||
ProviderName: "mock",
|
||||
Configuration: config2,
|
||||
}
|
||||
|
||||
watcher.allProvidersConfigs <- dynamic.Message{
|
||||
ProviderName: "mock",
|
||||
Configuration: config,
|
||||
}
|
||||
|
||||
close(blockConfConsumer)
|
||||
|
||||
watcher.allProvidersConfigs <- dynamic.Message{
|
||||
ProviderName: "mock",
|
||||
Configuration: config3,
|
||||
}
|
||||
|
||||
select {
|
||||
case <-blockConfConsumerAssert:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("Timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenProvidersThrottleProviderConfigReload(t *testing.T) {
|
||||
|
@ -258,7 +316,7 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) {
|
|||
ProviderName: "mock",
|
||||
Configuration: &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i))),
|
||||
th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar")),
|
||||
),
|
||||
},
|
||||
|
@ -318,7 +376,7 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) {
|
|||
ProviderName: "mock",
|
||||
Configuration: &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo")),
|
||||
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar")),
|
||||
),
|
||||
},
|
||||
|
@ -350,14 +408,14 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
|
|||
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo")),
|
||||
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar")),
|
||||
),
|
||||
}
|
||||
|
||||
transientConfiguration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("bad")),
|
||||
th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bad")),
|
||||
),
|
||||
}
|
||||
|
@ -372,7 +430,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
watcher := NewConfigurationWatcher(routinesPool, pvd, []string{"defaultEP"}, "")
|
||||
watcher := NewConfigurationWatcher(routinesPool, pvd, []string{}, "")
|
||||
|
||||
var lastConfig dynamic.Configuration
|
||||
watcher.AddListener(func(conf dynamic.Configuration) {
|
||||
|
@ -389,7 +447,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
|
|||
|
||||
expected := dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP"))),
|
||||
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar@mock")),
|
||||
th.WithMiddlewares(),
|
||||
),
|
||||
|
@ -397,6 +455,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
|
|||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
Models: map[string]*dynamic.TCPModel{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
|
@ -419,14 +478,14 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
|
|||
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo")),
|
||||
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar")),
|
||||
),
|
||||
}
|
||||
|
||||
transientConfiguration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("bad")),
|
||||
th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bad")),
|
||||
),
|
||||
}
|
||||
|
@ -452,7 +511,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
|
|||
err := providerAggregator.AddProvider(pvd)
|
||||
assert.NoError(t, err)
|
||||
|
||||
watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{"defaultEP"}, "")
|
||||
watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{}, "")
|
||||
|
||||
var configurationReloads int
|
||||
var lastConfig dynamic.Configuration
|
||||
|
@ -479,7 +538,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
|
|||
|
||||
expected := dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP"))),
|
||||
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar@mock")),
|
||||
th.WithMiddlewares(),
|
||||
),
|
||||
|
@ -487,6 +546,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
|
|||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
Models: map[string]*dynamic.TCPModel{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
|
@ -509,7 +569,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
|
|||
func TestApplyConfigUnderStress(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
|
||||
watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{"defaultEP"}, "")
|
||||
watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "")
|
||||
|
||||
routinesPool.GoCtx(func(ctx context.Context) {
|
||||
i := 0
|
||||
|
@ -519,7 +579,7 @@ func TestApplyConfigUnderStress(t *testing.T) {
|
|||
return
|
||||
case watcher.allProvidersConfigs <- dynamic.Message{ProviderName: "mock", Configuration: &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i))),
|
||||
th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar")),
|
||||
),
|
||||
}}:
|
||||
|
@ -554,28 +614,28 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
|
|||
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo")),
|
||||
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar")),
|
||||
),
|
||||
}
|
||||
|
||||
transientConfiguration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("bad")),
|
||||
th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bad")),
|
||||
),
|
||||
}
|
||||
|
||||
transientConfiguration2 := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("bad2")),
|
||||
th.WithRouters(th.WithRouter("bad2", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bad2")),
|
||||
),
|
||||
}
|
||||
|
||||
finalConfiguration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("final")),
|
||||
th.WithRouters(th.WithRouter("final", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("final")),
|
||||
),
|
||||
}
|
||||
|
@ -595,7 +655,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
|
|||
err := providerAggregator.AddProvider(pvd)
|
||||
assert.NoError(t, err)
|
||||
|
||||
watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{"defaultEP"}, "")
|
||||
watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{}, "")
|
||||
|
||||
var configurationReloads int
|
||||
var lastConfig dynamic.Configuration
|
||||
|
@ -614,7 +674,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
|
|||
|
||||
expected := dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("defaultEP"))),
|
||||
th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("final@mock")),
|
||||
th.WithMiddlewares(),
|
||||
),
|
||||
|
@ -622,6 +682,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
|
|||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
Models: map[string]*dynamic.TCPModel{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
|
@ -646,7 +707,7 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
|
|||
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(th.WithRouter("foo")),
|
||||
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
|
||||
th.WithLoadBalancerServices(th.WithService("bar")),
|
||||
),
|
||||
}
|
||||
|
@ -658,7 +719,7 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
watcher := NewConfigurationWatcher(routinesPool, pvd, []string{"defaultEP"}, "")
|
||||
watcher := NewConfigurationWatcher(routinesPool, pvd, []string{}, "")
|
||||
|
||||
var publishedProviderConfig dynamic.Configuration
|
||||
|
||||
|
@ -677,8 +738,8 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
|
|||
expected := dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
th.WithRouters(
|
||||
th.WithRouter("foo@mock", th.WithEntryPoints("defaultEP")),
|
||||
th.WithRouter("foo@mock2", th.WithEntryPoints("defaultEP")),
|
||||
th.WithRouter("foo@mock", th.WithEntryPoints("ep")),
|
||||
th.WithRouter("foo@mock2", th.WithEntryPoints("ep")),
|
||||
),
|
||||
th.WithLoadBalancerServices(
|
||||
th.WithService("bar@mock"),
|
||||
|
@ -690,6 +751,7 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
|
|||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
Models: map[string]*dynamic.TCPModel{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
|
|
|
@ -131,7 +131,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
continue
|
||||
}
|
||||
|
||||
if err = muxer.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||
if err = muxer.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
|
|
|
@ -311,7 +311,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
|||
if routerConfig.TLS == nil {
|
||||
logger.Debug().Msgf("Adding route for %q", routerConfig.Rule)
|
||||
|
||||
if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||
if err := router.muxerTCP.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error().Err(err).Send()
|
||||
}
|
||||
|
@ -321,7 +321,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
|||
if routerConfig.TLS.Passthrough {
|
||||
logger.Debug().Msgf("Adding Passthrough route for %q", routerConfig.Rule)
|
||||
|
||||
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error().Err(err).Send()
|
||||
}
|
||||
|
@ -355,7 +355,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
|||
|
||||
logger.Debug().Msgf("Adding special TLS closing route for %q because broken TLS options %s", routerConfig.Rule, tlsOptionsName)
|
||||
|
||||
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, &brokenTLSRouter{}); err != nil {
|
||||
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, &brokenTLSRouter{}); err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error().Err(err).Send()
|
||||
}
|
||||
|
@ -389,7 +389,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
|||
|
||||
logger.Debug().Msgf("Adding TLS route for %q", routerConfig.Rule)
|
||||
|
||||
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
|
|
|
@ -201,9 +201,9 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
|||
conn.Close()
|
||||
}
|
||||
|
||||
// AddRoute defines a handler for the given rule.
|
||||
func (r *Router) AddRoute(rule string, priority int, target tcp.Handler) error {
|
||||
return r.muxerTCP.AddRoute(rule, priority, target)
|
||||
// AddTCPRoute defines a handler for the given rule.
|
||||
func (r *Router) AddTCPRoute(rule string, priority int, target tcp.Handler) error {
|
||||
return r.muxerTCP.AddRoute(rule, "", priority, target)
|
||||
}
|
||||
|
||||
// AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig.
|
||||
|
@ -267,7 +267,7 @@ func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
|
|||
}
|
||||
|
||||
rule := "HostSNI(`" + sniHost + "`)"
|
||||
if err := r.muxerHTTPS.AddRoute(rule, tcpmuxer.GetRulePriority(rule), tcpHandler); err != nil {
|
||||
if err := r.muxerHTTPS.AddRoute(rule, "", tcpmuxer.GetRulePriority(rule), tcpHandler); err != nil {
|
||||
log.Error().Err(err).Msg("Error while adding route for host")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -947,10 +947,10 @@ func TestPostgres(t *testing.T) {
|
|||
// This test requires to have a TLS route, but does not actually check the
|
||||
// content of the handler. It would require to code a TLS handshake to
|
||||
// check the SNI and content of the handlerFunc.
|
||||
err = router.muxerTCPTLS.AddRoute("HostSNI(`test.localhost`)", 0, nil)
|
||||
err = router.muxerTCPTLS.AddRoute("HostSNI(`test.localhost`)", "", 0, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.AddRoute("HostSNI(`*`)", 0, tcp2.HandlerFunc(func(conn tcp2.WriteCloser) {
|
||||
err = router.muxerTCP.AddRoute("HostSNI(`*`)", "", 0, tcp2.HandlerFunc(func(conn tcp2.WriteCloser) {
|
||||
_, _ = conn.Write([]byte("OK"))
|
||||
_ = conn.Close()
|
||||
}))
|
||||
|
|
|
@ -47,7 +47,7 @@ func TestShutdownTCP(t *testing.T) {
|
|||
router, err := tcprouter.NewRouter()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.AddRoute("HostSNI(`*`)", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
||||
err = router.AddTCPRoute("HostSNI(`*`)", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
||||
_, err := http.ReadRequest(bufio.NewReader(conn))
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
@ -319,7 +319,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
|||
|
||||
proxy = tracingMiddle.NewService(ctx, serviceName, proxy)
|
||||
|
||||
lb.Add(proxyName, proxy, nil)
|
||||
lb.Add(proxyName, proxy, server.Weight)
|
||||
|
||||
// servers are considered UP by default.
|
||||
info.UpdateServerStatus(target.String(), runtime.StatusUp)
|
||||
|
|
Loading…
Reference in a new issue