Merge branch 'master' of github.com:traefik/traefik
All checks were successful
Build & Push / build-and-push (push) Successful in 10m37s
All checks were successful
Build & Push / build-and-push (push) Successful in 10m37s
Signed-off-by: baalajimaestro <baalajimaestro@ptr.moe>
This commit is contained in:
commit
86a7f8765b
46 changed files with 1031 additions and 615 deletions
|
@ -99,38 +99,6 @@ helm install traefik traefik/traefik
|
||||||
- "--log.level=DEBUG"
|
- "--log.level=DEBUG"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Exposing the Traefik dashboard
|
|
||||||
|
|
||||||
This Helm chart does not expose the Traefik dashboard by default, for security concerns.
|
|
||||||
Thus, there are multiple ways to expose the dashboard.
|
|
||||||
For instance, the dashboard access could be achieved through a port-forward:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
kubectl port-forward $(kubectl get pods --selector "app.kubernetes.io/name=traefik" --output=name) 9000:9000
|
|
||||||
```
|
|
||||||
|
|
||||||
It can then be reached at: `http://127.0.0.1:9000/dashboard/`
|
|
||||||
|
|
||||||
Another way would be to apply your own configuration, for instance,
|
|
||||||
by defining and applying an IngressRoute CRD (`kubectl apply -f dashboard.yaml`):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# dashboard.yaml
|
|
||||||
apiVersion: traefik.io/v1alpha1
|
|
||||||
kind: IngressRoute
|
|
||||||
metadata:
|
|
||||||
name: dashboard
|
|
||||||
spec:
|
|
||||||
entryPoints:
|
|
||||||
- web
|
|
||||||
routes:
|
|
||||||
- match: Host(`traefik.localhost`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
|
|
||||||
kind: Rule
|
|
||||||
services:
|
|
||||||
- name: api@internal
|
|
||||||
kind: TraefikService
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use the Binary Distribution
|
## Use the Binary Distribution
|
||||||
|
|
||||||
Grab the latest binary from the [releases](https://github.com/traefik/traefik/releases) page.
|
Grab the latest binary from the [releases](https://github.com/traefik/traefik/releases) page.
|
||||||
|
|
|
@ -36,6 +36,7 @@ rules:
|
||||||
resources:
|
resources:
|
||||||
- services
|
- services
|
||||||
- secrets
|
- secrets
|
||||||
|
- nodes
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
|
@ -64,6 +65,23 @@ rules:
|
||||||
- ingresses/status
|
- ingresses/status
|
||||||
verbs:
|
verbs:
|
||||||
- update
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- traefik.io
|
||||||
|
resources:
|
||||||
|
- middlewares
|
||||||
|
- middlewaretcps
|
||||||
|
- ingressroutes
|
||||||
|
- traefikservices
|
||||||
|
- ingressroutetcps
|
||||||
|
- ingressrouteudps
|
||||||
|
- tlsoptions
|
||||||
|
- tlsstores
|
||||||
|
- serverstransports
|
||||||
|
- serverstransporttcps
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "You can find the reference for this file [there](../../reference/dynamic-configuration/kubernetes-crd/#rbac)."
|
!!! info "You can find the reference for this file [there](../../reference/dynamic-configuration/kubernetes-crd/#rbac)."
|
||||||
|
|
|
@ -341,6 +341,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||||
| [Derak Cloud](https://derak.cloud/) | `derak` | `DERAK_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/derak) |
|
| [Derak Cloud](https://derak.cloud/) | `derak` | `DERAK_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/derak) |
|
||||||
| [deSEC](https://desec.io) | `desec` | `DESEC_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/desec) |
|
| [deSEC](https://desec.io) | `desec` | `DESEC_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/desec) |
|
||||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/digitalocean) |
|
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/digitalocean) |
|
||||||
|
| [DirectAdmin](https://www.directadmin.com) | `directadmin` | `DIRECTADMIN_API_URL` , `DIRECTADMIN_USERNAME`, `DIRECTADMIN_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/directadmin) |
|
||||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | [Additional configuration](https://go-acme.github.io/lego/dns/dnsmadeeasy) |
|
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | [Additional configuration](https://go-acme.github.io/lego/dns/dnsmadeeasy) |
|
||||||
| [dnsHome.de](https://www.dnshome.de) | `dnsHomede` | `DNSHOMEDE_CREDENTIALS` | [Additional configuration](https://go-acme.github.io/lego/dns/dnshomede) |
|
| [dnsHome.de](https://www.dnshome.de) | `dnsHomede` | `DNSHOMEDE_CREDENTIALS` | [Additional configuration](https://go-acme.github.io/lego/dns/dnshomede) |
|
||||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/dnsimple) |
|
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/dnsimple) |
|
||||||
|
@ -384,12 +385,15 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||||
| [Joker.com](https://joker.com) | `joker` | `JOKER_API_MODE` with `JOKER_API_KEY` or `JOKER_USERNAME`, `JOKER_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/joker) |
|
| [Joker.com](https://joker.com) | `joker` | `JOKER_API_MODE` with `JOKER_API_KEY` or `JOKER_USERNAME`, `JOKER_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/joker) |
|
||||||
| [Liara](https://liara.ir) | `liara` | `LIARA_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/liara) |
|
| [Liara](https://liara.ir) | `liara` | `LIARA_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/liara) |
|
||||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/lightsail) |
|
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/lightsail) |
|
||||||
|
| [Lima-City](https://www.lima-city.de) | `limacity` | `LIMACITY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/limacity) |
|
||||||
| [Linode v4](https://www.linode.com) | `linode` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
|
| [Linode v4](https://www.linode.com) | `linode` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
|
||||||
| [Liquid Web](https://www.liquidweb.com/) | `liquidweb` | `LIQUID_WEB_PASSWORD`, `LIQUID_WEB_USERNAME`, `LIQUID_WEB_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/liquidweb) |
|
| [Liquid Web](https://www.liquidweb.com/) | `liquidweb` | `LIQUID_WEB_PASSWORD`, `LIQUID_WEB_USERNAME`, `LIQUID_WEB_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/liquidweb) |
|
||||||
| [Loopia](https://loopia.com/) | `loopia` | `LOOPIA_API_PASSWORD`, `LOOPIA_API_USER` | [Additional configuration](https://go-acme.github.io/lego/dns/loopia) |
|
| [Loopia](https://loopia.com/) | `loopia` | `LOOPIA_API_PASSWORD`, `LOOPIA_API_USER` | [Additional configuration](https://go-acme.github.io/lego/dns/loopia) |
|
||||||
| [LuaDNS](https://luadns.com) | `luadns` | `LUADNS_API_USERNAME`, `LUADNS_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/luadns) |
|
| [LuaDNS](https://luadns.com) | `luadns` | `LUADNS_API_USERNAME`, `LUADNS_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/luadns) |
|
||||||
| [Mail-in-a-Box](https://mailinabox.email) | `mailinabox` | `MAILINABOX_EMAIL`, `MAILINABOX_PASSWORD`, `MAILINABOX_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/mailinabox) |
|
| [Mail-in-a-Box](https://mailinabox.email) | `mailinabox` | `MAILINABOX_EMAIL`, `MAILINABOX_PASSWORD`, `MAILINABOX_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/mailinabox) |
|
||||||
| [Metaname](https://metaname.net) | `metaname` | `METANAME_ACCOUNT_REFERENCE`, `METANAME_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/metaname) |
|
| [Metaname](https://metaname.net) | `metaname` | `METANAME_ACCOUNT_REFERENCE`, `METANAME_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/metaname) |
|
||||||
|
| [mijn.host](https://mijn.host/) | `mijnhost` | `MIJNHOST_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/mijnhost) |
|
||||||
|
| [Mittwald](https://www.mittwald.de) | `mittwald` | `MITTWALD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/mittwald) |
|
||||||
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
|
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
|
||||||
| [Mythic Beasts](https://www.mythic-beasts.com) | `mythicbeasts` | `MYTHICBEASTS_USER_NAME`, `MYTHICBEASTS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mythicbeasts) |
|
| [Mythic Beasts](https://www.mythic-beasts.com) | `mythicbeasts` | `MYTHICBEASTS_USER_NAME`, `MYTHICBEASTS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mythicbeasts) |
|
||||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
|
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
|
||||||
|
@ -418,8 +422,8 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | [Additional configuration](https://go-acme.github.io/lego/dns/route53) |
|
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | [Additional configuration](https://go-acme.github.io/lego/dns/route53) |
|
||||||
| [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/sakuracloud) |
|
| [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/sakuracloud) |
|
||||||
| [Scaleway](https://www.scaleway.com) | `scaleway` | `SCW_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/scaleway) |
|
| [Scaleway](https://www.scaleway.com) | `scaleway` | `SCW_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/scaleway) |
|
||||||
| [Selectel](https://selectel.ru/en/) | `selectel` | `SELECTEL_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/selectel) |
|
|
||||||
| [Selectel v2](https://selectel.ru/en/) | `selectelv2` | `SELECTELV2_ACCOUNT_ID`, `SELECTELV2_PASSWORD`, `SELECTELV2_PROJECT_ID`, `SELECTELV2_USERNAME` | [Additional configuration](https://go-acme.github.io/lego/dns/selectelv2) |
|
| [Selectel v2](https://selectel.ru/en/) | `selectelv2` | `SELECTELV2_ACCOUNT_ID`, `SELECTELV2_PASSWORD`, `SELECTELV2_PROJECT_ID`, `SELECTELV2_USERNAME` | [Additional configuration](https://go-acme.github.io/lego/dns/selectelv2) |
|
||||||
|
| [Selectel](https://selectel.ru/en/) | `selectel` | `SELECTEL_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/selectel) |
|
||||||
| [Servercow](https://servercow.de) | `servercow` | `SERVERCOW_USERNAME`, `SERVERCOW_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/servercow) |
|
| [Servercow](https://servercow.de) | `servercow` | `SERVERCOW_USERNAME`, `SERVERCOW_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/servercow) |
|
||||||
| [Shellrent](https://www.shellrent.com) | `shellrent` | `SHELLRENT_USERNAME`, `SHELLRENT_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/shellrent) |
|
| [Shellrent](https://www.shellrent.com) | `shellrent` | `SHELLRENT_USERNAME`, `SHELLRENT_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/shellrent) |
|
||||||
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
|
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
|
||||||
|
|
|
@ -250,6 +250,8 @@ accessLog:
|
||||||
| `TLSVersion` | The TLS version used by the connection (e.g. `1.2`) (if connection is TLS). |
|
| `TLSVersion` | The TLS version used by the connection (e.g. `1.2`) (if connection is TLS). |
|
||||||
| `TLSCipher` | The TLS cipher used by the connection (e.g. `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`) (if connection is TLS) |
|
| `TLSCipher` | The TLS cipher used by the connection (e.g. `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`) (if connection is TLS) |
|
||||||
| `TLSClientSubject` | The string representation of the TLS client certificate's Subject (e.g. `CN=username,O=organization`) |
|
| `TLSClientSubject` | The string representation of the TLS client certificate's Subject (e.g. `CN=username,O=organization`) |
|
||||||
|
| `TraceId` | A consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string |
|
||||||
|
| `SpanId` | A unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string. |
|
||||||
|
|
||||||
## Log Rotation
|
## Log Rotation
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,6 @@ Defines the list of query parameters to not redact.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
tracing:
|
tracing:
|
||||||
otlp:
|
|
||||||
safeQueryParams:
|
safeQueryParams:
|
||||||
- bar
|
- bar
|
||||||
- buz
|
- buz
|
||||||
|
@ -178,10 +177,9 @@ tracing:
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[tracing]
|
[tracing]
|
||||||
[tracing.otlp]
|
|
||||||
safeQueryParams = ["bar", "buz"]
|
safeQueryParams = ["bar", "buz"]
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--tracing.otlp.safeQueryParams=bar,buz
|
--tracing.safeQueryParams=bar,buz
|
||||||
```
|
```
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
[http.services.Service03]
|
[http.services.Service03]
|
||||||
[http.services.Service03.mirroring]
|
[http.services.Service03.mirroring]
|
||||||
service = "foobar"
|
service = "foobar"
|
||||||
|
mirrorBody = true
|
||||||
maxBodySize = 42
|
maxBodySize = 42
|
||||||
|
|
||||||
[[http.services.Service03.mirroring.mirrors]]
|
[[http.services.Service03.mirroring.mirrors]]
|
||||||
|
|
|
@ -89,6 +89,7 @@ http:
|
||||||
Service03:
|
Service03:
|
||||||
mirroring:
|
mirroring:
|
||||||
service: foobar
|
service: foobar
|
||||||
|
mirrorBody: true
|
||||||
maxBodySize: 42
|
maxBodySize: 42
|
||||||
mirrors:
|
mirrors:
|
||||||
- name: foobar
|
- name: foobar
|
||||||
|
|
|
@ -2506,6 +2506,11 @@ spec:
|
||||||
Default value is -1, which means unlimited size.
|
Default value is -1, which means unlimited size.
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
|
mirrorBody:
|
||||||
|
description: |-
|
||||||
|
MirrorBody defines whether the body of the request should be mirrored.
|
||||||
|
Default value is true.
|
||||||
|
type: boolean
|
||||||
mirrors:
|
mirrors:
|
||||||
description: Mirrors defines the list of mirrors where Traefik
|
description: Mirrors defines the list of mirrors where Traefik
|
||||||
will duplicate the traffic.
|
will duplicate the traffic.
|
||||||
|
|
|
@ -63,6 +63,7 @@ spec:
|
||||||
mirroring:
|
mirroring:
|
||||||
name: wrr2
|
name: wrr2
|
||||||
kind: TraefikService
|
kind: TraefikService
|
||||||
|
mirrorBody: true
|
||||||
# Optional
|
# Optional
|
||||||
maxBodySize: 2000000000
|
maxBodySize: 2000000000
|
||||||
mirrors:
|
mirrors:
|
||||||
|
|
|
@ -264,6 +264,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
||||||
| `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` |
|
| `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` |
|
||||||
| `traefik/http/services/Service03/mirroring/healthCheck` | `` |
|
| `traefik/http/services/Service03/mirroring/healthCheck` | `` |
|
||||||
| `traefik/http/services/Service03/mirroring/maxBodySize` | `42` |
|
| `traefik/http/services/Service03/mirroring/maxBodySize` | `42` |
|
||||||
|
| `traefik/http/services/Service03/mirroring/mirrorBody` | `true` |
|
||||||
| `traefik/http/services/Service03/mirroring/mirrors/0/name` | `foobar` |
|
| `traefik/http/services/Service03/mirroring/mirrors/0/name` | `foobar` |
|
||||||
| `traefik/http/services/Service03/mirroring/mirrors/0/percent` | `42` |
|
| `traefik/http/services/Service03/mirroring/mirrors/0/percent` | `42` |
|
||||||
| `traefik/http/services/Service03/mirroring/mirrors/1/name` | `foobar` |
|
| `traefik/http/services/Service03/mirroring/mirrors/1/name` | `foobar` |
|
||||||
|
|
|
@ -121,6 +121,11 @@ spec:
|
||||||
Default value is -1, which means unlimited size.
|
Default value is -1, which means unlimited size.
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
|
mirrorBody:
|
||||||
|
description: |-
|
||||||
|
MirrorBody defines whether the body of the request should be mirrored.
|
||||||
|
Default value is true.
|
||||||
|
type: boolean
|
||||||
mirrors:
|
mirrors:
|
||||||
description: Mirrors defines the list of mirrors where Traefik
|
description: Mirrors defines the list of mirrors where Traefik
|
||||||
will duplicate the traffic.
|
will duplicate the traffic.
|
||||||
|
|
|
@ -1207,6 +1207,7 @@ http:
|
||||||
The mirroring is able to mirror requests sent to a service to other services.
|
The mirroring is able to mirror requests sent to a service to other services.
|
||||||
Please note that by default the whole request is buffered in memory while it is being mirrored.
|
Please note that by default the whole request is buffered in memory while it is being mirrored.
|
||||||
See the maxBodySize option in the example below for how to modify this behaviour.
|
See the maxBodySize option in the example below for how to modify this behaviour.
|
||||||
|
You can also omit the request body by setting the mirrorBody option to `false`.
|
||||||
|
|
||||||
!!! info "Supported Providers"
|
!!! info "Supported Providers"
|
||||||
|
|
||||||
|
@ -1219,6 +1220,9 @@ http:
|
||||||
mirrored-api:
|
mirrored-api:
|
||||||
mirroring:
|
mirroring:
|
||||||
service: appv1
|
service: appv1
|
||||||
|
# mirrorBody defines whether the request body should be mirrored.
|
||||||
|
# Default value is true.
|
||||||
|
mirrorBody: false
|
||||||
# maxBodySize is the maximum size allowed for the body of the request.
|
# maxBodySize is the maximum size allowed for the body of the request.
|
||||||
# If the body is larger, the request is not mirrored.
|
# If the body is larger, the request is not mirrored.
|
||||||
# Default value is -1, which means unlimited size.
|
# Default value is -1, which means unlimited size.
|
||||||
|
@ -1248,6 +1252,9 @@ http:
|
||||||
# If the body is larger, the request is not mirrored.
|
# If the body is larger, the request is not mirrored.
|
||||||
# Default value is -1, which means unlimited size.
|
# Default value is -1, which means unlimited size.
|
||||||
maxBodySize = 1024
|
maxBodySize = 1024
|
||||||
|
# mirrorBody defines whether the request body should be mirrored.
|
||||||
|
# Default value is true.
|
||||||
|
mirrorBody = false
|
||||||
[[http.services.mirrored-api.mirroring.mirrors]]
|
[[http.services.mirrored-api.mirroring.mirrors]]
|
||||||
name = "appv2"
|
name = "appv2"
|
||||||
percent = 10
|
percent = 10
|
||||||
|
|
22
go.mod
22
go.mod
|
@ -17,7 +17,7 @@ require (
|
||||||
github.com/docker/go-connections v0.5.0
|
github.com/docker/go-connections v0.5.0
|
||||||
github.com/fatih/structs v1.1.0
|
github.com/fatih/structs v1.1.0
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/go-acme/lego/v4 v4.17.4
|
github.com/go-acme/lego/v4 v4.18.0
|
||||||
github.com/go-kit/kit v0.13.0
|
github.com/go-kit/kit v0.13.0
|
||||||
github.com/go-kit/log v0.2.1
|
github.com/go-kit/log v0.2.1
|
||||||
github.com/golang/protobuf v1.5.4
|
github.com/golang/protobuf v1.5.4
|
||||||
|
@ -64,7 +64,7 @@ require (
|
||||||
github.com/tetratelabs/wazero v1.7.2
|
github.com/tetratelabs/wazero v1.7.2
|
||||||
github.com/tidwall/gjson v1.17.0
|
github.com/tidwall/gjson v1.17.0
|
||||||
github.com/traefik/grpc-web v0.16.0
|
github.com/traefik/grpc-web v0.16.0
|
||||||
github.com/traefik/paerser v0.2.0
|
github.com/traefik/paerser v0.2.1
|
||||||
github.com/traefik/yaegi v0.16.1
|
github.com/traefik/yaegi v0.16.1
|
||||||
github.com/unrolled/render v1.0.2
|
github.com/unrolled/render v1.0.2
|
||||||
github.com/unrolled/secure v1.0.9
|
github.com/unrolled/secure v1.0.9
|
||||||
|
@ -85,8 +85,8 @@ require (
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // No tag on the repo.
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // No tag on the repo.
|
||||||
golang.org/x/mod v0.18.0
|
golang.org/x/mod v0.18.0
|
||||||
golang.org/x/net v0.26.0
|
golang.org/x/net v0.26.0
|
||||||
golang.org/x/sys v0.21.0
|
golang.org/x/sys v0.23.0
|
||||||
golang.org/x/text v0.16.0
|
golang.org/x/text v0.17.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.5.0
|
||||||
golang.org/x/tools v0.22.0
|
golang.org/x/tools v0.22.0
|
||||||
google.golang.org/grpc v1.64.1
|
google.golang.org/grpc v1.64.1
|
||||||
|
@ -221,7 +221,7 @@ require (
|
||||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||||
github.com/hashicorp/serf v0.10.1 // indirect
|
github.com/hashicorp/serf v0.10.1 // indirect
|
||||||
github.com/huandu/xstrings v1.4.0 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||||
github.com/imdario/mergo v0.3.16 // indirect
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
||||||
|
@ -300,12 +300,12 @@ require (
|
||||||
github.com/selectel/go-selvpcclient/v3 v3.1.1 // indirect
|
github.com/selectel/go-selvpcclient/v3 v3.1.1 // indirect
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/shopspring/decimal v1.3.1 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||||
github.com/softlayer/softlayer-go v1.1.5 // indirect
|
github.com/softlayer/softlayer-go v1.1.5 // indirect
|
||||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||||
github.com/sony/gobreaker v0.5.0 // indirect
|
github.com/sony/gobreaker v0.5.0 // indirect
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.7.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898 // indirect
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898 // indirect
|
||||||
|
@ -317,7 +317,7 @@ require (
|
||||||
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
||||||
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect
|
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect
|
||||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||||
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
github.com/vultr/govultr/v3 v3.9.0 // indirect
|
||||||
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e // indirect
|
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e // indirect
|
||||||
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 // indirect
|
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
|
@ -338,10 +338,10 @@ require (
|
||||||
go.uber.org/ratelimit v0.3.0 // indirect
|
go.uber.org/ratelimit v0.3.0 // indirect
|
||||||
go.uber.org/zap v1.26.0 // indirect
|
go.uber.org/zap v1.26.0 // indirect
|
||||||
golang.org/x/arch v0.4.0 // indirect
|
golang.org/x/arch v0.4.0 // indirect
|
||||||
golang.org/x/crypto v0.24.0 // indirect
|
golang.org/x/crypto v0.26.0 // indirect
|
||||||
golang.org/x/oauth2 v0.21.0 // indirect
|
golang.org/x/oauth2 v0.21.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/term v0.21.0 // indirect
|
golang.org/x/term v0.23.0 // indirect
|
||||||
google.golang.org/api v0.172.0 // indirect
|
google.golang.org/api v0.172.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
|
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
|
|
48
go.sum
48
go.sum
|
@ -283,8 +283,8 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||||
|
@ -302,8 +302,8 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv
|
||||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-acme/lego/v4 v4.17.4 h1:h0nePd3ObP6o7kAkndtpTzCw8shOZuWckNYeUQwo36Q=
|
github.com/go-acme/lego/v4 v4.18.0 h1:2hH8KcdRBSb+p5o9VZIm61GAOXYALgILUCSs1Q+OYsk=
|
||||||
github.com/go-acme/lego/v4 v4.17.4/go.mod h1:dU94SvPNqimEeb7EVilGGSnS0nU1O5Exir0pQ4QFL4U=
|
github.com/go-acme/lego/v4 v4.18.0/go.mod h1:Blkg3izvXpl3zxk7WKngIuwR2I/hvYVP3vRnvgBp7m8=
|
||||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||||
|
@ -553,8 +553,8 @@ github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY
|
||||||
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E=
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E=
|
||||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||||
|
@ -922,8 +922,8 @@ github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj
|
||||||
github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk=
|
github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk=
|
||||||
github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
|
github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
@ -950,8 +950,8 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B
|
||||||
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
|
@ -1016,8 +1016,8 @@ github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltas
|
||||||
github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4=
|
github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4=
|
||||||
github.com/traefik/http-wasm-host-go v0.0.0-20240618100324-3c53dcaa1a70 h1:I+oBnV0orhmasb87yaX54tOAfqrV9+yKoQ1Cum5mq8w=
|
github.com/traefik/http-wasm-host-go v0.0.0-20240618100324-3c53dcaa1a70 h1:I+oBnV0orhmasb87yaX54tOAfqrV9+yKoQ1Cum5mq8w=
|
||||||
github.com/traefik/http-wasm-host-go v0.0.0-20240618100324-3c53dcaa1a70/go.mod h1:zQB3w+df4hryDEqBorGyA1DwPJ86LfKIASNLFuj6CuI=
|
github.com/traefik/http-wasm-host-go v0.0.0-20240618100324-3c53dcaa1a70/go.mod h1:zQB3w+df4hryDEqBorGyA1DwPJ86LfKIASNLFuj6CuI=
|
||||||
github.com/traefik/paerser v0.2.0 h1:zqCLGSXoNlcBd+mzqSCLjon/I6phqIjeJL2xFB2ysgQ=
|
github.com/traefik/paerser v0.2.1 h1:LFgeak1NmjEHF53c9ENdXdL1UMkF/lD5t+7Evsz4hH4=
|
||||||
github.com/traefik/paerser v0.2.0/go.mod h1:afzaVcgF8A+MpTnPG4wBr4whjanCSYA6vK5RwaYVtRc=
|
github.com/traefik/paerser v0.2.1/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
|
||||||
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
||||||
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
||||||
github.com/transip/gotransip/v6 v6.23.0 h1:PsTdjortrEZ8IFFifEryzjVjOy9SgK4ahlnhKBBIQgA=
|
github.com/transip/gotransip/v6 v6.23.0 h1:PsTdjortrEZ8IFFifEryzjVjOy9SgK4ahlnhKBBIQgA=
|
||||||
|
@ -1053,8 +1053,8 @@ github.com/vulcand/oxy/v2 v2.0.0 h1:V+scHhd2xBjO8ojBRgxCM+OdZxRA/YTs8M70w5tdNy8=
|
||||||
github.com/vulcand/oxy/v2 v2.0.0/go.mod h1:uIAz3sYafO7i+V3SC8oDlMn/lt1i9aWcyXuXqVswKzE=
|
github.com/vulcand/oxy/v2 v2.0.0/go.mod h1:uIAz3sYafO7i+V3SC8oDlMn/lt1i9aWcyXuXqVswKzE=
|
||||||
github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50=
|
github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50=
|
||||||
github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA=
|
github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA=
|
||||||
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
|
github.com/vultr/govultr/v3 v3.9.0 h1:63V/22mpfquRA5DenJ9EF0VozHg0k+X4dhUWcDXHPyc=
|
||||||
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
|
github.com/vultr/govultr/v3 v3.9.0/go.mod h1:Rd8ebpXm7jxH3MDmhnEs+zrlYW212ouhx+HeUMfHm2o=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
|
@ -1161,8 +1161,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
@ -1253,8 +1253,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -1322,8 +1322,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
@ -1333,8 +1333,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
@ -1348,8 +1348,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|
|
@ -606,10 +606,8 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() {
|
||||||
func (s *AccessLogSuite) TestAccessLogDisabledForInternals() {
|
func (s *AccessLogSuite) TestAccessLogDisabledForInternals() {
|
||||||
ensureWorkingDirectoryIsClean()
|
ensureWorkingDirectoryIsClean()
|
||||||
|
|
||||||
file := s.adaptFile("fixtures/access_log/access_log_ping.toml", struct{}{})
|
|
||||||
|
|
||||||
// Start Traefik.
|
// Start Traefik.
|
||||||
s.traefikCmd(withConfigFile(file))
|
s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
traefikLog, err := os.ReadFile(traefikTestLogFile)
|
traefikLog, err := os.ReadFile(traefikTestLogFile)
|
||||||
|
@ -619,7 +617,7 @@ func (s *AccessLogSuite) TestAccessLogDisabledForInternals() {
|
||||||
|
|
||||||
// waitForTraefik makes at least one call to the rawdata api endpoint,
|
// waitForTraefik makes at least one call to the rawdata api endpoint,
|
||||||
// but the logs for this endpoint are ignored in checkAccessLogOutput.
|
// but the logs for this endpoint are ignored in checkAccessLogOutput.
|
||||||
s.waitForTraefik("customPing")
|
s.waitForTraefik("service3")
|
||||||
|
|
||||||
s.checkStatsForLogFile()
|
s.checkStatsForLogFile()
|
||||||
|
|
||||||
|
@ -636,8 +634,9 @@ func (s *AccessLogSuite) TestAccessLogDisabledForInternals() {
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
// Make some requests on the custom ping router.
|
// Make some requests on the custom ping router.
|
||||||
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/ping", nil)
|
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8010/ping", nil)
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
req.Host = "ping.docker.local"
|
||||||
|
|
||||||
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
@ -649,6 +648,25 @@ func (s *AccessLogSuite) TestAccessLogDisabledForInternals() {
|
||||||
|
|
||||||
require.Equal(s.T(), 0, count)
|
require.Equal(s.T(), 0, count)
|
||||||
|
|
||||||
|
// Make some requests on the custom ping router in error.
|
||||||
|
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8010/ping-error", nil)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
req.Host = "ping-error.docker.local"
|
||||||
|
|
||||||
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.BodyContains("X-Forwarded-Host: ping-error.docker.local"))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.BodyContains("X-Forwarded-Host: ping-error.docker.local"))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
// Here we verify that the remove of observability doesn't break the metrics for the error page service.
|
||||||
|
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/metrics", nil)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("service3"))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyNotContains("service=\"ping"))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
// Verify no other Traefik problems.
|
// Verify no other Traefik problems.
|
||||||
s.checkNoOtherTraefikProblems()
|
s.checkNoOtherTraefikProblems()
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,3 +53,11 @@ profiles:
|
||||||
- HTTPRouteRequestTimeout
|
- HTTPRouteRequestTimeout
|
||||||
name: GATEWAY-HTTP
|
name: GATEWAY-HTTP
|
||||||
summary: Core tests succeeded. Extended tests succeeded.
|
summary: Core tests succeeded. Extended tests succeeded.
|
||||||
|
- core:
|
||||||
|
result: success
|
||||||
|
statistics:
|
||||||
|
Failed: 0
|
||||||
|
Passed: 11
|
||||||
|
Skipped: 0
|
||||||
|
name: GATEWAY-TLS
|
||||||
|
summary: Core tests succeeded.
|
||||||
|
|
|
@ -22,10 +22,17 @@
|
||||||
address = ":8008"
|
address = ":8008"
|
||||||
[entryPoints.preflight]
|
[entryPoints.preflight]
|
||||||
address = ":8009"
|
address = ":8009"
|
||||||
|
[entryPoints.ping]
|
||||||
|
address = ":8010"
|
||||||
|
|
||||||
[api]
|
[api]
|
||||||
insecure = true
|
insecure = true
|
||||||
|
|
||||||
|
[ping]
|
||||||
|
|
||||||
|
[metrics]
|
||||||
|
[metrics.prometheus]
|
||||||
|
|
||||||
[providers]
|
[providers]
|
||||||
[providers.docker]
|
[providers.docker]
|
||||||
exposedByDefault = false
|
exposedByDefault = false
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
[global]
|
|
||||||
checkNewVersion = false
|
|
||||||
sendAnonymousUsage = false
|
|
||||||
|
|
||||||
[log]
|
|
||||||
level = "ERROR"
|
|
||||||
filePath = "traefik.log"
|
|
||||||
|
|
||||||
[accessLog]
|
|
||||||
filePath = "access.log"
|
|
||||||
|
|
||||||
[entryPoints]
|
|
||||||
[entryPoints.web]
|
|
||||||
address = ":8000"
|
|
||||||
|
|
||||||
[api]
|
|
||||||
insecure = true
|
|
||||||
|
|
||||||
[ping]
|
|
||||||
|
|
||||||
[providers]
|
|
||||||
[providers.file]
|
|
||||||
filename = "{{ .SelfFilename }}"
|
|
||||||
|
|
||||||
## dynamic configuration ##
|
|
||||||
[http.routers]
|
|
||||||
[http.routers.customPing]
|
|
||||||
entryPoints = ["web"]
|
|
||||||
rule = "PathPrefix(`/ping`)"
|
|
||||||
service = "ping@internal"
|
|
|
@ -2506,6 +2506,11 @@ spec:
|
||||||
Default value is -1, which means unlimited size.
|
Default value is -1, which means unlimited size.
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
|
mirrorBody:
|
||||||
|
description: |-
|
||||||
|
MirrorBody defines whether the body of the request should be mirrored.
|
||||||
|
Default value is true.
|
||||||
|
type: boolean
|
||||||
mirrors:
|
mirrors:
|
||||||
description: Mirrors defines the list of mirrors where Traefik
|
description: Mirrors defines the list of mirrors where Traefik
|
||||||
will duplicate the traffic.
|
will duplicate the traffic.
|
||||||
|
|
|
@ -28,6 +28,10 @@
|
||||||
service = "mirrorWithMaxBody"
|
service = "mirrorWithMaxBody"
|
||||||
rule = "Path(`/whoamiWithMaxBody`)"
|
rule = "Path(`/whoamiWithMaxBody`)"
|
||||||
|
|
||||||
|
[http.routers.router3]
|
||||||
|
service = "mirrorWithoutBody"
|
||||||
|
rule = "Path(`/whoamiWithoutBody`)"
|
||||||
|
|
||||||
|
|
||||||
[http.services]
|
[http.services]
|
||||||
[http.services.mirror.mirroring]
|
[http.services.mirror.mirroring]
|
||||||
|
@ -49,6 +53,16 @@
|
||||||
name = "mirror2"
|
name = "mirror2"
|
||||||
percent = 50
|
percent = 50
|
||||||
|
|
||||||
|
[http.services.mirrorWithoutBody.mirroring]
|
||||||
|
service = "service1"
|
||||||
|
mirrorBody = false
|
||||||
|
[[http.services.mirrorWithoutBody.mirroring.mirrors]]
|
||||||
|
name = "mirror1"
|
||||||
|
percent = 10
|
||||||
|
[[http.services.mirrorWithoutBody.mirroring.mirrors]]
|
||||||
|
name = "mirror2"
|
||||||
|
percent = 50
|
||||||
|
|
||||||
|
|
||||||
[http.services.service1.loadBalancer]
|
[http.services.service1.loadBalancer]
|
||||||
[[http.services.service1.loadBalancer.servers]]
|
[[http.services.service1.loadBalancer.servers]]
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -193,7 +194,11 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
||||||
Version: *k8sConformanceTraefikVersion,
|
Version: *k8sConformanceTraefikVersion,
|
||||||
Contact: []string{"@traefik/maintainers"},
|
Contact: []string{"@traefik/maintainers"},
|
||||||
},
|
},
|
||||||
ConformanceProfiles: sets.New(ksuite.GatewayHTTPConformanceProfileName, ksuite.GatewayGRPCConformanceProfileName),
|
ConformanceProfiles: sets.New(
|
||||||
|
ksuite.GatewayHTTPConformanceProfileName,
|
||||||
|
ksuite.GatewayGRPCConformanceProfileName,
|
||||||
|
ksuite.GatewayTLSConformanceProfileName,
|
||||||
|
),
|
||||||
SupportedFeatures: sets.New(
|
SupportedFeatures: sets.New(
|
||||||
features.SupportGateway,
|
features.SupportGateway,
|
||||||
features.SupportGatewayPort8080,
|
features.SupportGatewayPort8080,
|
||||||
|
@ -207,6 +212,7 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
||||||
features.SupportHTTPRoutePathRewrite,
|
features.SupportHTTPRoutePathRewrite,
|
||||||
features.SupportHTTPRoutePathRedirect,
|
features.SupportHTTPRoutePathRedirect,
|
||||||
features.SupportHTTPRouteResponseHeaderModification,
|
features.SupportHTTPRouteResponseHeaderModification,
|
||||||
|
features.SupportTLSRoute,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
@ -224,6 +230,11 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
||||||
// TODO: to publish this report automatically, we have to figure out how to handle the date diff.
|
// TODO: to publish this report automatically, we have to figure out how to handle the date diff.
|
||||||
report.Date = "-"
|
report.Date = "-"
|
||||||
|
|
||||||
|
// Ordering profile reports for the serialized report to be comparable.
|
||||||
|
slices.SortFunc(report.ProfileReports, func(a, b v1.ProfileReport) int {
|
||||||
|
return strings.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
|
|
||||||
rawReport, err := yaml.Marshal(report)
|
rawReport, err := yaml.Marshal(report)
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
s.T().Logf("Conformance report:\n%s", string(rawReport))
|
s.T().Logf("Conformance report:\n%s", string(rawReport))
|
||||||
|
|
|
@ -94,3 +94,21 @@ services:
|
||||||
traefik.http.routers.rt-preflightCORS.middlewares: preflightCORS
|
traefik.http.routers.rt-preflightCORS.middlewares: preflightCORS
|
||||||
traefik.http.middlewares.preflightCORS.headers.accessControlAllowMethods: OPTIONS, GET
|
traefik.http.middlewares.preflightCORS.headers.accessControlAllowMethods: OPTIONS, GET
|
||||||
traefik.http.services.preflightCORS.loadbalancer.server.port: 80
|
traefik.http.services.preflightCORS.loadbalancer.server.port: 80
|
||||||
|
|
||||||
|
ping:
|
||||||
|
image: traefik/whoami
|
||||||
|
labels:
|
||||||
|
traefik.enable: true
|
||||||
|
traefik.http.routers.ping.entryPoints: ping
|
||||||
|
traefik.http.routers.ping.rule: PathPrefix(`/ping`)
|
||||||
|
traefik.http.routers.ping.service: ping@internal
|
||||||
|
|
||||||
|
traefik.http.routers.ping-error.entryPoints: ping
|
||||||
|
traefik.http.routers.ping-error.rule: PathPrefix(`/ping-error`)
|
||||||
|
traefik.http.routers.ping-error.middlewares: errors, basicauth
|
||||||
|
traefik.http.routers.ping-error.service: ping@internal
|
||||||
|
traefik.http.middlewares.basicauth.basicauth.users: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
|
||||||
|
traefik.http.middlewares.errors.errors.status: 401
|
||||||
|
traefik.http.middlewares.errors.errors.service: service3
|
||||||
|
traefik.http.middlewares.errors.errors.query: /
|
||||||
|
traefik.http.services.service3.loadbalancer.server.port: 80
|
||||||
|
|
|
@ -1004,8 +1004,13 @@ func (s *SimpleSuite) TestMirrorWithBody() {
|
||||||
_, err = rand.Read(body5)
|
_, err = rand.Read(body5)
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
verifyBody := func(req *http.Request) {
|
// forceOkResponse is used to avoid errors when Content-Length is set but no body is received
|
||||||
|
verifyBody := func(req *http.Request, canBodyBeEmpty bool) (forceOkResponse bool) {
|
||||||
b, _ := io.ReadAll(req.Body)
|
b, _ := io.ReadAll(req.Body)
|
||||||
|
if canBodyBeEmpty && req.Header.Get("NoBody") == "true" {
|
||||||
|
require.Empty(s.T(), b)
|
||||||
|
return true
|
||||||
|
}
|
||||||
switch req.Header.Get("Size") {
|
switch req.Header.Get("Size") {
|
||||||
case "20":
|
case "20":
|
||||||
require.Equal(s.T(), body20, b)
|
require.Equal(s.T(), body20, b)
|
||||||
|
@ -1014,20 +1019,25 @@ func (s *SimpleSuite) TestMirrorWithBody() {
|
||||||
default:
|
default:
|
||||||
s.T().Fatal("Size header not present")
|
s.T().Fatal("Size header not present")
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
main := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
main := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
verifyBody(req)
|
verifyBody(req, false)
|
||||||
atomic.AddInt32(&count, 1)
|
atomic.AddInt32(&count, 1)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mirror1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
mirror1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
verifyBody(req)
|
if verifyBody(req, true) {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
atomic.AddInt32(&countMirror1, 1)
|
atomic.AddInt32(&countMirror1, 1)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mirror2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
mirror2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
verifyBody(req)
|
if verifyBody(req, true) {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
atomic.AddInt32(&countMirror2, 1)
|
atomic.AddInt32(&countMirror2, 1)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -1104,6 +1114,28 @@ func (s *SimpleSuite) TestMirrorWithBody() {
|
||||||
assert.Equal(s.T(), int32(10), countTotal)
|
assert.Equal(s.T(), int32(10), countTotal)
|
||||||
assert.Equal(s.T(), int32(0), val1)
|
assert.Equal(s.T(), int32(0), val1)
|
||||||
assert.Equal(s.T(), int32(0), val2)
|
assert.Equal(s.T(), int32(0), val2)
|
||||||
|
|
||||||
|
atomic.StoreInt32(&count, 0)
|
||||||
|
atomic.StoreInt32(&countMirror1, 0)
|
||||||
|
atomic.StoreInt32(&countMirror2, 0)
|
||||||
|
|
||||||
|
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoamiWithoutBody", bytes.NewBuffer(body20))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
req.Header.Set("Size", "20")
|
||||||
|
req.Header.Set("NoBody", "true")
|
||||||
|
for range 10 {
|
||||||
|
response, err := http.DefaultClient.Do(req)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
assert.Equal(s.T(), http.StatusOK, response.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
countTotal = atomic.LoadInt32(&count)
|
||||||
|
val1 = atomic.LoadInt32(&countMirror1)
|
||||||
|
val2 = atomic.LoadInt32(&countMirror2)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), int32(10), countTotal)
|
||||||
|
assert.Equal(s.T(), int32(1), val1)
|
||||||
|
assert.Equal(s.T(), int32(5), val2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SimpleSuite) TestMirrorCanceled() {
|
func (s *SimpleSuite) TestMirrorCanceled() {
|
||||||
|
|
32
integration/testdata/rawdata-gateway.json
vendored
32
integration/testdata/rawdata-gateway.json
vendored
|
@ -34,7 +34,7 @@
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
"web"
|
"web"
|
||||||
],
|
],
|
||||||
"service": "default-http-app-1-my-gateway-web-0-wrr",
|
"service": "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||||
"ruleSyntax": "v3",
|
"ruleSyntax": "v3",
|
||||||
"priority": 100008,
|
"priority": 100008,
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
"websecure"
|
"websecure"
|
||||||
],
|
],
|
||||||
"service": "default-http-app-1-my-https-gateway-websecure-0-wrr",
|
"service": "default-http-app-1-my-https-gateway-websecure-0-1c0cf64bde37d9d0df06-wrr",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||||
"ruleSyntax": "v3",
|
"ruleSyntax": "v3",
|
||||||
"priority": 100008,
|
"priority": 100008,
|
||||||
|
@ -96,7 +96,7 @@
|
||||||
"dashboard@internal"
|
"dashboard@internal"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-http-app-1-my-gateway-web-0-wrr@kubernetesgateway": {
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr@kubernetesgateway": {
|
||||||
"weighted": {
|
"weighted": {
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06@kubernetesgateway"
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06@kubernetesgateway"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-http-app-1-my-https-gateway-websecure-0-wrr@kubernetesgateway": {
|
"default-http-app-1-my-https-gateway-websecure-0-1c0cf64bde37d9d0df06-wrr@kubernetesgateway": {
|
||||||
"weighted": {
|
"weighted": {
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -150,11 +150,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tcpRouters": {
|
"tcpRouters": {
|
||||||
"default-tcp-app-1-my-tcp-gateway-footcp@kubernetesgateway": {
|
"default-tcp-app-1-my-tcp-gateway-footcp-0-e3b0c44298fc1c149afb@kubernetesgateway": {
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
"footcp"
|
"footcp"
|
||||||
],
|
],
|
||||||
"service": "default-tcp-app-1-my-tcp-gateway-footcp-wrr-0",
|
"service": "default-tcp-app-1-my-tcp-gateway-footcp-0-e3b0c44298fc1c149afb-wrr",
|
||||||
"rule": "HostSNI(`*`)",
|
"rule": "HostSNI(`*`)",
|
||||||
"ruleSyntax": "v3",
|
"ruleSyntax": "v3",
|
||||||
"priority": -1,
|
"priority": -1,
|
||||||
|
@ -163,11 +163,11 @@
|
||||||
"footcp"
|
"footcp"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-tcp-app-1-my-tls-gateway-footlsterminate@kubernetesgateway": {
|
"default-tcp-app-1-my-tls-gateway-footlsterminate-0-e3b0c44298fc1c149afb@kubernetesgateway": {
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
"footlsterminate"
|
"footlsterminate"
|
||||||
],
|
],
|
||||||
"service": "default-tcp-app-1-my-tls-gateway-footlsterminate-wrr-0",
|
"service": "default-tcp-app-1-my-tls-gateway-footlsterminate-0-e3b0c44298fc1c149afb-wrr",
|
||||||
"rule": "HostSNI(`*`)",
|
"rule": "HostSNI(`*`)",
|
||||||
"ruleSyntax": "v3",
|
"ruleSyntax": "v3",
|
||||||
"priority": -1,
|
"priority": -1,
|
||||||
|
@ -179,11 +179,11 @@
|
||||||
"footlsterminate"
|
"footlsterminate"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26@kubernetesgateway": {
|
"default-tls-app-1-my-tls-gateway-footlspassthrough-0-e3b0c44298fc1c149afb@kubernetesgateway": {
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
"footlspassthrough"
|
"footlspassthrough"
|
||||||
],
|
],
|
||||||
"service": "default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr-0",
|
"service": "default-tls-app-1-my-tls-gateway-footlspassthrough-0-e3b0c44298fc1c149afb-wrr",
|
||||||
"rule": "HostSNI(`foo.bar`)",
|
"rule": "HostSNI(`foo.bar`)",
|
||||||
"ruleSyntax": "v3",
|
"ruleSyntax": "v3",
|
||||||
"priority": 18,
|
"priority": 18,
|
||||||
|
@ -197,7 +197,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tcpServices": {
|
"tcpServices": {
|
||||||
"default-tcp-app-1-my-tcp-gateway-footcp-wrr-0@kubernetesgateway": {
|
"default-tcp-app-1-my-tcp-gateway-footcp-0-e3b0c44298fc1c149afb-wrr@kubernetesgateway": {
|
||||||
"weighted": {
|
"weighted": {
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -208,10 +208,10 @@
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
"default-tcp-app-1-my-tcp-gateway-footcp@kubernetesgateway"
|
"default-tcp-app-1-my-tcp-gateway-footcp-0-e3b0c44298fc1c149afb@kubernetesgateway"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-tcp-app-1-my-tls-gateway-footlsterminate-wrr-0@kubernetesgateway": {
|
"default-tcp-app-1-my-tls-gateway-footlsterminate-0-e3b0c44298fc1c149afb-wrr@kubernetesgateway": {
|
||||||
"weighted": {
|
"weighted": {
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -222,10 +222,10 @@
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
"default-tcp-app-1-my-tls-gateway-footlsterminate@kubernetesgateway"
|
"default-tcp-app-1-my-tls-gateway-footlsterminate-0-e3b0c44298fc1c149afb@kubernetesgateway"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr-0@kubernetesgateway": {
|
"default-tls-app-1-my-tls-gateway-footlspassthrough-0-e3b0c44298fc1c149afb-wrr@kubernetesgateway": {
|
||||||
"weighted": {
|
"weighted": {
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -236,7 +236,7 @@
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
"default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26@kubernetesgateway"
|
"default-tls-app-1-my-tls-gateway-footlspassthrough-0-e3b0c44298fc1c149afb@kubernetesgateway"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-whoamitcp-8080@kubernetesgateway": {
|
"default-whoamitcp-8080@kubernetesgateway": {
|
||||||
|
|
|
@ -81,6 +81,7 @@ type RouterTLSConfig struct {
|
||||||
// Mirroring holds the Mirroring configuration.
|
// Mirroring holds the Mirroring configuration.
|
||||||
type Mirroring struct {
|
type Mirroring struct {
|
||||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||||
|
MirrorBody *bool `json:"mirrorBody,omitempty" toml:"mirrorBody,omitempty" yaml:"mirrorBody,omitempty" export:"true"`
|
||||||
MaxBodySize *int64 `json:"maxBodySize,omitempty" toml:"maxBodySize,omitempty" yaml:"maxBodySize,omitempty" export:"true"`
|
MaxBodySize *int64 `json:"maxBodySize,omitempty" toml:"maxBodySize,omitempty" yaml:"maxBodySize,omitempty" export:"true"`
|
||||||
Mirrors []MirrorService `json:"mirrors,omitempty" toml:"mirrors,omitempty" yaml:"mirrors,omitempty" export:"true"`
|
Mirrors []MirrorService `json:"mirrors,omitempty" toml:"mirrors,omitempty" yaml:"mirrors,omitempty" export:"true"`
|
||||||
HealthCheck *HealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
HealthCheck *HealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||||
|
@ -88,6 +89,8 @@ type Mirroring struct {
|
||||||
|
|
||||||
// SetDefaults Default values for a WRRService.
|
// SetDefaults Default values for a WRRService.
|
||||||
func (m *Mirroring) SetDefaults() {
|
func (m *Mirroring) SetDefaults() {
|
||||||
|
defaultMirrorBody := true
|
||||||
|
m.MirrorBody = &defaultMirrorBody
|
||||||
var defaultMaxBodySize int64 = -1
|
var defaultMaxBodySize int64 = -1
|
||||||
m.MaxBodySize = &defaultMaxBodySize
|
m.MaxBodySize = &defaultMaxBodySize
|
||||||
}
|
}
|
||||||
|
|
|
@ -967,6 +967,11 @@ func (in *MirrorService) DeepCopy() *MirrorService {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Mirroring) DeepCopyInto(out *Mirroring) {
|
func (in *Mirroring) DeepCopyInto(out *Mirroring) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.MirrorBody != nil {
|
||||||
|
in, out := &in.MirrorBody, &out.MirrorBody
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.MaxBodySize != nil {
|
if in.MaxBodySize != nil {
|
||||||
in, out := &in.MaxBodySize, &out.MaxBodySize
|
in, out := &in.MaxBodySize, &out.MaxBodySize
|
||||||
*out = new(int64)
|
*out = new(int64)
|
||||||
|
|
|
@ -212,6 +212,9 @@ type Tracing struct {
|
||||||
func (t *Tracing) SetDefaults() {
|
func (t *Tracing) SetDefaults() {
|
||||||
t.ServiceName = "traefik"
|
t.ServiceName = "traefik"
|
||||||
t.SampleRate = 1.0
|
t.SampleRate = 1.0
|
||||||
|
|
||||||
|
t.OTLP = &opentelemetry.Config{}
|
||||||
|
t.OTLP.SetDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Providers contains providers configuration.
|
// Providers contains providers configuration.
|
||||||
|
|
|
@ -77,6 +77,11 @@ const (
|
||||||
TLSCipher = "TLSCipher"
|
TLSCipher = "TLSCipher"
|
||||||
// TLSClientSubject is the string representation of the TLS client certificate's Subject.
|
// TLSClientSubject is the string representation of the TLS client certificate's Subject.
|
||||||
TLSClientSubject = "TLSClientSubject"
|
TLSClientSubject = "TLSClientSubject"
|
||||||
|
|
||||||
|
// TraceID is the consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string.
|
||||||
|
TraceID = "TraceId"
|
||||||
|
// SpanID is the unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string.
|
||||||
|
SpanID = "SpanId"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These are written out in the default case when no config is provided to specify keys of interest.
|
// These are written out in the default case when no config is provided to specify keys of interest.
|
||||||
|
|
|
@ -43,9 +43,21 @@ const capturedData key = "capturedData"
|
||||||
// It satisfies the alice.Constructor type.
|
// It satisfies the alice.Constructor type.
|
||||||
func Wrap(next http.Handler) (http.Handler, error) {
|
func Wrap(next http.Handler) (http.Handler, error) {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
capt, err := FromContext(req.Context())
|
||||||
|
if err != nil {
|
||||||
c := &Capture{}
|
c := &Capture{}
|
||||||
newRW, newReq := c.renew(rw, req)
|
newRW, newReq := c.renew(rw, req)
|
||||||
next.ServeHTTP(newRW, newReq)
|
next.ServeHTTP(newRW, newReq)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if capt.NeedsReset(rw) {
|
||||||
|
newRW, newReq := capt.renew(rw, req)
|
||||||
|
next.ServeHTTP(newRW, newReq)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/containous/alice"
|
"github.com/containous/alice"
|
||||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
@ -69,6 +70,12 @@ func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||||
|
|
||||||
e.tracer.CaptureServerRequest(span, req)
|
e.tracer.CaptureServerRequest(span, req)
|
||||||
|
|
||||||
|
if logData := accesslog.GetLogData(req); logData != nil {
|
||||||
|
spanContext := span.SpanContext()
|
||||||
|
logData.Core[accesslog.TraceID] = spanContext.TraceID().String()
|
||||||
|
logData.Core[accesslog.SpanID] = spanContext.SpanID().String()
|
||||||
|
}
|
||||||
|
|
||||||
recorder := newStatusCodeRecorder(rw, http.StatusOK)
|
recorder := newStatusCodeRecorder(rw, http.StatusOK)
|
||||||
e.next.ServeHTTP(recorder, req)
|
e.next.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
ptypes "github.com/traefik/paerser/types"
|
ptypes "github.com/traefik/paerser/types"
|
||||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||||
"github.com/traefik/traefik/v3/pkg/types"
|
"github.com/traefik/traefik/v3/pkg/types"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
@ -181,3 +182,27 @@ func TestEntryPointMiddleware_metrics(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEntryPointMiddleware_tracingInfoIntoLog(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/", http.NoBody)
|
||||||
|
req = req.WithContext(
|
||||||
|
context.WithValue(
|
||||||
|
req.Context(),
|
||||||
|
accesslog.DataTableKey,
|
||||||
|
&accesslog.LogData{Core: accesslog.CoreLogData{}},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {})
|
||||||
|
|
||||||
|
tracer := &mockTracer{}
|
||||||
|
|
||||||
|
handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{}, []string{}, []string{}), nil, "test", next)
|
||||||
|
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||||
|
|
||||||
|
expectedSpanCtx := tracer.spans[0].SpanContext()
|
||||||
|
|
||||||
|
logData := accesslog.GetLogData(req)
|
||||||
|
assert.Equal(t, expectedSpanCtx.TraceID().String(), logData.Core[accesslog.TraceID])
|
||||||
|
assert.Equal(t, expectedSpanCtx.SpanID().String(), logData.Core[accesslog.SpanID])
|
||||||
|
}
|
||||||
|
|
|
@ -290,6 +290,7 @@ func (c configBuilder) buildMirroring(ctx context.Context, tService *traefikv1al
|
||||||
Mirroring: &dynamic.Mirroring{
|
Mirroring: &dynamic.Mirroring{
|
||||||
Service: fullNameMain,
|
Service: fullNameMain,
|
||||||
Mirrors: mirrorServices,
|
Mirrors: mirrorServices,
|
||||||
|
MirrorBody: tService.Spec.Mirroring.MirrorBody,
|
||||||
MaxBodySize: tService.Spec.Mirroring.MaxBodySize,
|
MaxBodySize: tService.Spec.Mirroring.MaxBodySize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,9 @@ type TraefikServiceSpec struct {
|
||||||
type Mirroring struct {
|
type Mirroring struct {
|
||||||
LoadBalancerSpec `json:",inline"`
|
LoadBalancerSpec `json:",inline"`
|
||||||
|
|
||||||
|
// MirrorBody defines whether the body of the request should be mirrored.
|
||||||
|
// Default value is true.
|
||||||
|
MirrorBody *bool `json:"mirrorBody,omitempty"`
|
||||||
// MaxBodySize defines the maximum size allowed for the body of the request.
|
// MaxBodySize defines the maximum size allowed for the body of the request.
|
||||||
// If the body is larger, the request is not mirrored.
|
// If the body is larger, the request is not mirrored.
|
||||||
// Default value is -1, which means unlimited size.
|
// Default value is -1, which means unlimited size.
|
||||||
|
|
|
@ -972,6 +972,11 @@ func (in *MirrorService) DeepCopy() *MirrorService {
|
||||||
func (in *Mirroring) DeepCopyInto(out *Mirroring) {
|
func (in *Mirroring) DeepCopyInto(out *Mirroring) {
|
||||||
*out = *in
|
*out = *in
|
||||||
in.LoadBalancerSpec.DeepCopyInto(&out.LoadBalancerSpec)
|
in.LoadBalancerSpec.DeepCopyInto(&out.LoadBalancerSpec)
|
||||||
|
if in.MirrorBody != nil {
|
||||||
|
in, out := &in.MirrorBody, &out.MirrorBody
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.MaxBodySize != nil {
|
if in.MaxBodySize != nil {
|
||||||
in, out := &in.MaxBodySize, &out.MaxBodySize
|
in, out := &in.MaxBodySize, &out.MaxBodySize
|
||||||
*out = new(int64)
|
*out = new(int64)
|
||||||
|
|
|
@ -32,6 +32,10 @@ metadata:
|
||||||
name: TCP-app-1
|
name: TCP-app-1
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
kind: Gateway
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
|
|
|
@ -36,16 +36,16 @@ spec:
|
||||||
group: ""
|
group: ""
|
||||||
allowedRoutes:
|
allowedRoutes:
|
||||||
kinds:
|
kinds:
|
||||||
- kind: TCPRoute
|
- kind: TLSRoute
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
namespaces:
|
namespaces:
|
||||||
from: Same
|
from: Same
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: TCPRoute
|
kind: TLSRoute
|
||||||
apiVersion: gateway.networking.k8s.io/v1alpha2
|
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||||
metadata:
|
metadata:
|
||||||
name: tcp-app-1
|
name: tls-app-1
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
parentRefs:
|
parentRefs:
|
||||||
|
|
|
@ -216,7 +216,7 @@ func (p *Provider) loadGRPCService(conf *dynamic.Configuration, routeKey string,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadGRPCBackendRef(route *gatev1.GRPCRoute, backendRef gatev1.GRPCBackendRef) (string, *dynamic.Service, *metav1.Condition) {
|
func (p *Provider) loadGRPCBackendRef(route *gatev1.GRPCRoute, backendRef gatev1.GRPCBackendRef) (string, *dynamic.Service, *metav1.Condition) {
|
||||||
kind := ptr.Deref(backendRef.Kind, "Service")
|
kind := ptr.Deref(backendRef.Kind, kindService)
|
||||||
|
|
||||||
group := groupCore
|
group := groupCore
|
||||||
if backendRef.Group != nil && *backendRef.Group != "" {
|
if backendRef.Group != nil && *backendRef.Group != "" {
|
||||||
|
@ -230,7 +230,7 @@ func (p *Provider) loadGRPCBackendRef(route *gatev1.GRPCRoute, backendRef gatev1
|
||||||
|
|
||||||
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
||||||
|
|
||||||
if group != groupCore || kind != "Service" {
|
if group != groupCore || kind != kindService {
|
||||||
return serviceName, nil, &metav1.Condition{
|
return serviceName, nil, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
|
@ -241,7 +241,7 @@ func (p *Provider) loadGRPCBackendRef(route *gatev1.GRPCRoute, backendRef gatev1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.isReferenceGranted(groupGateway, kindGRPCRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
if err := p.isReferenceGranted(kindGRPCRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
||||||
return serviceName, nil, &metav1.Condition{
|
return serviceName, nil, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
|
|
|
@ -158,7 +158,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var serviceCondition *metav1.Condition
|
var serviceCondition *metav1.Condition
|
||||||
router.Service, serviceCondition = p.loadService(conf, routeKey, routeRule, route)
|
router.Service, serviceCondition = p.loadWRRService(conf, routerName, routeRule, route)
|
||||||
if serviceCondition != nil {
|
if serviceCondition != nil {
|
||||||
condition = *serviceCondition
|
condition = *serviceCondition
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener,
|
||||||
return conf, condition
|
return conf, condition
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadService(conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute) (string, *metav1.Condition) {
|
func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute) (string, *metav1.Condition) {
|
||||||
name := routeKey + "-wrr"
|
name := routeKey + "-wrr"
|
||||||
if _, ok := conf.HTTP.Services[name]; ok {
|
if _, ok := conf.HTTP.Services[name]; ok {
|
||||||
return name, nil
|
return name, nil
|
||||||
|
@ -182,7 +182,7 @@ func (p *Provider) loadService(conf *dynamic.Configuration, routeKey string, rou
|
||||||
var wrr dynamic.WeightedRoundRobin
|
var wrr dynamic.WeightedRoundRobin
|
||||||
var condition *metav1.Condition
|
var condition *metav1.Condition
|
||||||
for _, backendRef := range routeRule.BackendRefs {
|
for _, backendRef := range routeRule.BackendRefs {
|
||||||
svcName, svc, errCondition := p.loadHTTPService(route, backendRef)
|
svcName, svc, errCondition := p.loadService(route, backendRef)
|
||||||
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
||||||
if errCondition != nil {
|
if errCondition != nil {
|
||||||
condition = errCondition
|
condition = errCondition
|
||||||
|
@ -208,10 +208,10 @@ func (p *Provider) loadService(conf *dynamic.Configuration, routeKey string, rou
|
||||||
return name, condition
|
return name, condition
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadHTTPService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef.
|
// loadService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef.
|
||||||
// Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc).
|
// Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc).
|
||||||
func (p *Provider) loadHTTPService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, *metav1.Condition) {
|
func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, *metav1.Condition) {
|
||||||
kind := ptr.Deref(backendRef.Kind, "Service")
|
kind := ptr.Deref(backendRef.Kind, kindService)
|
||||||
|
|
||||||
group := groupCore
|
group := groupCore
|
||||||
if backendRef.Group != nil && *backendRef.Group != "" {
|
if backendRef.Group != nil && *backendRef.Group != "" {
|
||||||
|
@ -225,7 +225,7 @@ func (p *Provider) loadHTTPService(route *gatev1.HTTPRoute, backendRef gatev1.HT
|
||||||
|
|
||||||
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
||||||
|
|
||||||
if err := p.isReferenceGranted(groupGateway, kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
if err := p.isReferenceGranted(kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
||||||
return serviceName, nil, &metav1.Condition{
|
return serviceName, nil, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
|
@ -236,7 +236,7 @@ func (p *Provider) loadHTTPService(route *gatev1.HTTPRoute, backendRef gatev1.HT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if group != groupCore || kind != "Service" {
|
if group != groupCore || kind != kindService {
|
||||||
name, service, err := p.loadHTTPBackendRef(namespace, backendRef)
|
name, service, err := p.loadHTTPBackendRef(namespace, backendRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return serviceName, nil, &metav1.Condition{
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
|
|
@ -47,6 +47,7 @@ const (
|
||||||
kindGRPCRoute = "GRPCRoute"
|
kindGRPCRoute = "GRPCRoute"
|
||||||
kindTCPRoute = "TCPRoute"
|
kindTCPRoute = "TCPRoute"
|
||||||
kindTLSRoute = "TLSRoute"
|
kindTLSRoute = "TLSRoute"
|
||||||
|
kindService = "Service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider holds configurations of the provider.
|
// Provider holds configurations of the provider.
|
||||||
|
@ -376,11 +377,19 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gatewayStatus, err := p.makeGatewayStatus(gateway, listeners, addresses)
|
gatewayStatus, errConditions := p.makeGatewayStatus(gateway, listeners, addresses)
|
||||||
if err != nil {
|
if len(errConditions) > 0 {
|
||||||
|
messages := map[string]struct{}{}
|
||||||
|
for _, condition := range errConditions {
|
||||||
|
messages[condition.Message] = struct{}{}
|
||||||
|
}
|
||||||
|
var conditionsErr error
|
||||||
|
for message := range messages {
|
||||||
|
conditionsErr = multierror.Append(conditionsErr, errors.New(message))
|
||||||
|
}
|
||||||
logger.Error().
|
logger.Error().
|
||||||
Err(err).
|
Err(conditionsErr).
|
||||||
Msg("Unable to create Gateway status")
|
Msg("Gateway Not Accepted")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = p.client.UpdateGatewayStatus(ctx, ktypes.NamespacedName{Name: gateway.Name, Namespace: gateway.Namespace}, gatewayStatus); err != nil {
|
if err = p.client.UpdateGatewayStatus(ctx, ktypes.NamespacedName{Name: gateway.Name, Namespace: gateway.Namespace}, gatewayStatus); err != nil {
|
||||||
|
@ -576,7 +585,7 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, gateway *gatev1.Gat
|
||||||
certificateNamespace = string(*certificateRef.Namespace)
|
certificateNamespace = string(*certificateRef.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.isReferenceGranted(groupGateway, kindGateway, gateway.Namespace, groupCore, "Secret", string(certificateRef.Name), certificateNamespace); err != nil {
|
if err := p.isReferenceGranted(kindGateway, gateway.Namespace, groupCore, "Secret", string(certificateRef.Name), certificateNamespace); err != nil {
|
||||||
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
||||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
|
@ -631,10 +640,10 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, gateway *gatev1.Gat
|
||||||
return gatewayListeners
|
return gatewayListeners
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listeners []gatewayListener, addresses []gatev1.GatewayStatusAddress) (gatev1.GatewayStatus, error) {
|
func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listeners []gatewayListener, addresses []gatev1.GatewayStatusAddress) (gatev1.GatewayStatus, []metav1.Condition) {
|
||||||
gatewayStatus := gatev1.GatewayStatus{Addresses: addresses}
|
gatewayStatus := gatev1.GatewayStatus{Addresses: addresses}
|
||||||
|
|
||||||
var result error
|
var errorConditions []metav1.Condition
|
||||||
for _, listener := range listeners {
|
for _, listener := range listeners {
|
||||||
if len(listener.Status.Conditions) == 0 {
|
if len(listener.Status.Conditions) == 0 {
|
||||||
listener.Status.Conditions = append(listener.Status.Conditions,
|
listener.Status.Conditions = append(listener.Status.Conditions,
|
||||||
|
@ -669,14 +678,11 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listeners []gatewa
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, condition := range listener.Status.Conditions {
|
errorConditions = append(errorConditions, listener.Status.Conditions...)
|
||||||
result = multierror.Append(result, errors.New(condition.Message))
|
|
||||||
}
|
|
||||||
|
|
||||||
gatewayStatus.Listeners = append(gatewayStatus.Listeners, *listener.Status)
|
gatewayStatus.Listeners = append(gatewayStatus.Listeners, *listener.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result != nil {
|
if len(errorConditions) > 0 {
|
||||||
// GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid"
|
// GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid"
|
||||||
gatewayStatus.Conditions = append(gatewayStatus.Conditions, metav1.Condition{
|
gatewayStatus.Conditions = append(gatewayStatus.Conditions, metav1.Condition{
|
||||||
Type: string(gatev1.GatewayConditionAccepted),
|
Type: string(gatev1.GatewayConditionAccepted),
|
||||||
|
@ -687,7 +693,7 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listeners []gatewa
|
||||||
Message: "All Listeners must be valid",
|
Message: "All Listeners must be valid",
|
||||||
})
|
})
|
||||||
|
|
||||||
return gatewayStatus, result
|
return gatewayStatus, errorConditions
|
||||||
}
|
}
|
||||||
|
|
||||||
gatewayStatus.Conditions = append(gatewayStatus.Conditions,
|
gatewayStatus.Conditions = append(gatewayStatus.Conditions,
|
||||||
|
@ -783,7 +789,7 @@ func (p *Provider) entryPointName(port gatev1.PortNumber, protocol gatev1.Protoc
|
||||||
return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol)
|
return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) isReferenceGranted(fromGroup, fromKind, fromNamespace, toGroup, toKind, toName, toNamespace string) error {
|
func (p *Provider) isReferenceGranted(fromKind, fromNamespace, toGroup, toKind, toName, toNamespace string) error {
|
||||||
if toNamespace == fromNamespace {
|
if toNamespace == fromNamespace {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -793,7 +799,7 @@ func (p *Provider) isReferenceGranted(fromGroup, fromKind, fromNamespace, toGrou
|
||||||
return fmt.Errorf("listing ReferenceGrant: %w", err)
|
return fmt.Errorf("listing ReferenceGrant: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
refGrants = filterReferenceGrantsFrom(refGrants, fromGroup, fromKind, fromNamespace)
|
refGrants = filterReferenceGrantsFrom(refGrants, groupGateway, fromKind, fromNamespace)
|
||||||
refGrants = filterReferenceGrantsTo(refGrants, toGroup, toKind, toName)
|
refGrants = filterReferenceGrantsTo(refGrants, toGroup, toKind, toName)
|
||||||
if len(refGrants) == 0 {
|
if len(refGrants) == 0 {
|
||||||
return errors.New("missing ReferenceGrant")
|
return errors.New("missing ReferenceGrant")
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
@ -108,6 +109,13 @@ func (p *Provider) loadTCPRoute(listener gatewayListener, route *gatev1alpha2.TC
|
||||||
Reason: string(gatev1.RouteConditionResolvedRefs),
|
Reason: string(gatev1.RouteConditionResolvedRefs),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for ri, rule := range route.Spec.Rules {
|
||||||
|
if rule.BackendRefs == nil {
|
||||||
|
// Should not happen due to validation.
|
||||||
|
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tcproute_types.go#L76
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
router := dynamic.TCPRouter{
|
router := dynamic.TCPRouter{
|
||||||
Rule: "HostSNI(`*`)",
|
Rule: "HostSNI(`*`)",
|
||||||
EntryPoints: []string{listener.EPName},
|
EntryPoints: []string{listener.EPName},
|
||||||
|
@ -115,110 +123,161 @@ func (p *Provider) loadTCPRoute(listener gatewayListener, route *gatev1alpha2.TC
|
||||||
}
|
}
|
||||||
|
|
||||||
if listener.Protocol == gatev1.TLSProtocolType && listener.TLS != nil {
|
if listener.Protocol == gatev1.TLSProtocolType && listener.TLS != nil {
|
||||||
// TODO support let's encrypt
|
|
||||||
router.TLS = &dynamic.RouterTCPTLSConfig{
|
router.TLS = &dynamic.RouterTCPTLSConfig{
|
||||||
Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1.TLSModePassthrough,
|
Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1.TLSModePassthrough,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||||
routerName := provider.Normalize(route.Namespace + "-" + route.Name + "-" + listener.GWName + "-" + listener.EPName)
|
routeKey := provider.Normalize(fmt.Sprintf("%s-%s-%s-%s-%d", route.Namespace, route.Name, listener.GWName, listener.EPName, ri))
|
||||||
|
// Routing criteria should be introduced at some point.
|
||||||
|
routerName := makeRouterName("", routeKey)
|
||||||
|
|
||||||
var ruleServiceNames []string
|
if len(rule.BackendRefs) == 1 && isInternalService(rule.BackendRefs[0]) {
|
||||||
for i, rule := range route.Spec.Rules {
|
router.Service = string(rule.BackendRefs[0].Name)
|
||||||
if rule.BackendRefs == nil {
|
conf.TCP.Routers[routerName] = &router
|
||||||
// Should not happen due to validation.
|
|
||||||
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tcproute_types.go#L76
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
wrrService, subServices, err := p.loadTCPServices(route.Namespace, rule.BackendRefs)
|
var serviceCondition *metav1.Condition
|
||||||
|
router.Service, serviceCondition = p.loadTCPWRRService(conf, routerName, rule.BackendRefs, route)
|
||||||
|
if serviceCondition != nil {
|
||||||
|
condition = *serviceCondition
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.TCP.Routers[routerName] = &router
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, condition
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTCPWRRService is generating a WRR service, even when there is only one target.
|
||||||
|
func (p *Provider) loadTCPWRRService(conf *dynamic.Configuration, routeKey string, backendRefs []gatev1.BackendRef, route *gatev1alpha2.TCPRoute) (string, *metav1.Condition) {
|
||||||
|
name := routeKey + "-wrr"
|
||||||
|
if _, ok := conf.TCP.Services[name]; ok {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrr dynamic.TCPWeightedRoundRobin
|
||||||
|
var condition *metav1.Condition
|
||||||
|
for _, backendRef := range backendRefs {
|
||||||
|
svcName, svc, errCondition := p.loadTCPService(route, backendRef)
|
||||||
|
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
||||||
|
|
||||||
|
if errCondition != nil {
|
||||||
|
condition = errCondition
|
||||||
|
|
||||||
|
errName := routeKey + "-err-lb"
|
||||||
|
conf.TCP.Services[errName] = &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
Servers: []dynamic.TCPServer{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
wrr.Services = append(wrr.Services, dynamic.TCPWRRService{
|
||||||
|
Name: errName,
|
||||||
|
Weight: weight,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc != nil {
|
||||||
|
conf.TCP.Services[svcName] = svc
|
||||||
|
}
|
||||||
|
|
||||||
|
wrr.Services = append(wrr.Services, dynamic.TCPWRRService{
|
||||||
|
Name: svcName,
|
||||||
|
Weight: weight,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.TCP.Services[name] = &dynamic.TCPService{Weighted: &wrr}
|
||||||
|
return name, condition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadTCPService(route *gatev1alpha2.TCPRoute, backendRef gatev1.BackendRef) (string, *dynamic.TCPService, *metav1.Condition) {
|
||||||
|
kind := ptr.Deref(backendRef.Kind, kindService)
|
||||||
|
|
||||||
|
group := groupCore
|
||||||
|
if backendRef.Group != nil && *backendRef.Group != "" {
|
||||||
|
group = string(*backendRef.Group)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := route.Namespace
|
||||||
|
if backendRef.Namespace != nil && *backendRef.Namespace != "" {
|
||||||
|
namespace = string(*backendRef.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
||||||
|
|
||||||
|
if err := p.isReferenceGranted(kindTCPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonRefNotPermitted),
|
||||||
|
Message: fmt.Sprintf("Cannot load TCPRoute BackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if group != groupCore || kind != kindService {
|
||||||
|
name, err := p.loadTCPBackendRef(backendRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return conf, metav1.Condition{
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonInvalidKind),
|
||||||
|
Message: fmt.Sprintf("Cannot load TCPRoute BackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
port := ptr.Deref(backendRef.Port, gatev1.PortNumber(0))
|
||||||
|
if port == 0 {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonUnsupportedProtocol),
|
||||||
|
Message: fmt.Sprintf("Cannot load TCPRoute BackendRef %s/%s/%s/%s port is required", group, kind, namespace, backendRef.Name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
portStr := strconv.FormatInt(int64(port), 10)
|
||||||
|
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
||||||
|
|
||||||
|
lb, err := p.loadTCPServers(namespace, backendRef)
|
||||||
|
if err != nil {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
LastTransitionTime: metav1.Now(),
|
LastTransitionTime: metav1.Now(),
|
||||||
Reason: string(gatev1.RouteReasonBackendNotFound),
|
Reason: string(gatev1.RouteReasonBackendNotFound),
|
||||||
Message: fmt.Sprintf("Cannot load TCPRoute service %s/%s: %v", route.Namespace, route.Name, err),
|
Message: fmt.Sprintf("Cannot load TCPRoute BackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for svcName, svc := range subServices {
|
return serviceName, &dynamic.TCPService{LoadBalancer: lb}, nil
|
||||||
conf.TCP.Services[svcName] = svc
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceName := fmt.Sprintf("%s-wrr-%d", routerName, i)
|
|
||||||
conf.TCP.Services[serviceName] = wrrService
|
|
||||||
|
|
||||||
ruleServiceNames = append(ruleServiceNames, serviceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ruleServiceNames) == 1 {
|
|
||||||
router.Service = ruleServiceNames[0]
|
|
||||||
conf.TCP.Routers[routerName] = &router
|
|
||||||
return conf, condition
|
|
||||||
}
|
|
||||||
|
|
||||||
routeServiceKey := routerName + "-wrr"
|
|
||||||
routeService := &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
|
|
||||||
|
|
||||||
for _, name := range ruleServiceNames {
|
|
||||||
service := dynamic.TCPWRRService{Name: name}
|
|
||||||
service.SetDefaults()
|
|
||||||
|
|
||||||
routeService.Weighted.Services = append(routeService.Weighted.Services, service)
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.TCP.Services[routeServiceKey] = routeService
|
|
||||||
|
|
||||||
router.Service = routeServiceKey
|
|
||||||
conf.TCP.Routers[routerName] = &router
|
|
||||||
|
|
||||||
return conf, condition
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadTCPServices is generating a WRR service, even when there is only one target.
|
func (p *Provider) loadTCPServers(namespace string, backendRef gatev1.BackendRef) (*dynamic.TCPServersLoadBalancer, error) {
|
||||||
func (p *Provider) loadTCPServices(namespace string, backendRefs []gatev1.BackendRef) (*dynamic.TCPService, map[string]*dynamic.TCPService, error) {
|
|
||||||
services := map[string]*dynamic.TCPService{}
|
|
||||||
|
|
||||||
wrrSvc := &dynamic.TCPService{
|
|
||||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
|
||||||
Services: []dynamic.TCPWRRService{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, backendRef := range backendRefs {
|
|
||||||
if backendRef.Group == nil || backendRef.Kind == nil {
|
|
||||||
// Should not happen as this is validated by kubernetes
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if isInternalService(backendRef) {
|
|
||||||
return nil, nil, fmt.Errorf("traefik internal service %s is not allowed in a WRR loadbalancer", backendRef.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
weight := int(ptr.Deref(backendRef.Weight, 1))
|
|
||||||
|
|
||||||
if isTraefikService(backendRef) {
|
|
||||||
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: string(backendRef.Name), Weight: &weight})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" {
|
|
||||||
return nil, nil, fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if backendRef.Port == nil {
|
if backendRef.Port == nil {
|
||||||
return nil, nil, errors.New("port is required for Kubernetes Service reference")
|
return nil, errors.New("port is required for Kubernetes Service reference")
|
||||||
}
|
}
|
||||||
|
|
||||||
service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
|
service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("getting service: %w", err)
|
return nil, fmt.Errorf("getting service: %w", err)
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, nil, errors.New("service not found")
|
return nil, errors.New("service not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
var svcPort *corev1.ServicePort
|
var svcPort *corev1.ServicePort
|
||||||
|
@ -229,18 +288,18 @@ func (p *Provider) loadTCPServices(namespace string, backendRefs []gatev1.Backen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if svcPort == nil {
|
if svcPort == nil {
|
||||||
return nil, nil, fmt.Errorf("service port %d not found", *backendRef.Port)
|
return nil, fmt.Errorf("service port %d not found", *backendRef.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointSlices, err := p.client.ListEndpointSlicesForService(namespace, string(backendRef.Name))
|
endpointSlices, err := p.client.ListEndpointSlicesForService(namespace, string(backendRef.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("getting endpointslices: %w", err)
|
return nil, fmt.Errorf("getting endpointslices: %w", err)
|
||||||
}
|
}
|
||||||
if len(endpointSlices) == 0 {
|
if len(endpointSlices) == 0 {
|
||||||
return nil, nil, errors.New("endpointslices not found")
|
return nil, errors.New("endpointslices not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
svc := dynamic.TCPService{LoadBalancer: &dynamic.TCPServersLoadBalancer{}}
|
lb := &dynamic.TCPServersLoadBalancer{}
|
||||||
|
|
||||||
addresses := map[string]struct{}{}
|
addresses := map[string]struct{}{}
|
||||||
for _, endpointSlice := range endpointSlices {
|
for _, endpointSlice := range endpointSlices {
|
||||||
|
@ -266,24 +325,25 @@ func (p *Provider) loadTCPServices(namespace string, backendRefs []gatev1.Backen
|
||||||
}
|
}
|
||||||
|
|
||||||
addresses[address] = struct{}{}
|
addresses[address] = struct{}{}
|
||||||
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.TCPServer{
|
lb.Servers = append(lb.Servers, dynamic.TCPServer{
|
||||||
|
// TODO determine whether the servers needs TLS, from the port?
|
||||||
Address: net.JoinHostPort(address, strconv.Itoa(int(port))),
|
Address: net.JoinHostPort(address, strconv.Itoa(int(port))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := provider.Normalize(service.Namespace + "-" + service.Name + "-" + strconv.Itoa(int(svcPort.Port)))
|
return lb, nil
|
||||||
services[serviceName] = &svc
|
}
|
||||||
|
|
||||||
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: serviceName, Weight: &weight})
|
func (p *Provider) loadTCPBackendRef(backendRef gatev1.BackendRef) (string, error) {
|
||||||
|
// Support for cross-provider references (e.g: api@internal).
|
||||||
|
// This provides the same behavior as for IngressRoutes.
|
||||||
|
if *backendRef.Kind == "TraefikService" && strings.Contains(string(backendRef.Name), "@") {
|
||||||
|
return string(backendRef.Name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(wrrSvc.Weighted.Services) == 0 {
|
return "", fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||||
return nil, nil, errors.New("no service has been created")
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrrSvc, services, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeTCPConfiguration(from, to *dynamic.Configuration) {
|
func mergeTCPConfiguration(from, to *dynamic.Configuration) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
"github.com/traefik/traefik/v3/pkg/provider"
|
"github.com/traefik/traefik/v3/pkg/provider"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
ktypes "k8s.io/apimachinery/pkg/types"
|
ktypes "k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
)
|
)
|
||||||
|
@ -109,6 +111,13 @@ func (p *Provider) loadTLSRoute(listener gatewayListener, route *gatev1alpha2.TL
|
||||||
Reason: string(gatev1.RouteConditionResolvedRefs),
|
Reason: string(gatev1.RouteConditionResolvedRefs),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for ri, routeRule := range route.Spec.Rules {
|
||||||
|
if len(routeRule.BackendRefs) == 0 {
|
||||||
|
// Should not happen due to validation.
|
||||||
|
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tlsroute_types.go#L120
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
router := dynamic.TCPRouter{
|
router := dynamic.TCPRouter{
|
||||||
RuleSyntax: "v3",
|
RuleSyntax: "v3",
|
||||||
Rule: hostSNIRule(hostnames),
|
Rule: hostSNIRule(hostnames),
|
||||||
|
@ -119,64 +128,142 @@ func (p *Provider) loadTLSRoute(listener gatewayListener, route *gatev1alpha2.TL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||||
routeKey := provider.Normalize(route.Namespace + "-" + route.Name + "-" + listener.GWName + "-" + listener.EPName)
|
routeKey := provider.Normalize(fmt.Sprintf("%s-%s-%s-%s-%d", route.Namespace, route.Name, listener.GWName, listener.EPName, ri))
|
||||||
routerName := makeRouterName(router.Rule, routeKey)
|
// Routing criteria should be introduced at some point.
|
||||||
|
routerName := makeRouterName("", routeKey)
|
||||||
|
|
||||||
var ruleServiceNames []string
|
if len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0]) {
|
||||||
for i, routeRule := range route.Spec.Rules {
|
router.Service = string(routeRule.BackendRefs[0].Name)
|
||||||
if len(routeRule.BackendRefs) == 0 {
|
conf.TCP.Routers[routerName] = &router
|
||||||
// Should not happen due to validation.
|
|
||||||
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tlsroute_types.go#L120
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
wrrService, subServices, err := p.loadTCPServices(route.Namespace, routeRule.BackendRefs)
|
var serviceCondition *metav1.Condition
|
||||||
|
router.Service, serviceCondition = p.loadTLSWRRService(conf, routerName, routeRule.BackendRefs, route)
|
||||||
|
if serviceCondition != nil {
|
||||||
|
condition = *serviceCondition
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.TCP.Routers[routerName] = &router
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, condition
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTLSWRRService is generating a WRR service, even when there is only one target.
|
||||||
|
func (p *Provider) loadTLSWRRService(conf *dynamic.Configuration, routeKey string, backendRefs []gatev1.BackendRef, route *gatev1alpha2.TLSRoute) (string, *metav1.Condition) {
|
||||||
|
name := routeKey + "-wrr"
|
||||||
|
if _, ok := conf.TCP.Services[name]; ok {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrr dynamic.TCPWeightedRoundRobin
|
||||||
|
var condition *metav1.Condition
|
||||||
|
for _, backendRef := range backendRefs {
|
||||||
|
svcName, svc, errCondition := p.loadTLSService(route, backendRef)
|
||||||
|
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
||||||
|
|
||||||
|
if errCondition != nil {
|
||||||
|
condition = errCondition
|
||||||
|
|
||||||
|
errName := routeKey + "-err-lb"
|
||||||
|
conf.TCP.Services[errName] = &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
Servers: []dynamic.TCPServer{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
wrr.Services = append(wrr.Services, dynamic.TCPWRRService{
|
||||||
|
Name: errName,
|
||||||
|
Weight: weight,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc != nil {
|
||||||
|
conf.TCP.Services[svcName] = svc
|
||||||
|
}
|
||||||
|
|
||||||
|
wrr.Services = append(wrr.Services, dynamic.TCPWRRService{
|
||||||
|
Name: svcName,
|
||||||
|
Weight: weight,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.TCP.Services[name] = &dynamic.TCPService{Weighted: &wrr}
|
||||||
|
return name, condition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadTLSService(route *gatev1alpha2.TLSRoute, backendRef gatev1.BackendRef) (string, *dynamic.TCPService, *metav1.Condition) {
|
||||||
|
kind := ptr.Deref(backendRef.Kind, kindService)
|
||||||
|
|
||||||
|
group := groupCore
|
||||||
|
if backendRef.Group != nil && *backendRef.Group != "" {
|
||||||
|
group = string(*backendRef.Group)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := route.Namespace
|
||||||
|
if backendRef.Namespace != nil && *backendRef.Namespace != "" {
|
||||||
|
namespace = string(*backendRef.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
||||||
|
|
||||||
|
if err := p.isReferenceGranted(kindTLSRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonRefNotPermitted),
|
||||||
|
Message: fmt.Sprintf("Cannot load TLSRoute BackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if group != groupCore || kind != kindService {
|
||||||
|
name, err := p.loadTCPBackendRef(backendRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// update "ResolvedRefs" status true with "InvalidBackendRefs" reason
|
return serviceName, nil, &metav1.Condition{
|
||||||
condition = metav1.Condition{
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonInvalidKind),
|
||||||
|
Message: fmt.Sprintf("Cannot load TLSRoute BackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
port := ptr.Deref(backendRef.Port, gatev1.PortNumber(0))
|
||||||
|
if port == 0 {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonUnsupportedProtocol),
|
||||||
|
Message: fmt.Sprintf("Cannot load TLSRoute BackendRef %s/%s/%s/%s port is required", group, kind, namespace, backendRef.Name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
portStr := strconv.FormatInt(int64(port), 10)
|
||||||
|
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
||||||
|
|
||||||
|
lb, err := p.loadTCPServers(namespace, backendRef)
|
||||||
|
if err != nil {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
LastTransitionTime: metav1.Now(),
|
LastTransitionTime: metav1.Now(),
|
||||||
Reason: string(gatev1.RouteReasonBackendNotFound),
|
Reason: string(gatev1.RouteReasonBackendNotFound),
|
||||||
Message: fmt.Sprintf("Cannot load TLSRoute service %s/%s: %v", route.Namespace, route.Name, err),
|
Message: fmt.Sprintf("Cannot load TLSRoute BackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for svcName, svc := range subServices {
|
return serviceName, &dynamic.TCPService{LoadBalancer: lb}, nil
|
||||||
conf.TCP.Services[svcName] = svc
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceName := fmt.Sprintf("%s-wrr-%d", routerName, i)
|
|
||||||
conf.TCP.Services[serviceName] = wrrService
|
|
||||||
|
|
||||||
ruleServiceNames = append(ruleServiceNames, serviceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ruleServiceNames) == 1 {
|
|
||||||
router.Service = ruleServiceNames[0]
|
|
||||||
conf.TCP.Routers[routerName] = &router
|
|
||||||
|
|
||||||
return conf, condition
|
|
||||||
}
|
|
||||||
|
|
||||||
routeServiceKey := routerName + "-wrr"
|
|
||||||
routeService := &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
|
|
||||||
|
|
||||||
for _, name := range ruleServiceNames {
|
|
||||||
service := dynamic.TCPWRRService{Name: name}
|
|
||||||
service.SetDefaults()
|
|
||||||
|
|
||||||
routeService.Weighted.Services = append(routeService.Weighted.Services, service)
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.TCP.Services[routeServiceKey] = routeService
|
|
||||||
|
|
||||||
router.Service = routeServiceKey
|
|
||||||
conf.TCP.Routers[routerName] = &router
|
|
||||||
|
|
||||||
return conf, condition
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostSNIRule(hostnames []gatev1.Hostname) string {
|
func hostSNIRule(hostnames []gatev1.Hostname) string {
|
||||||
|
|
|
@ -61,6 +61,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
"traefik/http/services/Service01/loadBalancer/servers/0/url": "foobar",
|
"traefik/http/services/Service01/loadBalancer/servers/0/url": "foobar",
|
||||||
"traefik/http/services/Service01/loadBalancer/servers/1/url": "foobar",
|
"traefik/http/services/Service01/loadBalancer/servers/1/url": "foobar",
|
||||||
"traefik/http/services/Service02/mirroring/service": "foobar",
|
"traefik/http/services/Service02/mirroring/service": "foobar",
|
||||||
|
"traefik/http/services/Service02/mirroring/mirrorBody": "true",
|
||||||
"traefik/http/services/Service02/mirroring/maxBodySize": "42",
|
"traefik/http/services/Service02/mirroring/maxBodySize": "42",
|
||||||
"traefik/http/services/Service02/mirroring/mirrors/0/name": "foobar",
|
"traefik/http/services/Service02/mirroring/mirrors/0/name": "foobar",
|
||||||
"traefik/http/services/Service02/mirroring/mirrors/0/percent": "42",
|
"traefik/http/services/Service02/mirroring/mirrors/0/percent": "42",
|
||||||
|
@ -676,6 +677,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
"Service02": {
|
"Service02": {
|
||||||
Mirroring: &dynamic.Mirroring{
|
Mirroring: &dynamic.Mirroring{
|
||||||
Service: "foobar",
|
Service: "foobar",
|
||||||
|
MirrorBody: func(v bool) *bool { return &v }(true),
|
||||||
MaxBodySize: func(v int64) *int64 { return &v }(42),
|
MaxBodySize: func(v int64) *int64 { return &v }(42),
|
||||||
Mirrors: []dynamic.MirrorService{
|
Mirrors: []dynamic.MirrorService{
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,6 +25,7 @@ type Mirroring struct {
|
||||||
rw http.ResponseWriter
|
rw http.ResponseWriter
|
||||||
routinePool *safe.Pool
|
routinePool *safe.Pool
|
||||||
|
|
||||||
|
mirrorBody bool
|
||||||
maxBodySize int64
|
maxBodySize int64
|
||||||
wantsHealthCheck bool
|
wantsHealthCheck bool
|
||||||
|
|
||||||
|
@ -33,11 +34,12 @@ type Mirroring struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new instance of *Mirroring.
|
// New returns a new instance of *Mirroring.
|
||||||
func New(handler http.Handler, pool *safe.Pool, maxBodySize int64, hc *dynamic.HealthCheck) *Mirroring {
|
func New(handler http.Handler, pool *safe.Pool, mirrorBody bool, maxBodySize int64, hc *dynamic.HealthCheck) *Mirroring {
|
||||||
return &Mirroring{
|
return &Mirroring{
|
||||||
routinePool: pool,
|
routinePool: pool,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
rw: blackHoleResponseWriter{},
|
rw: blackHoleResponseWriter{},
|
||||||
|
mirrorBody: mirrorBody,
|
||||||
maxBodySize: maxBodySize,
|
maxBodySize: maxBodySize,
|
||||||
wantsHealthCheck: hc != nil,
|
wantsHealthCheck: hc != nil,
|
||||||
}
|
}
|
||||||
|
@ -83,7 +85,7 @@ func (m *Mirroring) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.Ctx(req.Context())
|
logger := log.Ctx(req.Context())
|
||||||
rr, bytesRead, err := newReusableRequest(req, m.maxBodySize)
|
rr, bytesRead, err := newReusableRequest(req, m.mirrorBody, m.maxBodySize)
|
||||||
if err != nil && !errors.Is(err, errBodyTooLarge) {
|
if err != nil && !errors.Is(err, errBodyTooLarge) {
|
||||||
http.Error(rw, fmt.Sprintf("%s: creating reusable request: %v",
|
http.Error(rw, fmt.Sprintf("%s: creating reusable request: %v",
|
||||||
http.StatusText(http.StatusInternalServerError), err), http.StatusInternalServerError)
|
http.StatusText(http.StatusInternalServerError), err), http.StatusInternalServerError)
|
||||||
|
@ -200,11 +202,11 @@ var errBodyTooLarge = errors.New("request body too large")
|
||||||
|
|
||||||
// if the returned error is errBodyTooLarge, newReusableRequest also returns the
|
// if the returned error is errBodyTooLarge, newReusableRequest also returns the
|
||||||
// bytes that were already consumed from the request's body.
|
// bytes that were already consumed from the request's body.
|
||||||
func newReusableRequest(req *http.Request, maxBodySize int64) (*reusableRequest, []byte, error) {
|
func newReusableRequest(req *http.Request, mirrorBody bool, maxBodySize int64) (*reusableRequest, []byte, error) {
|
||||||
if req == nil {
|
if req == nil {
|
||||||
return nil, nil, errors.New("nil input request")
|
return nil, nil, errors.New("nil input request")
|
||||||
}
|
}
|
||||||
if req.Body == nil || req.ContentLength == 0 {
|
if req.Body == nil || req.ContentLength == 0 || !mirrorBody {
|
||||||
return &reusableRequest{req: req}, nil, nil
|
return &reusableRequest{req: req}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ func TestMirroringOn100(t *testing.T) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
pool := safe.NewPool(context.Background())
|
pool := safe.NewPool(context.Background())
|
||||||
mirror := New(handler, pool, defaultMaxBodySize, nil)
|
mirror := New(handler, pool, true, defaultMaxBodySize, nil)
|
||||||
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
atomic.AddInt32(&countMirror1, 1)
|
atomic.AddInt32(&countMirror1, 1)
|
||||||
}), 10)
|
}), 10)
|
||||||
|
@ -50,7 +50,7 @@ func TestMirroringOn10(t *testing.T) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
pool := safe.NewPool(context.Background())
|
pool := safe.NewPool(context.Background())
|
||||||
mirror := New(handler, pool, defaultMaxBodySize, nil)
|
mirror := New(handler, pool, true, defaultMaxBodySize, nil)
|
||||||
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
atomic.AddInt32(&countMirror1, 1)
|
atomic.AddInt32(&countMirror1, 1)
|
||||||
}), 10)
|
}), 10)
|
||||||
|
@ -74,7 +74,7 @@ func TestMirroringOn10(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidPercent(t *testing.T) {
|
func TestInvalidPercent(t *testing.T) {
|
||||||
mirror := New(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), safe.NewPool(context.Background()), defaultMaxBodySize, nil)
|
mirror := New(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), safe.NewPool(context.Background()), true, defaultMaxBodySize, nil)
|
||||||
err := mirror.AddMirror(nil, -1)
|
err := mirror.AddMirror(nil, -1)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ func TestHijack(t *testing.T) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
pool := safe.NewPool(context.Background())
|
pool := safe.NewPool(context.Background())
|
||||||
mirror := New(handler, pool, defaultMaxBodySize, nil)
|
mirror := New(handler, pool, true, defaultMaxBodySize, nil)
|
||||||
|
|
||||||
var mirrorRequest bool
|
var mirrorRequest bool
|
||||||
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -117,7 +117,7 @@ func TestFlush(t *testing.T) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
pool := safe.NewPool(context.Background())
|
pool := safe.NewPool(context.Background())
|
||||||
mirror := New(handler, pool, defaultMaxBodySize, nil)
|
mirror := New(handler, pool, true, defaultMaxBodySize, nil)
|
||||||
|
|
||||||
var mirrorRequest bool
|
var mirrorRequest bool
|
||||||
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -154,7 +154,7 @@ func TestMirroringWithBody(t *testing.T) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
mirror := New(handler, pool, defaultMaxBodySize, nil)
|
mirror := New(handler, pool, true, defaultMaxBodySize, nil)
|
||||||
|
|
||||||
for range numMirrors {
|
for range numMirrors {
|
||||||
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -177,13 +177,55 @@ func TestMirroringWithBody(t *testing.T) {
|
||||||
assert.Equal(t, numMirrors, int(val))
|
assert.Equal(t, numMirrors, int(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMirroringWithIgnoredBody(t *testing.T) {
|
||||||
|
const numMirrors = 10
|
||||||
|
|
||||||
|
var (
|
||||||
|
countMirror int32
|
||||||
|
body = []byte(`body`)
|
||||||
|
emptyBody = []byte(``)
|
||||||
|
)
|
||||||
|
|
||||||
|
pool := safe.NewPool(context.Background())
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.NotNil(t, r.Body)
|
||||||
|
bb, err := io.ReadAll(r.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, body, bb)
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
mirror := New(handler, pool, false, defaultMaxBodySize, nil)
|
||||||
|
|
||||||
|
for range numMirrors {
|
||||||
|
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
assert.NotNil(t, r.Body)
|
||||||
|
bb, err := io.ReadAll(r.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, emptyBody, bb)
|
||||||
|
atomic.AddInt32(&countMirror, 1)
|
||||||
|
}), 100)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(body))
|
||||||
|
|
||||||
|
mirror.ServeHTTP(httptest.NewRecorder(), req)
|
||||||
|
|
||||||
|
pool.Stop()
|
||||||
|
|
||||||
|
val := atomic.LoadInt32(&countMirror)
|
||||||
|
assert.Equal(t, numMirrors, int(val))
|
||||||
|
}
|
||||||
|
|
||||||
func TestCloneRequest(t *testing.T) {
|
func TestCloneRequest(t *testing.T) {
|
||||||
t.Run("http request body is nil", func(t *testing.T) {
|
t.Run("http request body is nil", func(t *testing.T) {
|
||||||
req, err := http.NewRequest(http.MethodPost, "/", nil)
|
req, err := http.NewRequest(http.MethodPost, "/", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
rr, _, err := newReusableRequest(req, defaultMaxBodySize)
|
rr, _, err := newReusableRequest(req, true, defaultMaxBodySize)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// first call
|
// first call
|
||||||
|
@ -208,7 +250,7 @@ func TestCloneRequest(t *testing.T) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
req.ContentLength = int64(contentLength)
|
req.ContentLength = int64(contentLength)
|
||||||
|
|
||||||
rr, _, err := newReusableRequest(req, defaultMaxBodySize)
|
rr, _, err := newReusableRequest(req, true, defaultMaxBodySize)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// first call
|
// first call
|
||||||
|
@ -231,7 +273,7 @@ func TestCloneRequest(t *testing.T) {
|
||||||
req, err := http.NewRequest(http.MethodPost, "/", buf)
|
req, err := http.NewRequest(http.MethodPost, "/", buf)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, expectedBytes, err := newReusableRequest(req, 2)
|
_, expectedBytes, err := newReusableRequest(req, true, 2)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, expectedBytes, bb[:3])
|
assert.Equal(t, expectedBytes, bb[:3])
|
||||||
})
|
})
|
||||||
|
@ -243,7 +285,7 @@ func TestCloneRequest(t *testing.T) {
|
||||||
req, err := http.NewRequest(http.MethodPost, "/", buf)
|
req, err := http.NewRequest(http.MethodPost, "/", buf)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rr, expectedBytes, err := newReusableRequest(req, 20)
|
rr, expectedBytes, err := newReusableRequest(req, true, 20)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, expectedBytes)
|
assert.Nil(t, expectedBytes)
|
||||||
assert.Len(t, rr.body, 10)
|
assert.Len(t, rr.body, 10)
|
||||||
|
@ -255,14 +297,14 @@ func TestCloneRequest(t *testing.T) {
|
||||||
req, err := http.NewRequest(http.MethodGet, "/", buf)
|
req, err := http.NewRequest(http.MethodGet, "/", buf)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rr, expectedBytes, err := newReusableRequest(req, 20)
|
rr, expectedBytes, err := newReusableRequest(req, true, 20)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, expectedBytes)
|
assert.Nil(t, expectedBytes)
|
||||||
assert.Empty(t, rr.body)
|
assert.Empty(t, rr.body)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no request given", func(t *testing.T) {
|
t.Run("no request given", func(t *testing.T) {
|
||||||
_, _, err := newReusableRequest(nil, defaultMaxBodySize)
|
_, _, err := newReusableRequest(nil, true, defaultMaxBodySize)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/traefik/traefik/v3/pkg/healthcheck"
|
"github.com/traefik/traefik/v3/pkg/healthcheck"
|
||||||
"github.com/traefik/traefik/v3/pkg/logs"
|
"github.com/traefik/traefik/v3/pkg/logs"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||||
"github.com/traefik/traefik/v3/pkg/safe"
|
"github.com/traefik/traefik/v3/pkg/safe"
|
||||||
|
@ -34,7 +35,10 @@ import (
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultMaxBodySize int64 = -1
|
const (
|
||||||
|
defaultMirrorBody = true
|
||||||
|
defaultMaxBodySize int64 = -1
|
||||||
|
)
|
||||||
|
|
||||||
// RoundTripperGetter is a roundtripper getter interface.
|
// RoundTripperGetter is a roundtripper getter interface.
|
||||||
type RoundTripperGetter interface {
|
type RoundTripperGetter interface {
|
||||||
|
@ -197,11 +201,16 @@ func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.M
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mirrorBody := defaultMirrorBody
|
||||||
|
if config.MirrorBody != nil {
|
||||||
|
mirrorBody = *config.MirrorBody
|
||||||
|
}
|
||||||
|
|
||||||
maxBodySize := defaultMaxBodySize
|
maxBodySize := defaultMaxBodySize
|
||||||
if config.MaxBodySize != nil {
|
if config.MaxBodySize != nil {
|
||||||
maxBodySize = *config.MaxBodySize
|
maxBodySize = *config.MaxBodySize
|
||||||
}
|
}
|
||||||
handler := mirror.New(serviceHandler, m.routinePool, maxBodySize, config.HealthCheck)
|
handler := mirror.New(serviceHandler, m.routinePool, mirrorBody, maxBodySize, config.HealthCheck)
|
||||||
for _, mirrorConfig := range config.Mirrors {
|
for _, mirrorConfig := range config.Mirrors {
|
||||||
mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name)
|
mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -364,6 +373,17 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
||||||
proxy = observability.NewService(ctx, serviceName, proxy)
|
proxy = observability.NewService(ctx, serviceName, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName) {
|
||||||
|
// Some piece of middleware, like the ErrorPage, are relying on this serviceBuilder to get the handler for a given service,
|
||||||
|
// to re-target the request to it.
|
||||||
|
// Those pieces of middleware can be configured on routes that expose a Traefik internal service.
|
||||||
|
// In such a case, observability for internals being optional, the capture probe could be absent from context (no wrap via the entrypoint).
|
||||||
|
// But if the service targeted by this piece of middleware is not an internal one,
|
||||||
|
// and requires observability, we still want the capture probe to be present in the request context.
|
||||||
|
// Makes sure a capture probe is in the request context.
|
||||||
|
proxy, _ = capture.Wrap(proxy)
|
||||||
|
}
|
||||||
|
|
||||||
lb.Add(proxyName, proxy, server.Weight)
|
lb.Add(proxyName, proxy, server.Weight)
|
||||||
|
|
||||||
// servers are considered UP by default.
|
// servers are considered UP by default.
|
||||||
|
|
Loading…
Reference in a new issue