Merge branch 'master' of github.com:traefik/traefik

This commit is contained in:
baalajimaestro 2024-08-14 21:04:12 +05:30
commit 5fe4691625
Signed by: baalajimaestro
GPG key ID: F93C394FE9BBAFD5
70 changed files with 3962 additions and 2696 deletions

View file

@ -213,6 +213,7 @@ issues:
- 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked' - 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
- "should have a package comment, unless it's in another file for this package" - "should have a package comment, unless it's in another file for this package"
- 'fmt.Sprintf can be replaced with string' - 'fmt.Sprintf can be replaced with string'
- 'SA1019: dockertypes.ContainerNode is deprecated'
exclude-rules: exclude-rules:
- path: '(.+)_test.go' - path: '(.+)_test.go'
linters: linters:

View file

@ -1,3 +1,33 @@
## [v3.1.2](https://github.com/traefik/traefik/tree/v3.1.2) (2024-08-06)
[All Commits](https://github.com/traefik/traefik/compare/v3.1.1...v3.1.2)
**Bug fixes:**
- **[k8s,k8s/gatewayapi]** Include status addresses when comparing Gateway statuses ([#10972](https://github.com/traefik/traefik/pull/10972) by [kevinpollet](https://github.com/kevinpollet))
- **[k8s/ingress,k8s/crd,k8s]** Allow to disable Kubernetes cluster scope resources discovery ([#10946](https://github.com/traefik/traefik/pull/10946) by [rtribotte](https://github.com/rtribotte))
- **[logs]** Change logs output from stderr to stdout ([#10973](https://github.com/traefik/traefik/pull/10973) by [rtribotte](https://github.com/rtribotte))
- Fix grafana dashboard to work with scrape interval greater than 15s ([#10954](https://github.com/traefik/traefik/pull/10954) by [swiffer](https://github.com/swiffer))
**Documentation:**
- **[accesslogs]** Add Access logs section to the migration guide ([#10947](https://github.com/traefik/traefik/pull/10947) by [lbenguigui](https://github.com/lbenguigui))
- **[http]** Fix missing codeblock ending in HTTP discover documentation ([#10967](https://github.com/traefik/traefik/pull/10967) by [djcode](https://github.com/djcode))
- **[http]** Fix yaml config example for HTTP provider headers ([#10966](https://github.com/traefik/traefik/pull/10966) by [djcode](https://github.com/djcode))
- **[k8s,k8s/gatewayapi]** Use Standard channel by default with Gateway API ([#10974](https://github.com/traefik/traefik/pull/10974) by [mloiseleur](https://github.com/mloiseleur))
**Misc:**
- Merge branch v2.11 into v3.1 ([#10978](https://github.com/traefik/traefik/pull/10978) by [rtribotte](https://github.com/rtribotte))
- Merge v2.11 into v3.1 ([#10956](https://github.com/traefik/traefik/pull/10956) by [mmatur](https://github.com/mmatur))
## [v2.11.8](https://github.com/traefik/traefik/tree/v2.11.8) (2024-08-06)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.7...v2.11.8)
**Bug fixes:**
- **[docker]** Update to github.com/docker/docker v27.1.1 ([#10955](https://github.com/traefik/traefik/pull/10955) by [rtribotte](https://github.com/rtribotte))
- **[webui]** Upgrade webui dependencies ([#10961](https://github.com/traefik/traefik/pull/10961) by [mmatur](https://github.com/mmatur))
**Documentation:**
- Fix embedded youtube video ([#10958](https://github.com/traefik/traefik/pull/10958) by [mmatur](https://github.com/mmatur))
- Updated index.md to include video ([#10944](https://github.com/traefik/traefik/pull/10944) by [tomatokoolaid](https://github.com/tomatokoolaid))
## [v3.1.1](https://github.com/traefik/traefik/tree/v3.1.1) (2024-07-30) ## [v3.1.1](https://github.com/traefik/traefik/tree/v3.1.1) (2024-07-30)
[All Commits](https://github.com/traefik/traefik/compare/v3.1.0...v3.1.1) [All Commits](https://github.com/traefik/traefik/compare/v3.1.0...v3.1.1)

View file

@ -46,7 +46,7 @@ func setupLogger(staticConfiguration *static.Configuration) {
} }
func getLogWriter(staticConfiguration *static.Configuration) io.Writer { func getLogWriter(staticConfiguration *static.Configuration) io.Writer {
var w io.Writer = os.Stderr var w io.Writer = os.Stdout
if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 { if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 {
_, _ = os.OpenFile(staticConfiguration.Log.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) _, _ = os.OpenFile(staticConfiguration.Log.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)

View file

@ -1028,7 +1028,7 @@
"refId": "A" "refId": "A"
} }
], ],
"title": "5xx over [$interval]", "title": "5xx over $interval",
"type": "timeseries" "type": "timeseries"
}, },
{ {
@ -1128,7 +1128,7 @@
"refId": "A" "refId": "A"
} }
], ],
"title": "Other codes over [$interval]", "title": "Other codes over $interval",
"type": "timeseries" "type": "timeseries"
}, },
{ {

View file

@ -242,7 +242,7 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "sum(rate(traefik_entrypoint_requests_total{entrypoint=~\"$entrypoint\"}[1m])) by (entrypoint)", "expr": "sum(rate(traefik_entrypoint_requests_total{entrypoint=~\"$entrypoint\"}[$interval])) by (entrypoint)",
"legendFormat": "{{entrypoint}}", "legendFormat": "{{entrypoint}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
@ -340,7 +340,7 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "(sum(rate(traefik_entrypoint_request_duration_seconds_bucket{le=\"0.3\",code=\"200\",entrypoint=~\"$entrypoint\"}[5m])) by (method) + \n sum(rate(traefik_entrypoint_request_duration_seconds_bucket{le=\"1.2\",code=\"200\",entrypoint=~\"$entrypoint\"}[5m])) by (method)) / 2 / \n sum(rate(traefik_entrypoint_request_duration_seconds_count{code=\"200\",entrypoint=~\"$entrypoint\"}[5m])) by (method)\n", "expr": "(sum(rate(traefik_entrypoint_request_duration_seconds_bucket{le=\"0.3\",code=\"200\",entrypoint=~\"$entrypoint\"}[$interval])) by (method) + \n sum(rate(traefik_entrypoint_request_duration_seconds_bucket{le=\"1.2\",code=\"200\",entrypoint=~\"$entrypoint\"}[$interval])) by (method)) / 2 / \n sum(rate(traefik_entrypoint_request_duration_seconds_count{code=\"200\",entrypoint=~\"$entrypoint\"}[$interval])) by (method)\n",
"legendFormat": "{{method}}", "legendFormat": "{{method}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
@ -408,7 +408,7 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "sum(rate(traefik_service_requests_total{service=~\"$service.*\",protocol=\"http\"}[1m])) by (method, code)", "expr": "sum(rate(traefik_service_requests_total{service=~\"$service.*\",protocol=\"http\"}[$interval])) by (method, code)",
"legendFormat": "{{method}}[{{code}}]", "legendFormat": "{{method}}[{{code}}]",
"range": true, "range": true,
"refId": "A" "refId": "A"
@ -606,7 +606,7 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "topk(15,\n label_replace(\n sum by (service,code) \n (rate(traefik_service_requests_total{service=~\"$service.*\",protocol=\"http\"}[5m])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)", "expr": "topk(15,\n label_replace(\n sum by (service,code) \n (rate(traefik_service_requests_total{service=~\"$service.*\",protocol=\"http\"}[$interval])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)",
"legendFormat": "[{{code}}] on {{service}}", "legendFormat": "[{{code}}] on {{service}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
@ -710,7 +710,7 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "label_replace(\n 1 - (sum by (service)\n (rate(traefik_service_request_duration_seconds_bucket{le=\"1.2\",service=~\"$service.*\"}[5m])) / sum by (service) \n (rate(traefik_service_request_duration_seconds_count{service=~\"$service.*\"}[5m]))\n ) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\"\n)", "expr": "label_replace(\n 1 - (sum by (service)\n (rate(traefik_service_request_duration_seconds_bucket{le=\"1.2\",service=~\"$service.*\"}[$interval])) / sum by (service) \n (rate(traefik_service_request_duration_seconds_count{service=~\"$service.*\"}[$interval]))\n ) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\"\n)",
"legendFormat": "{{service}}", "legendFormat": "{{service}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
@ -804,7 +804,7 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "label_replace(\n 1 - (sum by (service)\n (rate(traefik_service_request_duration_seconds_bucket{le=\"0.3\",service=~\"$service.*\"}[5m])) / sum by (service) \n (rate(traefik_service_request_duration_seconds_count{service=~\"$service.*\"}[5m]))\n ) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\"\n)", "expr": "label_replace(\n 1 - (sum by (service)\n (rate(traefik_service_request_duration_seconds_bucket{le=\"0.3\",service=~\"$service.*\"}[$interval])) / sum by (service) \n (rate(traefik_service_request_duration_seconds_count{service=~\"$service.*\"}[$interval]))\n ) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\"\n)",
"legendFormat": "{{service}}", "legendFormat": "{{service}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
@ -916,13 +916,13 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "topk(15,\n label_replace(\n sum by (service,method,code) \n (rate(traefik_service_requests_total{service=~\"$service.*\",code=~\"2..\",protocol=\"http\"}[5m])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)", "expr": "topk(15,\n label_replace(\n sum by (service,method,code) \n (rate(traefik_service_requests_total{service=~\"$service.*\",code=~\"2..\",protocol=\"http\"}[$interval])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)",
"legendFormat": "{{method}}[{{code}}] on {{service}}", "legendFormat": "{{method}}[{{code}}] on {{service}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
} }
], ],
"title": "2xx over 5 min", "title": "2xx over $interval",
"type": "timeseries" "type": "timeseries"
}, },
{ {
@ -1015,13 +1015,13 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "topk(15,\n label_replace(\n sum by (service,method,code) \n (rate(traefik_service_requests_total{service=~\"$service.*\",code=~\"5..\",protocol=\"http\"}[5m])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)", "expr": "topk(15,\n label_replace(\n sum by (service,method,code) \n (rate(traefik_service_requests_total{service=~\"$service.*\",code=~\"5..\",protocol=\"http\"}[$interval])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)",
"legendFormat": "{{method}}[{{code}}] on {{service}}", "legendFormat": "{{method}}[{{code}}] on {{service}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
} }
], ],
"title": "5xx over 5 min", "title": "5xx over $interval",
"type": "timeseries" "type": "timeseries"
}, },
{ {
@ -1114,13 +1114,13 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "topk(15,\n label_replace(\n sum by (service,method,code) \n (rate(traefik_service_requests_total{service=~\"$service.*\",code!~\"2..|5..\",protocol=\"http\"}[5m])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)", "expr": "topk(15,\n label_replace(\n sum by (service,method,code) \n (rate(traefik_service_requests_total{service=~\"$service.*\",code!~\"2..|5..\",protocol=\"http\"}[$interval])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)",
"legendFormat": "{{method}}[{{code}}] on {{service}}", "legendFormat": "{{method}}[{{code}}] on {{service}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
} }
], ],
"title": "Other codes over 5 min", "title": "Other codes over $interval",
"type": "timeseries" "type": "timeseries"
}, },
{ {
@ -1213,7 +1213,7 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "topk(15,\n label_replace(\n sum by (service,method) \n (rate(traefik_service_requests_bytes_total{service=~\"$service.*\",protocol=\"http\"}[1m])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)", "expr": "topk(15,\n label_replace(\n sum by (service,method) \n (rate(traefik_service_requests_bytes_total{service=~\"$service.*\",protocol=\"http\"}[$interval])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)",
"legendFormat": "{{method}} on {{service}}", "legendFormat": "{{method}} on {{service}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
@ -1312,7 +1312,7 @@
"uid": "${DS_PROMETHEUS}" "uid": "${DS_PROMETHEUS}"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "topk(15,\n label_replace(\n sum by (service,method) \n (rate(traefik_service_responses_bytes_total{service=~\"$service.*\",protocol=\"http\"}[1m])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)", "expr": "topk(15,\n label_replace(\n sum by (service,method) \n (rate(traefik_service_responses_bytes_total{service=~\"$service.*\",protocol=\"http\"}[$interval])) > 0,\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)",
"legendFormat": "{{method}} on {{service}}", "legendFormat": "{{method}} on {{service}}",
"range": true, "range": true,
"refId": "A" "refId": "A"
@ -1448,6 +1448,69 @@
"skipUrlSync": false, "skipUrlSync": false,
"type": "datasource" "type": "datasource"
}, },
{
"auto": true,
"auto_count": 30,
"auto_min": "1m",
"current": {
"selected": false,
"text": "auto",
"value": "$__auto_interval_interval"
},
"hide": 0,
"name": "interval",
"options": [
{
"selected": true,
"text": "auto",
"value": "$__auto_interval_interval"
},
{
"selected": false,
"text": "1m",
"value": "1m"
},
{
"selected": false,
"text": "5m",
"value": "5m"
},
{
"selected": false,
"text": "10m",
"value": "10m"
},
{
"selected": false,
"text": "30m",
"value": "30m"
},
{
"selected": false,
"text": "1h",
"value": "1h"
},
{
"selected": false,
"text": "2h",
"value": "2h"
},
{
"selected": false,
"text": "4h",
"value": "4h"
},
{
"selected": false,
"text": "8h",
"value": "8h"
}
],
"query": "1m,5m,10m,30m,1h,2h,4h,8h",
"refresh": 2,
"skipUrlSync": false,
"type": "interval"
},
{ {
"current": {}, "current": {},
"datasource": { "datasource": {

View file

@ -617,6 +617,7 @@ It defaults to `2160` (90 days) to follow Let's Encrypt certificates' duration.
|----------------------|-------------------|-------------------------| |----------------------|-------------------|-------------------------|
| >= 1 year | 4 months | 1 week | | >= 1 year | 4 months | 1 week |
| >= 90 days | 30 days | 1 day | | >= 90 days | 30 days | 1 day |
| >= 30 days | 10 days | 12 hours |
| >= 7 days | 1 day | 1 hour | | >= 7 days | 1 day | 1 hour |
| >= 24 hours | 6 hours | 10 min | | >= 24 hours | 6 hours | 10 min |
| < 24 hours | 20 min | 1 min | | < 24 hours | 20 min | 1 min |

View file

@ -7,17 +7,23 @@ description: "Traefik Proxy, an open source Edge Router, auto-discovers configur
![Architecture](assets/img/traefik-architecture.png) ![Architecture](assets/img/traefik-architecture.png)
Traefik is an [open-source](https://github.com/traefik/traefik) *Edge Router* that makes publishing your services a fun and easy experience. Traefik is an [open-source](https://github.com/traefik/traefik) *Application Proxy* that makes publishing your services a fun and easy experience.
It receives requests on behalf of your system and finds out which components are responsible for handling them. It receives requests on behalf of your system and identifies which components are responsible for handling them, and routes them securely.
What sets Traefik apart, besides its many features, is that it automatically discovers the right configuration for your services. What sets Traefik apart, besides its many features, is that it automatically discovers the right configuration for your services.
The magic happens when Traefik inspects your infrastructure, where it finds relevant information and discovers which service serves which request. The magic happens when Traefik inspects your infrastructure, where it finds relevant information and discovers which service serves which request.
Traefik is natively compliant with every major cluster technology, such as Kubernetes, Docker, Docker Swarm, AWS, and [the list goes on](providers/overview.md); and can handle many at the same time. (It even works for legacy software running on bare metal.) Traefik is natively compliant with every major cluster technology, such as Kubernetes, Docker Swarm, AWS, and [the list goes on](providers/overview.md); and can handle many at the same time. (It even works for legacy software running on bare metal.)
With Traefik, there is no need to maintain and synchronize a separate configuration file: everything happens automatically, in real time (no restarts, no connection interruptions). With Traefik, there is no need to maintain and synchronize a separate configuration file: everything happens automatically, in real time (no restarts, no connection interruptions).
With Traefik, you spend time developing and deploying new features to your system, not on configuring and maintaining its working state. With Traefik, you spend time developing and deploying new features to your system, not on configuring and maintaining its working state.
And if your needs change, you can add API gateway and API management capabilities seamlessly to your existing Traefik deployments. It takes less than a minute, theres no rip-and-replace, and all your configurations are preserved. See how it works in this video:
<div style="text-align: center;">
<iframe src="https://www.youtube.com/embed/zriUO5YPgFg?modestbranding=1&rel=0&controls=1" width="560" height="315" title="Upgrade Traefik Proxy to API Gateway and API Management in Seconds // Traefik Labs" frameborder="0" allowfullscreen></iframe>
</div>
Developing Traefik, our main goal is to make it effortless to use, and we're sure you'll enjoy it. Developing Traefik, our main goal is to make it effortless to use, and we're sure you'll enjoy it.
-- The Traefik Maintainer Team -- The Traefik Maintainer Team
@ -26,4 +32,4 @@ Developing Traefik, our main goal is to make it effortless to use, and we're sur
Join our user friendly and active [Community Forum](https://community.traefik.io "Link to Traefik Community Forum") to discuss, learn, and connect with the Traefik community. Join our user friendly and active [Community Forum](https://community.traefik.io "Link to Traefik Community Forum") to discuss, learn, and connect with the Traefik community.
Using Traefik OSS in Production? Add enterprise-grade API Gateway and API Management capabilities to your existing deployments seamlessly. No rip and replace. No learning curve. Learn more from [this short video](https://info.traefik.io/traefik-upgrade-walkthrough) Using Traefik OSS in Production? Consider our enterprise-grade [API Gateway](https://traefik.io/traefik-hub-api-gateway/), [API Management](https://traefik.io/traefik-hub/), and [Commercial Support](https://info.traefik.io/request-commercial-support) solutions.

View file

@ -255,3 +255,48 @@ http:
[http.middlewares.test-compress.compress] [http.middlewares.test-compress.compress]
defaultEncoding = "gzip" defaultEncoding = "gzip"
``` ```
### `encodings`
_Optional, Default="zstd, br, gzip"_
`encodings` specifies the list of supported compression encodings.
At least one encoding value must be specified, and valid entries are `zstd` (Zstandard), `br` (Brotli), and `gzip` (Gzip).
The order of the list also sets the priority, the top entry has the highest priority.
```yaml tab="Docker & Swarm"
labels:
- "traefik.http.middlewares.test-compress.compress.encodings=zstd,br"
```
```yaml tab="Kubernetes"
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-compress
spec:
compress:
encodings:
- zstd
- br
```
```yaml tab="Consul Catalog"
- "traefik.http.middlewares.test-compress.compress.encodings=zstd,br"
```
```yaml tab="File (YAML)"
http:
middlewares:
test-compress:
compress:
encodings:
- zstd
- br
```
```toml tab="File (TOML)"
[http.middlewares]
[http.middlewares.test-compress.compress]
encodings = ["zstd","br"]
```

View file

@ -591,6 +591,11 @@ Please take a look at the observability documentation for more information:
- [Metrics](../observability/metrics/overview.md#addinternals) - [Metrics](../observability/metrics/overview.md#addinternals)
- [Tracing](../observability/tracing/overview.md#addinternals) - [Tracing](../observability/tracing/overview.md#addinternals)
#### Access logs
In v3, the `ServiceURL` field is not an object anymore but a string representation.
An update may be required if you index access logs.
## Dynamic Configuration Changes ## Dynamic Configuration Changes
### Router Rule Matchers ### Router Rule Matchers

View file

@ -455,7 +455,7 @@ To enable HTTP/3 on an EntryPoint, please check out the [HTTP/3 configuration](.
In `v2.6`, the [Kubernetes Gateway API provider](../providers/kubernetes-gateway.md) now only supports the version [v1alpha2](https://gateway-api.sigs.k8s.io/v1alpha2/guides/) of the specification and In `v2.6`, the [Kubernetes Gateway API provider](../providers/kubernetes-gateway.md) now only supports the version [v1alpha2](https://gateway-api.sigs.k8s.io/v1alpha2/guides/) of the specification and
[route namespaces](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.RouteNamespaces) selectors, which requires Traefik to fetch and watch the cluster namespaces. [route namespaces](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.RouteNamespaces) selectors, which requires Traefik to fetch and watch the cluster namespaces.
Therefore, the [RBAC](../reference/dynamic-configuration/kubernetes-gateway.md#rbac) and [CRD](../reference/dynamic-configuration/kubernetes-gateway.md#definitions) definitions must be updated. Therefore, the RBAC and CRD definitions must be updated.
## v2.6.0 to v2.6.1 ## v2.6.0 to v2.6.1

View file

@ -10,9 +10,11 @@ description: "Learn the steps needed to migrate to new Traefik Proxy v3 versions
### Kubernetes Provider RBACs ### Kubernetes Provider RBACs
Starting with v3.1, the Kubernetes Providers now use the [EndpointSlices API](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/) (Kubernetes >=v1.21) to discover service endpoint addresses. Starting with v3.1, the Kubernetes Providers now use the [EndpointSlices API](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/) (Kubernetes >=v1.21) to discover service endpoint addresses.
It also brings NodePort load-balancing which requires Nodes resources lookup.
Therefore, in the corresponding RBACs (see [KubernetesIngress](../routing/providers/kubernetes-ingress.md#configuration-example), [KubernetesCRD](../reference/dynamic-configuration/kubernetes-crd.md#rbac), and [KubernetesGateway](../reference/dynamic-configuration/kubernetes-gateway.md#rbac) provider RBACs), Therefore, in the corresponding RBACs (see [KubernetesIngress](../routing/providers/kubernetes-ingress.md#configuration-example), [KubernetesCRD](../reference/dynamic-configuration/kubernetes-crd.md#rbac), and [KubernetesGateway](../reference/dynamic-configuration/kubernetes-gateway-rbac.yml) provider RBACs):
the `endpoints` right has to be removed and the following `endpointslices` right has to be added.
- the `endpoints` right has to be removed and the following `endpointslices` right has to be added:
```yaml ```yaml
... ...
@ -26,6 +28,21 @@ the `endpoints` right has to be removed and the following `endpointslices` right
... ...
``` ```
- the `nodes` right has to be added:
```yaml
...
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
...
```
#### Gateway API: KubernetesGateway Provider #### Gateway API: KubernetesGateway Provider
In v3.1, the KubernetesGateway Provider is no longer an experimental feature. In v3.1, the KubernetesGateway Provider is no longer an experimental feature.
@ -51,3 +68,10 @@ It can be enabled without the associated `experimental.kubernetesgateway` option
The `kubernetesgateway` option should be removed from the experimental section of the static configuration. The `kubernetesgateway` option should be removed from the experimental section of the static configuration.
To configure `kubernetesgateway`, please check out the [KubernetesGateway Provider documentation](../providers/kubernetes-gateway.md). To configure `kubernetesgateway`, please check out the [KubernetesGateway Provider documentation](../providers/kubernetes-gateway.md).
## v3.1.0 to v3.1.1
### IngressClass Lookup
The Kubernetes Ingress provider option `disableIngressClassLookup` has been deprecated in v3.1.1, and will be removed in the next major version.
Please use the `disableClusterScopeResources` option instead to avoid cluster scope resources discovery (IngressClass, Nodes).

View file

@ -134,6 +134,7 @@ You can specify which Docker API Endpoint to use with the directive [`endpoint`]
- Accounting at container level, by exposing the socket on a another container than Traefik's. - Accounting at container level, by exposing the socket on a another container than Traefik's.
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process). - Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
- SSH public key authentication (SSH is supported with Docker > 18.09) - SSH public key authentication (SSH is supported with Docker > 18.09)
- Authentication using HTTP Basic authentication through an HTTP proxy that exposes the Docker daemon socket.
??? info "More Resources and Examples" ??? info "More Resources and Examples"
@ -216,6 +217,50 @@ See the [Docker API Access](#docker-api-access) section for more information.
# ... # ...
``` ```
??? example "Using HTTP"
Using Docker Engine API you can connect Traefik to remote daemon using HTTP.
```yaml tab="File (YAML)"
providers:
docker:
endpoint: "http://127.0.0.1:2375"
# ...
```
```toml tab="File (TOML)"
[providers.docker]
endpoint = "http://127.0.0.1:2375"
# ...
```
```bash tab="CLI"
--providers.docker.endpoint=http://127.0.0.1:2375
# ...
```
??? example "Using TCP"
Using Docker Engine API you can connect Traefik to remote daemon using TCP.
```yaml tab="File (YAML)"
providers:
docker:
endpoint: "tcp://127.0.0.1:2375"
# ...
```
```toml tab="File (TOML)"
[providers.docker]
endpoint = "tcp://127.0.0.1:2375"
# ...
```
```bash tab="CLI"
--providers.docker.endpoint=tcp://127.0.0.1:2375
# ...
```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
providers: providers:
docker: docker:
@ -231,6 +276,56 @@ providers:
--providers.docker.endpoint=unix:///var/run/docker.sock --providers.docker.endpoint=unix:///var/run/docker.sock
``` ```
### `username`
_Optional, Default=""_
Defines the username for Basic HTTP authentication.
This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.
```yaml tab="File (YAML)"
providers:
docker:
username: foo
# ...
```
```toml tab="File (TOML)"
[providers.docker]
username = "foo"
# ...
```
```bash tab="CLI"
--providers.docker.username="foo"
# ...
```
### `password`
_Optional, Default=""_
Defines the password for Basic HTTP authentication.
This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.
```yaml tab="File (YAML)"
providers:
docker:
password: foo
# ...
```
```toml tab="File (TOML)"
[providers.docker]
password = "foo"
# ...
```
```bash tab="CLI"
--providers.docker.password="foo"
# ...
```
### `useBindPortIP` ### `useBindPortIP`
_Optional, Default=false_ _Optional, Default=false_

View file

@ -84,6 +84,7 @@ Defines custom headers to be sent to the endpoint.
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
providers: providers:
http:
headers: headers:
name: value name: value
``` ```
@ -95,6 +96,7 @@ providers:
```bash tab="CLI" ```bash tab="CLI"
--providers.http.headers.name=value --providers.http.headers.name=value
```
### `tls` ### `tls`

View file

@ -8,7 +8,7 @@ description: "Learn how to use the Kubernetes Gateway API as a provider for conf
The Kubernetes Gateway provider is a Traefik implementation of the [Gateway API](https://gateway-api.sigs.k8s.io/) The Kubernetes Gateway provider is a Traefik implementation of the [Gateway API](https://gateway-api.sigs.k8s.io/)
specification from the Kubernetes Special Interest Groups (SIGs). specification from the Kubernetes Special Interest Groups (SIGs).
This provider supports version [v1.1.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.1.0) of the Gateway API specification. This provider supports Standard version [v1.1.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.1.0) of the Gateway API specification.
It fully supports all HTTP core and some extended features, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). It fully supports all HTTP core and some extended features, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels).
@ -26,8 +26,8 @@ For more details, check out the conformance [report](https://github.com/kubernet
1. Install/update the Kubernetes Gateway API CRDs. 1. Install/update the Kubernetes Gateway API CRDs.
```bash ```bash
# Install Gateway API CRDs from the Experimental channel. # Install Gateway API CRDs from the Standard channel.
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/experimental-install.yaml kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/standard-install.yaml
``` ```
2. Install/update the Traefik [RBAC](../reference/dynamic-configuration/kubernetes-gateway.md#rbac). 2. Install/update the Traefik [RBAC](../reference/dynamic-configuration/kubernetes-gateway.md#rbac).
@ -269,6 +269,15 @@ providers:
--providers.kubernetesgateway.experimentalchannel=true --providers.kubernetesgateway.experimentalchannel=true
``` ```
!!! info "Experimental Channel"
When enabling experimental channel resources support, the experimental CRDs (Custom Resource Definitions) needs to be deployed too.
```bash
# Install Gateway API CRDs from the Experimental channel.
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/experimental-install.yaml
```
### `labelselector` ### `labelselector`
_Optional, Default: ""_ _Optional, Default: ""_

View file

@ -287,6 +287,11 @@ providers:
_Optional, Default: false_ _Optional, Default: false_
??? warning "Deprecated"
The Kubernetes Ingress provider option `disableIngressClassLookup` has been deprecated in v3.1, and will be removed in the next major version.
Please use the `disableClusterScopeResources` option instead.
If the parameter is set to `true`, If the parameter is set to `true`,
Traefik will not discover IngressClasses in the cluster. Traefik will not discover IngressClasses in the cluster.
By doing so, it alleviates the requirement of giving Traefik the rights to look IngressClasses up. By doing so, it alleviates the requirement of giving Traefik the rights to look IngressClasses up.
@ -312,6 +317,33 @@ providers:
--providers.kubernetesingress.disableingressclasslookup=true --providers.kubernetesingress.disableingressclasslookup=true
``` ```
### `disableClusterScopeResources`
_Optional, Default: false_
When this parameter is set to `true`,
Traefik will not discover cluster scope resources (`IngressClass` and `Nodes`).
By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.
Furthermore, Traefik will not handle Ingresses with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).
This will also prevent from using the `NodePortLB` options on services.
```yaml tab="File (YAML)"
providers:
kubernetesIngress:
disableClusterScopeResources: true
# ...
```
```toml tab="File (TOML)"
[providers.kubernetesIngress]
disableClusterScopeResources = true
# ...
```
```bash tab="CLI"
--providers.kubernetesingress.disableClusterScopeResources=true
```
### `ingressEndpoint` ### `ingressEndpoint`
#### `hostname` #### `hostname`

View file

@ -151,6 +151,7 @@ You can specify which Docker API Endpoint to use with the directive [`endpoint`]
It allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes. It allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process). - Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
- SSH public key authentication (SSH is supported with Docker > 18.09) - SSH public key authentication (SSH is supported with Docker > 18.09)
- Authentication using HTTP Basic authentication through an HTTP proxy that exposes the Docker daemon socket.
??? info "More Resources and Examples" ??? info "More Resources and Examples"
@ -262,6 +263,50 @@ See the [Docker Swarm API Access](#docker-api-access) section for more informati
# ... # ...
``` ```
??? example "Using HTTP"
Using Docker Engine API you can connect Traefik to remote daemon using HTTP.
```yaml tab="File (YAML)"
providers:
swarm:
endpoint: "http://127.0.0.1:2375"
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
swarm = "http://127.0.0.1:2375"
# ...
```
```bash tab="CLI"
--providers.swarm.endpoint=http://127.0.0.1:2375
# ...
```
??? example "Using TCP"
Using Docker Engine API you can connect Traefik to remote daemon using TCP.
```yaml tab="File (YAML)"
providers:
swarm:
endpoint: "tcp://127.0.0.1:2375"
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
swarm = "tcp://127.0.0.1:2375"
# ...
```
```bash tab="CLI"
--providers.swarm.endpoint=tcp://127.0.0.1:2375
# ...
```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
providers: providers:
swarm: swarm:
@ -277,6 +322,56 @@ providers:
--providers.swarm.endpoint=unix:///var/run/docker.sock --providers.swarm.endpoint=unix:///var/run/docker.sock
``` ```
### `username`
_Optional, Default=""_
Defines the username for Basic HTTP authentication.
This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.
```yaml tab="File (YAML)"
providers:
swarm:
username: foo
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
username = "foo"
# ...
```
```bash tab="CLI"
--providers.swarm.username="foo"
# ...
```
### `password`
_Optional, Default=""_
Defines the password for Basic HTTP authentication.
This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.
```yaml tab="File (YAML)"
providers:
swarm:
password: foo
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
password = "foo"
# ...
```
```bash tab="CLI"
--providers.swarm.password="foo"
# ...
```
### `useBindPortIP` ### `useBindPortIP`
_Optional, Default=false_ _Optional, Default=false_

View file

@ -19,6 +19,7 @@
- "traefik.http.middlewares.middleware05.circuitbreaker.responsecode=42" - "traefik.http.middlewares.middleware05.circuitbreaker.responsecode=42"
- "traefik.http.middlewares.middleware06.compress=true" - "traefik.http.middlewares.middleware06.compress=true"
- "traefik.http.middlewares.middleware06.compress.defaultencoding=foobar" - "traefik.http.middlewares.middleware06.compress.defaultencoding=foobar"
- "traefik.http.middlewares.middleware06.compress.encodings=foobar, foobar"
- "traefik.http.middlewares.middleware06.compress.excludedcontenttypes=foobar, foobar" - "traefik.http.middlewares.middleware06.compress.excludedcontenttypes=foobar, foobar"
- "traefik.http.middlewares.middleware06.compress.includedcontenttypes=foobar, foobar" - "traefik.http.middlewares.middleware06.compress.includedcontenttypes=foobar, foobar"
- "traefik.http.middlewares.middleware06.compress.minresponsebodybytes=42" - "traefik.http.middlewares.middleware06.compress.minresponsebodybytes=42"

View file

@ -143,6 +143,7 @@
excludedContentTypes = ["foobar", "foobar"] excludedContentTypes = ["foobar", "foobar"]
includedContentTypes = ["foobar", "foobar"] includedContentTypes = ["foobar", "foobar"]
minResponseBodyBytes = 42 minResponseBodyBytes = 42
encodings = ["foobar", "foobar"]
defaultEncoding = "foobar" defaultEncoding = "foobar"
[http.middlewares.Middleware07] [http.middlewares.Middleware07]
[http.middlewares.Middleware07.contentType] [http.middlewares.Middleware07.contentType]

View file

@ -152,6 +152,9 @@ http:
- foobar - foobar
- foobar - foobar
minResponseBodyBytes: 42 minResponseBodyBytes: 42
encodings:
- foobar
- foobar
defaultEncoding: foobar defaultEncoding: foobar
Middleware07: Middleware07:
contentType: contentType:

View file

@ -904,7 +904,7 @@ spec:
compress: compress:
description: |- description: |-
Compress holds the compress middleware configuration. Compress holds the compress middleware configuration.
This middleware compresses responses before sending them to the client, using gzip compression. This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression.
More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/ More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
properties: properties:
defaultEncoding: defaultEncoding:
@ -912,6 +912,12 @@ spec:
the `Accept-Encoding` header is not in the request or contains the `Accept-Encoding` header is not in the request or contains
a wildcard (`*`). a wildcard (`*`).
type: string type: string
encodings:
description: Encodings defines the list of supported compression
algorithms.
items:
type: string
type: array
excludedContentTypes: excludedContentTypes:
description: |- description: |-
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.

View file

@ -22,6 +22,8 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` | | `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` |
| `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` | | `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` |
| `traefik/http/middlewares/Middleware06/compress/defaultEncoding` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/defaultEncoding` | `foobar` |
| `traefik/http/middlewares/Middleware06/compress/encodings/0` | `foobar` |
| `traefik/http/middlewares/Middleware06/compress/encodings/1` | `foobar` |
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` |
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` |
| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` |

View file

@ -180,7 +180,7 @@ spec:
compress: compress:
description: |- description: |-
Compress holds the compress middleware configuration. Compress holds the compress middleware configuration.
This middleware compresses responses before sending them to the client, using gzip compression. This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression.
More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/ More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
properties: properties:
defaultEncoding: defaultEncoding:
@ -188,6 +188,12 @@ spec:
the `Accept-Encoding` header is not in the request or contains the `Accept-Encoding` header is not in the request or contains
a wildcard (`*`). a wildcard (`*`).
type: string type: string
encodings:
description: Encodings defines the list of supported compression
algorithms.
items:
type: string
type: array
excludedContentTypes: excludedContentTypes:
description: |- description: |-
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.

View file

@ -591,6 +591,9 @@ Client timeout for HTTP connections. (Default: ```0```)
`--providers.docker.network`: `--providers.docker.network`:
Default Docker network used. Default Docker network used.
`--providers.docker.password`:
Password for Basic HTTP authentication.
`--providers.docker.tls.ca`: `--providers.docker.tls.ca`:
TLS CA TLS CA
@ -606,6 +609,9 @@ TLS key
`--providers.docker.usebindportip`: `--providers.docker.usebindportip`:
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```) Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
`--providers.docker.username`:
Username for Basic HTTP authentication.
`--providers.docker.watch`: `--providers.docker.watch`:
Watch Docker events. (Default: ```true```) Watch Docker events. (Default: ```true```)
@ -726,6 +732,9 @@ Allow ExternalName services. (Default: ```false```)
`--providers.kubernetescrd.certauthfilepath`: `--providers.kubernetescrd.certauthfilepath`:
Kubernetes certificate authority file path (not needed for in-cluster client). Kubernetes certificate authority file path (not needed for in-cluster client).
`--providers.kubernetescrd.disableclusterscoperesources`:
Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services). (Default: ```false```)
`--providers.kubernetescrd.endpoint`: `--providers.kubernetescrd.endpoint`:
Kubernetes server endpoint (required for external cluster client). Kubernetes server endpoint (required for external cluster client).
@ -798,8 +807,11 @@ Allow ExternalName services. (Default: ```false```)
`--providers.kubernetesingress.certauthfilepath`: `--providers.kubernetesingress.certauthfilepath`:
Kubernetes certificate authority file path (not needed for in-cluster client). Kubernetes certificate authority file path (not needed for in-cluster client).
`--providers.kubernetesingress.disableclusterscoperesources`:
Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services). (Default: ```false```)
`--providers.kubernetesingress.disableingressclasslookup`: `--providers.kubernetesingress.disableingressclasslookup`:
Disables the lookup of IngressClasses. (Default: ```false```) Disables the lookup of IngressClasses (Deprecated, please use DisableClusterScopeResources). (Default: ```false```)
`--providers.kubernetesingress.endpoint`: `--providers.kubernetesingress.endpoint`:
Kubernetes server endpoint (required for external cluster client). Kubernetes server endpoint (required for external cluster client).
@ -969,6 +981,9 @@ Client timeout for HTTP connections. (Default: ```0```)
`--providers.swarm.network`: `--providers.swarm.network`:
Default Docker network used. Default Docker network used.
`--providers.swarm.password`:
Password for Basic HTTP authentication.
`--providers.swarm.refreshseconds`: `--providers.swarm.refreshseconds`:
Polling interval for swarm mode. (Default: ```15```) Polling interval for swarm mode. (Default: ```15```)
@ -987,6 +1002,9 @@ TLS key
`--providers.swarm.usebindportip`: `--providers.swarm.usebindportip`:
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```) Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
`--providers.swarm.username`:
Username for Basic HTTP authentication.
`--providers.swarm.watch`: `--providers.swarm.watch`:
Watch Docker events. (Default: ```true```) Watch Docker events. (Default: ```true```)

View file

@ -591,6 +591,9 @@ Client timeout for HTTP connections. (Default: ```0```)
`TRAEFIK_PROVIDERS_DOCKER_NETWORK`: `TRAEFIK_PROVIDERS_DOCKER_NETWORK`:
Default Docker network used. Default Docker network used.
`TRAEFIK_PROVIDERS_DOCKER_PASSWORD`:
Password for Basic HTTP authentication.
`TRAEFIK_PROVIDERS_DOCKER_TLS_CA`: `TRAEFIK_PROVIDERS_DOCKER_TLS_CA`:
TLS CA TLS CA
@ -606,6 +609,9 @@ TLS key
`TRAEFIK_PROVIDERS_DOCKER_USEBINDPORTIP`: `TRAEFIK_PROVIDERS_DOCKER_USEBINDPORTIP`:
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```) Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
`TRAEFIK_PROVIDERS_DOCKER_USERNAME`:
Username for Basic HTTP authentication.
`TRAEFIK_PROVIDERS_DOCKER_WATCH`: `TRAEFIK_PROVIDERS_DOCKER_WATCH`:
Watch Docker events. (Default: ```true```) Watch Docker events. (Default: ```true```)
@ -726,6 +732,9 @@ Allow ExternalName services. (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESCRD_CERTAUTHFILEPATH`: `TRAEFIK_PROVIDERS_KUBERNETESCRD_CERTAUTHFILEPATH`:
Kubernetes certificate authority file path (not needed for in-cluster client). Kubernetes certificate authority file path (not needed for in-cluster client).
`TRAEFIK_PROVIDERS_KUBERNETESCRD_DISABLECLUSTERSCOPERESOURCES`:
Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services). (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESCRD_ENDPOINT`: `TRAEFIK_PROVIDERS_KUBERNETESCRD_ENDPOINT`:
Kubernetes server endpoint (required for external cluster client). Kubernetes server endpoint (required for external cluster client).
@ -798,8 +807,11 @@ Allow ExternalName services. (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`: `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`:
Kubernetes certificate authority file path (not needed for in-cluster client). Kubernetes certificate authority file path (not needed for in-cluster client).
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_DISABLECLUSTERSCOPERESOURCES`:
Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services). (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_DISABLEINGRESSCLASSLOOKUP`: `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_DISABLEINGRESSCLASSLOOKUP`:
Disables the lookup of IngressClasses. (Default: ```false```) Disables the lookup of IngressClasses (Deprecated, please use DisableClusterScopeResources). (Default: ```false```)
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ENDPOINT`: `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ENDPOINT`:
Kubernetes server endpoint (required for external cluster client). Kubernetes server endpoint (required for external cluster client).
@ -969,6 +981,9 @@ Client timeout for HTTP connections. (Default: ```0```)
`TRAEFIK_PROVIDERS_SWARM_NETWORK`: `TRAEFIK_PROVIDERS_SWARM_NETWORK`:
Default Docker network used. Default Docker network used.
`TRAEFIK_PROVIDERS_SWARM_PASSWORD`:
Password for Basic HTTP authentication.
`TRAEFIK_PROVIDERS_SWARM_REFRESHSECONDS`: `TRAEFIK_PROVIDERS_SWARM_REFRESHSECONDS`:
Polling interval for swarm mode. (Default: ```15```) Polling interval for swarm mode. (Default: ```15```)
@ -987,6 +1002,9 @@ TLS key
`TRAEFIK_PROVIDERS_SWARM_USEBINDPORTIP`: `TRAEFIK_PROVIDERS_SWARM_USEBINDPORTIP`:
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```) Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
`TRAEFIK_PROVIDERS_SWARM_USERNAME`:
Username for Basic HTTP authentication.
`TRAEFIK_PROVIDERS_SWARM_WATCH`: `TRAEFIK_PROVIDERS_SWARM_WATCH`:
Watch Docker events. (Default: ```true```) Watch Docker events. (Default: ```true```)

View file

@ -85,6 +85,8 @@
useBindPortIP = true useBindPortIP = true
watch = true watch = true
defaultRule = "foobar" defaultRule = "foobar"
username = "foobar"
password = "foobar"
endpoint = "foobar" endpoint = "foobar"
httpClientTimeout = "42s" httpClientTimeout = "42s"
[providers.docker.tls] [providers.docker.tls]
@ -100,6 +102,8 @@
useBindPortIP = true useBindPortIP = true
watch = true watch = true
defaultRule = "foobar" defaultRule = "foobar"
username = "foobar"
password = "foobar"
endpoint = "foobar" endpoint = "foobar"
httpClientTimeout = "42s" httpClientTimeout = "42s"
refreshSeconds = "42s" refreshSeconds = "42s"
@ -124,6 +128,7 @@
allowEmptyServices = true allowEmptyServices = true
allowExternalNameServices = true allowExternalNameServices = true
disableIngressClassLookup = true disableIngressClassLookup = true
disableClusterScopeResources = true
nativeLBByDefault = true nativeLBByDefault = true
[providers.kubernetesIngress.ingressEndpoint] [providers.kubernetesIngress.ingressEndpoint]
ip = "foobar" ip = "foobar"
@ -141,6 +146,7 @@
throttleDuration = "42s" throttleDuration = "42s"
allowEmptyServices = true allowEmptyServices = true
nativeLBByDefault = true nativeLBByDefault = true
disableClusterScopeResources = true
[providers.kubernetesGateway] [providers.kubernetesGateway]
endpoint = "foobar" endpoint = "foobar"
token = "foobar" token = "foobar"

View file

@ -96,6 +96,8 @@ providers:
useBindPortIP: true useBindPortIP: true
watch: true watch: true
defaultRule: foobar defaultRule: foobar
username: foobar
password: foobar
endpoint: foobar endpoint: foobar
tls: tls:
ca: foobar ca: foobar
@ -111,6 +113,8 @@ providers:
useBindPortIP: true useBindPortIP: true
watch: true watch: true
defaultRule: foobar defaultRule: foobar
username: foobar
password: foobar
endpoint: foobar endpoint: foobar
tls: tls:
ca: foobar ca: foobar
@ -141,6 +145,7 @@ providers:
allowEmptyServices: true allowEmptyServices: true
allowExternalNameServices: true allowExternalNameServices: true
disableIngressClassLookup: true disableIngressClassLookup: true
disableClusterScopeResources: true
nativeLBByDefault: true nativeLBByDefault: true
kubernetesCRD: kubernetesCRD:
endpoint: foobar endpoint: foobar
@ -156,6 +161,7 @@ providers:
throttleDuration: 42s throttleDuration: 42s
allowEmptyServices: true allowEmptyServices: true
nativeLBByDefault: true nativeLBByDefault: true
disableClusterScopeResources: true
kubernetesGateway: kubernetesGateway:
endpoint: foobar endpoint: foobar
token: foobar token: foobar

20
go.mod
View file

@ -12,8 +12,8 @@ require (
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd // No tag on the repo. github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd // No tag on the repo.
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // No tag on the repo. github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // No tag on the repo.
github.com/coreos/go-systemd/v22 v22.5.0 github.com/coreos/go-systemd/v22 v22.5.0
github.com/docker/cli v24.0.9+incompatible github.com/docker/cli v27.1.1+incompatible
github.com/docker/docker v27.0.3+incompatible github.com/docker/docker v27.1.1+incompatible
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
@ -22,7 +22,7 @@ require (
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
github.com/google/go-github/v28 v28.1.1 github.com/google/go-github/v28 v28.1.1
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.1 github.com/gorilla/websocket v1.5.1
github.com/hashicorp/consul/api v1.26.1 github.com/hashicorp/consul/api v1.26.1
github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-hclog v1.6.3
@ -34,7 +34,7 @@ require (
github.com/influxdata/influxdb-client-go/v2 v2.7.0 github.com/influxdata/influxdb-client-go/v2 v2.7.0
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // No tag on the repo. github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // No tag on the repo.
github.com/juliens/wasm-goexport v0.0.6 github.com/juliens/wasm-goexport v0.0.6
github.com/klauspost/compress v1.17.4 github.com/klauspost/compress v1.17.9
github.com/kvtools/consul v1.0.2 github.com/kvtools/consul v1.0.2
github.com/kvtools/etcdv3 v1.0.2 github.com/kvtools/etcdv3 v1.0.2
github.com/kvtools/redis v1.1.0 github.com/kvtools/redis v1.1.0
@ -129,7 +129,7 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.11.5 // indirect github.com/Microsoft/hcsshim v0.11.7 // indirect
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/VividCortex/gohistogram v1.0.0 // indirect github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
@ -157,9 +157,9 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/civo/civogo v0.3.11 // indirect github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/cloudflare-go v0.97.0 // indirect github.com/cloudflare/cloudflare-go v0.97.0 // indirect
github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/containerd v1.7.20 // indirect
github.com/containerd/errdefs v0.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect github.com/cpu/goacmedns v0.1.1 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect
@ -194,7 +194,7 @@ require (
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/go-zookeeper/zk v1.0.3 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/flock v0.12.0 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
@ -255,7 +255,7 @@ require (
github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/spdystream v0.2.0 // indirect github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/user v0.1.0 // indirect github.com/moby/sys/user v0.2.0 // indirect
github.com/moby/term v0.5.0 // indirect github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
@ -287,7 +287,7 @@ require (
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pquerna/otp v1.4.0 // indirect github.com/pquerna/otp v1.4.0 // indirect
github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/redis/go-redis/v9 v9.2.1 // indirect github.com/redis/go-redis/v9 v9.2.1 // indirect
github.com/rs/cors v1.7.0 // indirect github.com/rs/cors v1.7.0 // indirect

35
go.sum
View file

@ -82,8 +82,8 @@ github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.11.5 h1:haEcLNpj9Ka1gd3B3tAEs9CpE0c+1IhoL59w/exYU38= github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
@ -192,12 +192,12 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ=
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0=
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd h1:0n+lFLh5zU0l6KSk3KpnDwfbPGAR44aRLgTbCnhRBHU= github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd h1:0n+lFLh5zU0l6KSk3KpnDwfbPGAR44aRLgTbCnhRBHU=
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd/go.mod h1:BbQgeDS5i0tNvypwEoF1oNjOJw8knRAE1DnVvjDstcQ= github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd/go.mod h1:BbQgeDS5i0tNvypwEoF1oNjOJw8knRAE1DnVvjDstcQ=
github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e h1:D+uTEzDZc1Fhmd0Pq06c+O9+KkAyExw0eVmu/NOqaHU= github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e h1:D+uTEzDZc1Fhmd0Pq06c+O9+KkAyExw0eVmu/NOqaHU=
@ -247,10 +247,10 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY= github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY=
github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8= github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8=
github.com/docker/cli v24.0.9+incompatible h1:OxbimnP/z+qVjDLpq9wbeFU3Nc30XhSe+LkwYQisD50= github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
github.com/docker/cli v24.0.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@ -377,8 +377,9 @@ github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.12.0 h1:xHW8t8GPAiGtqz7KxiSqfOEXwpOaqhpYZrTE2MQBgXY=
github.com/gofrs/flock v0.12.0/go.mod h1:FirDy1Ing0mI2+kB6wk+vyyAH+e6xiE+EYA0jnzV9jc=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -599,8 +600,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@ -742,8 +743,8 @@ github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= github.com/moby/sys/user v0.2.0 h1:OnpapJsRp25vkhw8TFG6OLJODNh/3rEwRWtJ3kakwRM=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/sys/user v0.2.0/go.mod h1:RYstrcWOJpVh+6qzUqp2bU3eaRpdiQeKGlKitaH0PM8=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -872,8 +873,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=

View file

@ -904,7 +904,7 @@ spec:
compress: compress:
description: |- description: |-
Compress holds the compress middleware configuration. Compress holds the compress middleware configuration.
This middleware compresses responses before sending them to the client, using gzip compression. This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression.
More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/ More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
properties: properties:
defaultEncoding: defaultEncoding:
@ -912,6 +912,12 @@ spec:
the `Accept-Encoding` header is not in the request or contains the `Accept-Encoding` header is not in the request or contains
a wildcard (`*`). a wildcard (`*`).
type: string type: string
encodings:
description: Encodings defines the list of supported compression
algorithms.
items:
type: string
type: array
excludedContentTypes: excludedContentTypes:
description: |- description: |-
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.

View file

@ -86,8 +86,8 @@ func (s *K8sConformanceSuite) SetupSuite() {
s.T().Fatal("Traefik image is not present") s.T().Fatal("Traefik image is not present")
} }
s.k3sContainer, err = k3s.RunContainer(ctx, s.k3sContainer, err = k3s.Run(ctx,
testcontainers.WithImage(k3sImage), k3sImage,
k3s.WithManifest("./fixtures/k8s-conformance/00-experimental-v1.1.0.yml"), k3s.WithManifest("./fixtures/k8s-conformance/00-experimental-v1.1.0.yml"),
k3s.WithManifest("./fixtures/k8s-conformance/01-rbac.yml"), k3s.WithManifest("./fixtures/k8s-conformance/01-rbac.yml"),
k3s.WithManifest("./fixtures/k8s-conformance/02-traefik.yml"), k3s.WithManifest("./fixtures/k8s-conformance/02-traefik.yml"),
@ -206,6 +206,7 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
features.SupportHTTPRouteHostRewrite, features.SupportHTTPRouteHostRewrite,
features.SupportHTTPRoutePathRewrite, features.SupportHTTPRoutePathRewrite,
features.SupportHTTPRoutePathRedirect, features.SupportHTTPRoutePathRedirect,
features.SupportHTTPRouteResponseHeaderModification,
), ),
}) })
require.NoError(s.T(), err) require.NoError(s.T(), err)

View file

@ -214,6 +214,7 @@ type providers struct {
ETCD *etcd `json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty"` ETCD *etcd `json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty"`
Redis *redis `json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty"` Redis *redis `json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty"`
HTTP *http `json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty"` HTTP *http `json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty"`
KubernetesIngress *ingress `json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" file:"allowEmpty"`
} }
func (p *providers) deprecationNotice(logger zerolog.Logger) bool { func (p *providers) deprecationNotice(logger zerolog.Logger) bool {
@ -243,6 +244,7 @@ func (p *providers) deprecationNotice(logger zerolog.Logger) bool {
etcdIncompatible := p.ETCD.deprecationNotice(logger) etcdIncompatible := p.ETCD.deprecationNotice(logger)
redisIncompatible := p.Redis.deprecationNotice(logger) redisIncompatible := p.Redis.deprecationNotice(logger)
httpIncompatible := p.HTTP.deprecationNotice(logger) httpIncompatible := p.HTTP.deprecationNotice(logger)
p.KubernetesIngress.deprecationNotice(logger)
return incompatible || return incompatible ||
dockerIncompatible || dockerIncompatible ||
consulIncompatible || consulIncompatible ||
@ -457,6 +459,22 @@ func (h *http) deprecationNotice(logger zerolog.Logger) bool {
return incompatible return incompatible
} }
type ingress struct {
DisableIngressClassLookup *bool `json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty"`
}
func (i *ingress) deprecationNotice(logger zerolog.Logger) {
if i == nil {
return
}
if i.DisableIngressClassLookup != nil {
logger.Error().Msg("Kubernetes Ingress provider `disableIngressClassLookup` option has been deprecated in v3.1, and will be removed in the next major version." +
"Please use the `disableClusterScopeResources` option instead." +
"For more information please read the migration guide: https://doc.traefik.io/traefik/v3.1/migration/v3/#ingressclasslookup")
}
}
type experimental struct { type experimental struct {
HTTP3 *bool `json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty"` HTTP3 *bool `json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty"`
KubernetesGateway *bool `json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty"` KubernetesGateway *bool `json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty"`

View file

@ -40,8 +40,9 @@ type Middleware struct {
Plugin map[string]PluginConf `json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty" export:"true"` Plugin map[string]PluginConf `json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty" export:"true"`
// Gateway API HTTPRoute filters middlewares. // Gateway API filter middlewares.
RequestHeaderModifier *RequestHeaderModifier `json:"requestHeaderModifier,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"` RequestHeaderModifier *HeaderModifier `json:"requestHeaderModifier,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
ResponseHeaderModifier *HeaderModifier `json:"responseHeaderModifier,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
RequestRedirect *RequestRedirect `json:"requestRedirect,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"` RequestRedirect *RequestRedirect `json:"requestRedirect,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
URLRewrite *URLRewrite `json:"URLRewrite,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"` URLRewrite *URLRewrite `json:"URLRewrite,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
} }
@ -165,8 +166,7 @@ func (c *CircuitBreaker) SetDefaults() {
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// Compress holds the compress middleware configuration. // Compress holds the compress middleware configuration.
// This middleware compresses responses before sending them to the client, using gzip compression. // This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression.
// More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
type Compress struct { type Compress struct {
// ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. // ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
// `application/grpc` is always excluded. // `application/grpc` is always excluded.
@ -176,10 +176,16 @@ type Compress struct {
// MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed. // MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed.
// Default: 1024. // Default: 1024.
MinResponseBodyBytes int `json:"minResponseBodyBytes,omitempty" toml:"minResponseBodyBytes,omitempty" yaml:"minResponseBodyBytes,omitempty" export:"true"` MinResponseBodyBytes int `json:"minResponseBodyBytes,omitempty" toml:"minResponseBodyBytes,omitempty" yaml:"minResponseBodyBytes,omitempty" export:"true"`
// Encodings defines the list of supported compression algorithms.
Encodings []string `json:"encodings,omitempty" toml:"encodings,omitempty" yaml:"encodings,omitempty" export:"true"`
// DefaultEncoding specifies the default encoding if the `Accept-Encoding` header is not in the request or contains a wildcard (`*`). // DefaultEncoding specifies the default encoding if the `Accept-Encoding` header is not in the request or contains a wildcard (`*`).
DefaultEncoding string `json:"defaultEncoding,omitempty" toml:"defaultEncoding,omitempty" yaml:"defaultEncoding,omitempty" export:"true"` DefaultEncoding string `json:"defaultEncoding,omitempty" toml:"defaultEncoding,omitempty" yaml:"defaultEncoding,omitempty" export:"true"`
} }
func (c *Compress) SetDefaults() {
c.Encodings = []string{"zstd", "br", "gzip"}
}
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// DigestAuth holds the digest auth middleware configuration. // DigestAuth holds the digest auth middleware configuration.
@ -689,8 +695,8 @@ type Users []string
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// RequestHeaderModifier holds the request header modifier configuration. // HeaderModifier holds the request/response header modifier configuration.
type RequestHeaderModifier struct { type HeaderModifier struct {
Set map[string]string `json:"set,omitempty"` Set map[string]string `json:"set,omitempty"`
Add map[string]string `json:"add,omitempty"` Add map[string]string `json:"add,omitempty"`
Remove []string `json:"remove,omitempty"` Remove []string `json:"remove,omitempty"`

View file

@ -158,6 +158,11 @@ func (in *Compress) DeepCopyInto(out *Compress) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.Encodings != nil {
in, out := &in.Encodings, &out.Encodings
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }
@ -501,6 +506,41 @@ func (in *HTTPConfiguration) DeepCopy() *HTTPConfiguration {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HeaderModifier) DeepCopyInto(out *HeaderModifier) {
*out = *in
if in.Set != nil {
in, out := &in.Set, &out.Set
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Add != nil {
in, out := &in.Add, &out.Add
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Remove != nil {
in, out := &in.Remove, &out.Remove
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderModifier.
func (in *HeaderModifier) DeepCopy() *HeaderModifier {
if in == nil {
return nil
}
out := new(HeaderModifier)
in.DeepCopyInto(out)
return out
}
// 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 *Headers) DeepCopyInto(out *Headers) { func (in *Headers) DeepCopyInto(out *Headers) {
*out = *in *out = *in
@ -861,7 +901,12 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
} }
if in.RequestHeaderModifier != nil { if in.RequestHeaderModifier != nil {
in, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier in, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier
*out = new(RequestHeaderModifier) *out = new(HeaderModifier)
(*in).DeepCopyInto(*out)
}
if in.ResponseHeaderModifier != nil {
in, out := &in.ResponseHeaderModifier, &out.ResponseHeaderModifier
*out = new(HeaderModifier)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.RequestRedirect != nil { if in.RequestRedirect != nil {
@ -1082,41 +1127,6 @@ func (in *ReplacePathRegex) DeepCopy() *ReplacePathRegex {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RequestHeaderModifier) DeepCopyInto(out *RequestHeaderModifier) {
*out = *in
if in.Set != nil {
in, out := &in.Set, &out.Set
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Add != nil {
in, out := &in.Add, &out.Add
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Remove != nil {
in, out := &in.Remove, &out.Remove
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeaderModifier.
func (in *RequestHeaderModifier) DeepCopy() *RequestHeaderModifier {
if in == nil {
return nil
}
out := new(RequestHeaderModifier)
in.DeepCopyInto(out)
return out
}
// 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 *RequestRedirect) DeepCopyInto(out *RequestRedirect) { func (in *RequestRedirect) DeepCopyInto(out *RequestRedirect) {
*out = *in *out = *in

View file

@ -137,6 +137,7 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar", "traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
"traefik.http.middlewares.Middleware17.stripprefix.forceslash": "true", "traefik.http.middlewares.Middleware17.stripprefix.forceslash": "true",
"traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar", "traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
"traefik.http.middlewares.Middleware19.compress.encodings": "foobar, fiibar",
"traefik.http.middlewares.Middleware19.compress.minresponsebodybytes": "42", "traefik.http.middlewares.Middleware19.compress.minresponsebodybytes": "42",
"traefik.http.middlewares.Middleware20.plugin.tomato.aaa": "foo1", "traefik.http.middlewares.Middleware20.plugin.tomato.aaa": "foo1",
"traefik.http.middlewares.Middleware20.plugin.tomato.bbb": "foo2", "traefik.http.middlewares.Middleware20.plugin.tomato.bbb": "foo2",
@ -493,6 +494,10 @@ func TestDecodeConfiguration(t *testing.T) {
"Middleware19": { "Middleware19": {
Compress: &dynamic.Compress{ Compress: &dynamic.Compress{
MinResponseBodyBytes: 42, MinResponseBodyBytes: 42,
Encodings: []string{
"foobar",
"fiibar",
},
}, },
}, },
"Middleware2": { "Middleware2": {
@ -1009,6 +1014,10 @@ func TestEncodeConfiguration(t *testing.T) {
"Middleware19": { "Middleware19": {
Compress: &dynamic.Compress{ Compress: &dynamic.Compress{
MinResponseBodyBytes: 42, MinResponseBodyBytes: 42,
Encodings: []string{
"foobar",
"fiibar",
},
}, },
}, },
"Middleware2": { "Middleware2": {
@ -1377,6 +1386,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true", "traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware19.Compress.Encodings": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware19.Compress.MinResponseBodyBytes": "42", "traefik.HTTP.Middlewares.Middleware19.Compress.MinResponseBodyBytes": "42",
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1", "traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1",
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.bbb": "foo2", "traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.bbb": "foo2",

View file

@ -22,13 +22,18 @@ type Encoding struct {
Weight *float64 Weight *float64
} }
func getCompressionType(acceptEncoding []string, defaultType string) string { func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, supportedEncodings []string) string {
if defaultType == "" { if defaultEncoding == "" {
// Keeps the pre-existing default inside Traefik. if slices.Contains(supportedEncodings, brotliName) {
defaultType = brotliName // Keeps the pre-existing default inside Traefik if brotli is a supported encoding.
defaultEncoding = brotliName
} else if len(supportedEncodings) > 0 {
// Otherwise use the first supported encoding.
defaultEncoding = supportedEncodings[0]
}
} }
encodings, hasWeight := parseAcceptEncoding(acceptEncoding) encodings, hasWeight := parseAcceptEncoding(acceptEncoding, supportedEncodings)
if hasWeight { if hasWeight {
if len(encodings) == 0 { if len(encodings) == 0 {
@ -46,26 +51,26 @@ func getCompressionType(acceptEncoding []string, defaultType string) string {
} }
if encoding.Type == wildcardName { if encoding.Type == wildcardName {
return defaultType return defaultEncoding
} }
return encoding.Type return encoding.Type
} }
for _, dt := range []string{zstdName, brotliName, gzipName} { for _, dt := range supportedEncodings {
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == dt }) { if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == dt }) {
return dt return dt
} }
} }
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == wildcardName }) { if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == wildcardName }) {
return defaultType return defaultEncoding
} }
return identityName return identityName
} }
func parseAcceptEncoding(acceptEncoding []string) ([]Encoding, bool) { func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encoding, bool) {
var encodings []Encoding var encodings []Encoding
var hasWeight bool var hasWeight bool
@ -76,10 +81,9 @@ func parseAcceptEncoding(acceptEncoding []string) ([]Encoding, bool) {
continue continue
} }
switch parsed[0] { if !slices.Contains(supportedEncodings, parsed[0]) &&
case zstdName, brotliName, gzipName, identityName, wildcardName: parsed[0] != identityName &&
// supported encoding parsed[0] != wildcardName {
default:
continue continue
} }

View file

@ -6,83 +6,100 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func Test_getCompressionType(t *testing.T) { func Test_getCompressionEncoding(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
values []string acceptEncoding []string
defaultType string defaultEncoding string
supportedEncodings []string
expected string expected string
}{ }{
{ {
desc: "br > gzip (no weight)", desc: "br > gzip (no weight)",
values: []string{"gzip, br"}, acceptEncoding: []string{"gzip, br"},
expected: brotliName, expected: brotliName,
}, },
{ {
desc: "zstd > br > gzip (no weight)", desc: "zstd > br > gzip (no weight)",
values: []string{"zstd, gzip, br"}, acceptEncoding: []string{"zstd, gzip, br"},
expected: zstdName, expected: zstdName,
}, },
{ {
desc: "known compression type (no weight)", desc: "known compression encoding (no weight)",
values: []string{"compress, gzip"}, acceptEncoding: []string{"compress, gzip"},
expected: gzipName, expected: gzipName,
}, },
{ {
desc: "unknown compression type (no weight), no encoding", desc: "unknown compression encoding (no weight), no encoding",
values: []string{"compress, rar"}, acceptEncoding: []string{"compress, rar"},
expected: identityName, expected: identityName,
}, },
{ {
desc: "wildcard return the default compression type", desc: "wildcard return the default compression encoding",
values: []string{"*"}, acceptEncoding: []string{"*"},
expected: brotliName, expected: brotliName,
}, },
{ {
desc: "wildcard return the custom default compression type", desc: "wildcard return the custom default compression encoding",
values: []string{"*"}, acceptEncoding: []string{"*"},
defaultType: "foo", defaultEncoding: "foo",
expected: "foo", expected: "foo",
}, },
{ {
desc: "follows weight", desc: "follows weight",
values: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"}, acceptEncoding: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"},
expected: gzipName, expected: gzipName,
}, },
{ {
desc: "ignore unknown compression type", desc: "ignore unknown compression encoding",
values: []string{"compress;q=1.0, gzip;q=0.5"}, acceptEncoding: []string{"compress;q=1.0, gzip;q=0.5"},
expected: gzipName, expected: gzipName,
}, },
{ {
desc: "fallback on non-zero compression type", desc: "fallback on non-zero compression encoding",
values: []string{"compress;q=1.0, gzip, identity;q=0"}, acceptEncoding: []string{"compress;q=1.0, gzip, identity;q=0"},
expected: gzipName, expected: gzipName,
}, },
{ {
desc: "not acceptable (identity)", desc: "not acceptable (identity)",
values: []string{"compress;q=1.0, identity;q=0"}, acceptEncoding: []string{"compress;q=1.0, identity;q=0"},
expected: notAcceptable, expected: notAcceptable,
}, },
{ {
desc: "not acceptable (wildcard)", desc: "not acceptable (wildcard)",
values: []string{"compress;q=1.0, *;q=0"}, acceptEncoding: []string{"compress;q=1.0, *;q=0"},
expected: notAcceptable, expected: notAcceptable,
}, },
{ {
desc: "non-zero is higher than 0", desc: "non-zero is higher than 0",
values: []string{"gzip, *;q=0"}, acceptEncoding: []string{"gzip, *;q=0"},
expected: gzipName, expected: gzipName,
}, },
{
desc: "zstd forbidden, brotli first",
acceptEncoding: []string{"zstd, gzip, br"},
supportedEncodings: []string{brotliName, gzipName},
expected: brotliName,
},
{
desc: "follows weight, ignores forbidden encoding",
acceptEncoding: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"},
supportedEncodings: []string{zstdName, brotliName},
expected: brotliName,
},
} }
for _, test := range testCases { for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
encodingType := getCompressionType(test.values, test.defaultType) if test.supportedEncodings == nil {
test.supportedEncodings = defaultSupportedEncodings
}
assert.Equal(t, test.expected, encodingType) encoding := getCompressionEncoding(test.acceptEncoding, test.defaultEncoding, test.supportedEncodings)
assert.Equal(t, test.expected, encoding)
}) })
} }
} }
@ -91,6 +108,7 @@ func Test_parseAcceptEncoding(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
values []string values []string
supportedEncodings []string
expected []Encoding expected []Encoding
assertWeight assert.BoolAssertionFunc assertWeight assert.BoolAssertionFunc
}{ }{
@ -105,6 +123,17 @@ func Test_parseAcceptEncoding(t *testing.T) {
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
{
desc: "weight with supported encodings",
values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"},
supportedEncodings: []string{brotliName, gzipName},
expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)},
{Type: gzipName, Weight: ptr(0.8)},
{Type: wildcardName, Weight: ptr(0.1)},
},
assertWeight: assert.True,
},
{ {
desc: "mixed", desc: "mixed",
values: []string{"zstd,gzip, br;q=1.0, *;q=0"}, values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
@ -116,6 +145,16 @@ func Test_parseAcceptEncoding(t *testing.T) {
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
{
desc: "mixed with supported encodings",
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
supportedEncodings: []string{zstdName},
expected: []Encoding{
{Type: zstdName},
{Type: wildcardName, Weight: ptr[float64](0)},
},
assertWeight: assert.True,
},
{ {
desc: "no weight", desc: "no weight",
values: []string{"zstd, gzip, br, *"}, values: []string{"zstd, gzip, br, *"},
@ -127,6 +166,16 @@ func Test_parseAcceptEncoding(t *testing.T) {
}, },
assertWeight: assert.False, assertWeight: assert.False,
}, },
{
desc: "no weight with supported encodings",
values: []string{"zstd, gzip, br, *"},
supportedEncodings: []string{"gzip"},
expected: []Encoding{
{Type: gzipName},
{Type: wildcardName},
},
assertWeight: assert.False,
},
{ {
desc: "weight and identity", desc: "weight and identity",
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"}, values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
@ -137,13 +186,27 @@ func Test_parseAcceptEncoding(t *testing.T) {
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
{
desc: "weight and identity",
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
supportedEncodings: []string{"br"},
expected: []Encoding{
{Type: identityName, Weight: ptr(0.5)},
{Type: wildcardName, Weight: ptr[float64](0)},
},
assertWeight: assert.True,
},
} }
for _, test := range testCases { for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
aes, hasWeight := parseAcceptEncoding(test.values) if test.supportedEncodings == nil {
test.supportedEncodings = defaultSupportedEncodings
}
aes, hasWeight := parseAcceptEncoding(test.values, test.supportedEncodings)
assert.Equal(t, test.expected, aes) assert.Equal(t, test.expected, aes)
test.assertWeight(t, hasWeight) test.assertWeight(t, hasWeight)

View file

@ -16,9 +16,11 @@ import (
const typeName = "Compress" const typeName = "Compress"
// DefaultMinSize is the default minimum size (in bytes) required to enable compression. // defaultMinSize is the default minimum size (in bytes) required to enable compression.
// See https://github.com/klauspost/compress/blob/9559b037e79ad673c71f6ef7c732c00949014cd2/gzhttp/compress.go#L47. // See https://github.com/klauspost/compress/blob/9559b037e79ad673c71f6ef7c732c00949014cd2/gzhttp/compress.go#L47.
const DefaultMinSize = 1024 const defaultMinSize = 1024
var defaultSupportedEncodings = []string{zstdName, brotliName, gzipName}
// Compress is a middleware that allows to compress the response. // Compress is a middleware that allows to compress the response.
type compress struct { type compress struct {
@ -27,6 +29,7 @@ type compress struct {
excludes []string excludes []string
includes []string includes []string
minSize int minSize int
encodings []string
defaultEncoding string defaultEncoding string
brotliHandler http.Handler brotliHandler http.Handler
@ -62,17 +65,30 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str
includes = append(includes, mediaType) includes = append(includes, mediaType)
} }
minSize := DefaultMinSize minSize := defaultMinSize
if conf.MinResponseBodyBytes > 0 { if conf.MinResponseBodyBytes > 0 {
minSize = conf.MinResponseBodyBytes minSize = conf.MinResponseBodyBytes
} }
if len(conf.Encodings) == 0 {
return nil, errors.New("at least one encoding must be specified")
}
for _, encoding := range conf.Encodings {
if !slices.Contains(defaultSupportedEncodings, encoding) {
return nil, fmt.Errorf("unsupported encoding: %s", encoding)
}
}
if conf.DefaultEncoding != "" && !slices.Contains(conf.Encodings, conf.DefaultEncoding) {
return nil, fmt.Errorf("unsupported default encoding: %s", conf.DefaultEncoding)
}
c := &compress{ c := &compress{
next: next, next: next,
name: name, name: name,
excludes: excludes, excludes: excludes,
includes: includes, includes: includes,
minSize: minSize, minSize: minSize,
encodings: conf.Encodings,
defaultEncoding: conf.DefaultEncoding, defaultEncoding: conf.DefaultEncoding,
} }
@ -131,7 +147,7 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return return
} }
c.chooseHandler(getCompressionType(acceptEncoding, c.defaultEncoding), rw, req) c.chooseHandler(getCompressionEncoding(acceptEncoding, c.defaultEncoding, c.encodings), rw, req)
} }
func (c *compress) chooseHandler(typ string, rw http.ResponseWriter, req *http.Request) { func (c *compress) chooseHandler(typ string, rw http.ResponseWriter, req *http.Request) {

View file

@ -102,7 +102,11 @@ func TestNegotiation(t *testing.T) {
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
_, _ = rw.Write(generateBytes(10)) _, _ = rw.Write(generateBytes(10))
}) })
handler, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: 1}, "testing") cfg := dynamic.Compress{
MinResponseBodyBytes: 1,
Encodings: defaultSupportedEncodings,
}
handler, err := New(context.Background(), next, cfg, "testing")
require.NoError(t, err) require.NoError(t, err)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()
@ -123,7 +127,7 @@ func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) {
_, err := rw.Write(baseBody) _, err := rw.Write(baseBody)
assert.NoError(t, err) assert.NoError(t, err)
}) })
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err) require.NoError(t, err)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()
@ -153,7 +157,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
} }
}) })
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err) require.NoError(t, err)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()
@ -175,7 +179,7 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
} }
}) })
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err) require.NoError(t, err)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()
@ -202,7 +206,7 @@ func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
} }
}) })
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err) require.NoError(t, err)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()
@ -229,7 +233,7 @@ func TestShouldNotCompressWhenEmptyAcceptEncodingHeader(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
} }
}) })
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err) require.NoError(t, err)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()
@ -251,7 +255,7 @@ func TestShouldNotCompressHeadRequest(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
} }
}) })
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err) require.NoError(t, err)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()
@ -274,6 +278,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{ {
desc: "Exclude Request Content-Type", desc: "Exclude Request Content-Type",
conf: dynamic.Compress{ conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
ExcludedContentTypes: []string{"text/event-stream"}, ExcludedContentTypes: []string{"text/event-stream"},
}, },
reqContentType: "text/event-stream", reqContentType: "text/event-stream",
@ -281,6 +286,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{ {
desc: "Exclude Response Content-Type", desc: "Exclude Response Content-Type",
conf: dynamic.Compress{ conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
ExcludedContentTypes: []string{"text/event-stream"}, ExcludedContentTypes: []string{"text/event-stream"},
}, },
respContentType: "text/event-stream", respContentType: "text/event-stream",
@ -288,6 +294,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{ {
desc: "Include Response Content-Type", desc: "Include Response Content-Type",
conf: dynamic.Compress{ conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
IncludedContentTypes: []string{"text/plain"}, IncludedContentTypes: []string{"text/plain"},
}, },
respContentType: "text/html", respContentType: "text/html",
@ -295,6 +302,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{ {
desc: "Ignoring application/grpc with exclude option", desc: "Ignoring application/grpc with exclude option",
conf: dynamic.Compress{ conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
ExcludedContentTypes: []string{"application/json"}, ExcludedContentTypes: []string{"application/json"},
}, },
reqContentType: "application/grpc", reqContentType: "application/grpc",
@ -302,13 +310,16 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{ {
desc: "Ignoring application/grpc with include option", desc: "Ignoring application/grpc with include option",
conf: dynamic.Compress{ conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
IncludedContentTypes: []string{"application/json"}, IncludedContentTypes: []string{"application/json"},
}, },
reqContentType: "application/grpc", reqContentType: "application/grpc",
}, },
{ {
desc: "Ignoring application/grpc with no option", desc: "Ignoring application/grpc with no option",
conf: dynamic.Compress{}, conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
},
reqContentType: "application/grpc", reqContentType: "application/grpc",
}, },
} }
@ -358,6 +369,7 @@ func TestShouldCompressWhenSpecificContentType(t *testing.T) {
{ {
desc: "Include Response Content-Type", desc: "Include Response Content-Type",
conf: dynamic.Compress{ conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
IncludedContentTypes: []string{"text/html"}, IncludedContentTypes: []string{"text/html"},
}, },
respContentType: "text/html", respContentType: "text/html",
@ -429,7 +441,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
compress, err := New(context.Background(), test.handler, dynamic.Compress{}, "testing") compress, err := New(context.Background(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err) require.NoError(t, err)
ts := httptest.NewServer(compress) ts := httptest.NewServer(compress)
@ -464,7 +476,7 @@ func TestShouldWriteHeaderWhenFlush(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
} }
}) })
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err) require.NoError(t, err)
ts := httptest.NewServer(handler) ts := httptest.NewServer(handler)
@ -515,7 +527,7 @@ func TestIntegrationShouldCompress(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
compress, err := New(context.Background(), test.handler, dynamic.Compress{}, "testing") compress, err := New(context.Background(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err) require.NoError(t, err)
ts := httptest.NewServer(compress) ts := httptest.NewServer(compress)
@ -571,8 +583,11 @@ func TestMinResponseBodyBytes(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
} }
}) })
cfg := dynamic.Compress{
handler, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: test.minResponseBodyBytes}, "testing") MinResponseBodyBytes: test.minResponseBodyBytes,
Encodings: defaultSupportedEncodings,
}
handler, err := New(context.Background(), next, cfg, "testing")
require.NoError(t, err) require.NoError(t, err)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()
@ -607,8 +622,11 @@ func Test1xxResponses(t *testing.T) {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
}) })
cfg := dynamic.Compress{
compress, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: 1024}, "testing") MinResponseBodyBytes: 1024,
Encodings: defaultSupportedEncodings,
}
compress, err := New(context.Background(), next, cfg, "testing")
require.NoError(t, err) require.NoError(t, err)
server := httptest.NewServer(compress) server := httptest.NewServer(compress)

View file

@ -9,7 +9,7 @@ import (
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
const typeName = "RequestHeaderModifier" const requestHeaderModifierTypeName = "RequestHeaderModifier"
// requestHeaderModifier is a middleware used to modify the headers of an HTTP request. // requestHeaderModifier is a middleware used to modify the headers of an HTTP request.
type requestHeaderModifier struct { type requestHeaderModifier struct {
@ -22,8 +22,8 @@ type requestHeaderModifier struct {
} }
// NewRequestHeaderModifier creates a new request header modifier middleware. // NewRequestHeaderModifier creates a new request header modifier middleware.
func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dynamic.RequestHeaderModifier, name string) http.Handler { func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dynamic.HeaderModifier, name string) http.Handler {
logger := middlewares.GetLogger(ctx, name, typeName) logger := middlewares.GetLogger(ctx, name, requestHeaderModifierTypeName)
logger.Debug().Msg("Creating middleware") logger.Debug().Msg("Creating middleware")
return &requestHeaderModifier{ return &requestHeaderModifier{
@ -36,7 +36,7 @@ func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dyn
} }
func (r *requestHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) { func (r *requestHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) {
return r.name, typeName, trace.SpanKindUnspecified return r.name, requestHeaderModifierTypeName, trace.SpanKindUnspecified
} }
func (r *requestHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (r *requestHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -14,25 +14,25 @@ import (
func TestRequestHeaderModifier(t *testing.T) { func TestRequestHeaderModifier(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
config dynamic.RequestHeaderModifier config dynamic.HeaderModifier
requestHeaders http.Header requestHeaders http.Header
expectedHeaders http.Header expectedHeaders http.Header
}{ }{
{ {
desc: "no config", desc: "no config",
config: dynamic.RequestHeaderModifier{}, config: dynamic.HeaderModifier{},
expectedHeaders: map[string][]string{}, expectedHeaders: map[string][]string{},
}, },
{ {
desc: "set header", desc: "set header",
config: dynamic.RequestHeaderModifier{ config: dynamic.HeaderModifier{
Set: map[string]string{"Foo": "Bar"}, Set: map[string]string{"Foo": "Bar"},
}, },
expectedHeaders: map[string][]string{"Foo": {"Bar"}}, expectedHeaders: map[string][]string{"Foo": {"Bar"}},
}, },
{ {
desc: "set header with existing headers", desc: "set header with existing headers",
config: dynamic.RequestHeaderModifier{ config: dynamic.HeaderModifier{
Set: map[string]string{"Foo": "Bar"}, Set: map[string]string{"Foo": "Bar"},
}, },
requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}}, requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}},
@ -40,7 +40,7 @@ func TestRequestHeaderModifier(t *testing.T) {
}, },
{ {
desc: "set multiple headers with existing headers", desc: "set multiple headers with existing headers",
config: dynamic.RequestHeaderModifier{ config: dynamic.HeaderModifier{
Set: map[string]string{"Foo": "Bar", "Bar": "Foo"}, Set: map[string]string{"Foo": "Bar", "Bar": "Foo"},
}, },
requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foobar"}}, requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foobar"}},
@ -48,14 +48,14 @@ func TestRequestHeaderModifier(t *testing.T) {
}, },
{ {
desc: "add header", desc: "add header",
config: dynamic.RequestHeaderModifier{ config: dynamic.HeaderModifier{
Add: map[string]string{"Foo": "Bar"}, Add: map[string]string{"Foo": "Bar"},
}, },
expectedHeaders: map[string][]string{"Foo": {"Bar"}}, expectedHeaders: map[string][]string{"Foo": {"Bar"}},
}, },
{ {
desc: "add header with existing headers", desc: "add header with existing headers",
config: dynamic.RequestHeaderModifier{ config: dynamic.HeaderModifier{
Add: map[string]string{"Foo": "Bar"}, Add: map[string]string{"Foo": "Bar"},
}, },
requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}}, requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}},
@ -63,7 +63,7 @@ func TestRequestHeaderModifier(t *testing.T) {
}, },
{ {
desc: "add multiple headers with existing headers", desc: "add multiple headers with existing headers",
config: dynamic.RequestHeaderModifier{ config: dynamic.HeaderModifier{
Add: map[string]string{"Foo": "Bar", "Bar": "Foo"}, Add: map[string]string{"Foo": "Bar", "Bar": "Foo"},
}, },
requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foobar"}}, requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foobar"}},
@ -71,14 +71,14 @@ func TestRequestHeaderModifier(t *testing.T) {
}, },
{ {
desc: "remove header", desc: "remove header",
config: dynamic.RequestHeaderModifier{ config: dynamic.HeaderModifier{
Remove: []string{"Foo"}, Remove: []string{"Foo"},
}, },
expectedHeaders: map[string][]string{}, expectedHeaders: map[string][]string{},
}, },
{ {
desc: "remove header with existing headers", desc: "remove header with existing headers",
config: dynamic.RequestHeaderModifier{ config: dynamic.HeaderModifier{
Remove: []string{"Foo"}, Remove: []string{"Foo"},
}, },
requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}}, requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}},
@ -86,7 +86,7 @@ func TestRequestHeaderModifier(t *testing.T) {
}, },
{ {
desc: "remove multiple headers with existing headers", desc: "remove multiple headers with existing headers",
config: dynamic.RequestHeaderModifier{ config: dynamic.HeaderModifier{
Remove: []string{"Foo", "Bar"}, Remove: []string{"Foo", "Bar"},
}, },
requestHeaders: map[string][]string{"Foo": {"Bar"}, "Bar": {"Foo"}, "Baz": {"Bar"}}, requestHeaders: map[string][]string{"Foo": {"Bar"}, "Bar": {"Foo"}, "Baz": {"Bar"}},
@ -106,11 +106,11 @@ func TestRequestHeaderModifier(t *testing.T) {
handler := NewRequestHeaderModifier(context.Background(), next, test.config, "foo-request-header-modifier") handler := NewRequestHeaderModifier(context.Background(), next, test.config, "foo-request-header-modifier")
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
if test.requestHeaders != nil { for h, v := range test.requestHeaders {
req.Header = test.requestHeaders req.Header[h] = v
} }
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req) handler.ServeHTTP(resp, req)
assert.Equal(t, test.expectedHeaders, gotHeaders) assert.Equal(t, test.expectedHeaders, gotHeaders)

View file

@ -0,0 +1,60 @@
package headermodifier
import (
"context"
"net/http"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const responseHeaderModifierTypeName = "ResponseHeaderModifier"
// requestHeaderModifier is a middleware used to modify the headers of an HTTP response.
type responseHeaderModifier struct {
next http.Handler
name string
set map[string]string
add map[string]string
remove []string
}
// NewResponseHeaderModifier creates a new response header modifier middleware.
func NewResponseHeaderModifier(ctx context.Context, next http.Handler, config dynamic.HeaderModifier, name string) http.Handler {
logger := middlewares.GetLogger(ctx, name, responseHeaderModifierTypeName)
logger.Debug().Msg("Creating middleware")
return &responseHeaderModifier{
next: next,
name: name,
set: config.Set,
add: config.Add,
remove: config.Remove,
}
}
func (r *responseHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) {
return r.name, responseHeaderModifierTypeName, trace.SpanKindUnspecified
}
func (r *responseHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
r.next.ServeHTTP(middlewares.NewResponseModifier(rw, req, r.modifyResponseHeaders), req)
}
func (r *responseHeaderModifier) modifyResponseHeaders(res *http.Response) error {
for headerName, headerValue := range r.set {
res.Header.Set(headerName, headerValue)
}
for headerName, headerValue := range r.add {
res.Header.Add(headerName, headerValue)
}
for _, headerName := range r.remove {
res.Header.Del(headerName)
}
return nil
}

View file

@ -0,0 +1,121 @@
package headermodifier
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/testhelpers"
)
func TestResponseHeaderModifier(t *testing.T) {
testCases := []struct {
desc string
config dynamic.HeaderModifier
responseHeaders http.Header
expectedHeaders http.Header
}{
{
desc: "no config",
config: dynamic.HeaderModifier{},
expectedHeaders: map[string][]string{},
},
{
desc: "set header",
config: dynamic.HeaderModifier{
Set: map[string]string{"Foo": "Bar"},
},
expectedHeaders: map[string][]string{"Foo": {"Bar"}},
},
{
desc: "set header with existing headers",
config: dynamic.HeaderModifier{
Set: map[string]string{"Foo": "Bar"},
},
responseHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}},
expectedHeaders: map[string][]string{"Foo": {"Bar"}, "Bar": {"Foo"}},
},
{
desc: "set multiple headers with existing headers",
config: dynamic.HeaderModifier{
Set: map[string]string{"Foo": "Bar", "Bar": "Foo"},
},
responseHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foobar"}},
expectedHeaders: map[string][]string{"Foo": {"Bar"}, "Bar": {"Foo"}},
},
{
desc: "add header",
config: dynamic.HeaderModifier{
Add: map[string]string{"Foo": "Bar"},
},
expectedHeaders: map[string][]string{"Foo": {"Bar"}},
},
{
desc: "add header with existing headers",
config: dynamic.HeaderModifier{
Add: map[string]string{"Foo": "Bar"},
},
responseHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}},
expectedHeaders: map[string][]string{"Foo": {"Baz", "Bar"}, "Bar": {"Foo"}},
},
{
desc: "add multiple headers with existing headers",
config: dynamic.HeaderModifier{
Add: map[string]string{"Foo": "Bar", "Bar": "Foo"},
},
responseHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foobar"}},
expectedHeaders: map[string][]string{"Foo": {"Baz", "Bar"}, "Bar": {"Foobar", "Foo"}},
},
{
desc: "remove header",
config: dynamic.HeaderModifier{
Remove: []string{"Foo"},
},
expectedHeaders: map[string][]string{},
},
{
desc: "remove header with existing headers",
config: dynamic.HeaderModifier{
Remove: []string{"Foo"},
},
responseHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}},
expectedHeaders: map[string][]string{"Bar": {"Foo"}},
},
{
desc: "remove multiple headers with existing headers",
config: dynamic.HeaderModifier{
Remove: []string{"Foo", "Bar"},
},
responseHeaders: map[string][]string{"Foo": {"Bar"}, "Bar": {"Foo"}, "Baz": {"Bar"}},
expectedHeaders: map[string][]string{"Baz": {"Bar"}},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var nextCallCount int
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
nextCallCount++
rw.WriteHeader(http.StatusOK)
})
handler := NewResponseHeaderModifier(context.Background(), next, test.config, "foo-response-header-modifier")
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
resp := httptest.NewRecorder()
for k, v := range test.responseHeaders {
resp.Header()[k] = v
}
handler.ServeHTTP(resp, req)
assert.Equal(t, 1, nextCallCount)
assert.Equal(t, test.expectedHeaders, resp.Header())
})
}
}

View file

@ -13,9 +13,7 @@ import (
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
const ( const typeName = "RequestRedirect"
typeName = "RequestRedirect"
)
type redirect struct { type redirect struct {
name string name string

View file

@ -696,6 +696,8 @@ func getCertificateRenewDurations(certificatesDuration int) (time.Duration, time
return 4 * 30 * 24 * time.Hour, 7 * 24 * time.Hour // 4 month, 1 week return 4 * 30 * 24 * time.Hour, 7 * 24 * time.Hour // 4 month, 1 week
case certificatesDuration >= 3*30*24: // >= 90 days case certificatesDuration >= 3*30*24: // >= 90 days
return 30 * 24 * time.Hour, 24 * time.Hour // 30 days, 1 day return 30 * 24 * time.Hour, 24 * time.Hour // 30 days, 1 day
case certificatesDuration >= 30*24: // >= 30 days
return 10 * 24 * time.Hour, 12 * time.Hour // 10 days, 12 hours
case certificatesDuration >= 7*24: // >= 7 days case certificatesDuration >= 7*24: // >= 7 days
return 24 * time.Hour, time.Hour // 1 days, 1 hour return 24 * time.Hour, time.Hour // 1 days, 1 hour
case certificatesDuration >= 24: // >= 1 days case certificatesDuration >= 24: // >= 1 days

View file

@ -613,6 +613,12 @@ func Test_getCertificateRenewDurations(t *testing.T) {
expectRenewPeriod: time.Hour * 24 * 30, expectRenewPeriod: time.Hour * 24 * 30,
expectRenewInterval: time.Hour * 24, expectRenewInterval: time.Hour * 24,
}, },
{
desc: "30 Days certificates: 10 days renew period, 12 hour renew interval",
certificatesDurations: 24 * 30,
expectRenewPeriod: time.Hour * 24 * 10,
expectRenewInterval: time.Hour * 12,
},
{ {
desc: "7 Days certificates: 1 days renew period, 1 hour renew interval", desc: "7 Days certificates: 1 days renew period, 1 hour renew interval",
certificatesDurations: 24 * 7, certificatesDurations: 24 * 7,

View file

@ -1,22 +1,22 @@
package docker package docker
import ( import (
docker "github.com/docker/docker/api/types" dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
) )
func containerJSON(ops ...func(*docker.ContainerJSON)) docker.ContainerJSON { func containerJSON(ops ...func(*dockertypes.ContainerJSON)) dockertypes.ContainerJSON {
c := &docker.ContainerJSON{ c := &dockertypes.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{ ContainerJSONBase: &dockertypes.ContainerJSONBase{
Name: "fake", Name: "fake",
HostConfig: &container.HostConfig{}, HostConfig: &container.HostConfig{},
}, },
Config: &container.Config{}, Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{ NetworkSettings: &dockertypes.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{}, NetworkSettingsBase: dockertypes.NetworkSettingsBase{},
}, },
} }
@ -27,34 +27,34 @@ func containerJSON(ops ...func(*docker.ContainerJSON)) docker.ContainerJSON {
return *c return *c
} }
func name(name string) func(*docker.ContainerJSON) { func name(name string) func(*dockertypes.ContainerJSON) {
return func(c *docker.ContainerJSON) { return func(c *dockertypes.ContainerJSON) {
c.ContainerJSONBase.Name = name c.ContainerJSONBase.Name = name
} }
} }
func networkMode(mode string) func(*docker.ContainerJSON) { func networkMode(mode string) func(*dockertypes.ContainerJSON) {
return func(c *docker.ContainerJSON) { return func(c *dockertypes.ContainerJSON) {
c.ContainerJSONBase.HostConfig.NetworkMode = container.NetworkMode(mode) c.ContainerJSONBase.HostConfig.NetworkMode = container.NetworkMode(mode)
} }
} }
func nodeIP(ip string) func(*docker.ContainerJSON) { func nodeIP(ip string) func(*dockertypes.ContainerJSON) {
return func(c *docker.ContainerJSON) { return func(c *dockertypes.ContainerJSON) {
c.ContainerJSONBase.Node = &docker.ContainerNode{ c.ContainerJSONBase.Node = &dockertypes.ContainerNode{
IPAddress: ip, IPAddress: ip,
} }
} }
} }
func ports(portMap nat.PortMap) func(*docker.ContainerJSON) { func ports(portMap nat.PortMap) func(*dockertypes.ContainerJSON) {
return func(c *docker.ContainerJSON) { return func(c *dockertypes.ContainerJSON) {
c.NetworkSettings.NetworkSettingsBase.Ports = portMap c.NetworkSettings.NetworkSettingsBase.Ports = portMap
} }
} }
func withNetwork(name string, ops ...func(*network.EndpointSettings)) func(*docker.ContainerJSON) { func withNetwork(name string, ops ...func(*network.EndpointSettings)) func(*dockertypes.ContainerJSON) {
return func(c *docker.ContainerJSON) { return func(c *dockertypes.ContainerJSON) {
if c.NetworkSettings.Networks == nil { if c.NetworkSettings.Networks == nil {
c.NetworkSettings.Networks = map[string]*network.EndpointSettings{} c.NetworkSettings.Networks = map[string]*network.EndpointSettings{}
} }

View file

@ -7,6 +7,7 @@ import (
"time" "time"
docker "github.com/docker/docker/api/types" docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -3972,12 +3973,12 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
testCases := []struct { testCases := []struct {
service swarm.Service service swarm.Service
expected string expected string
networks map[string]*docker.NetworkResource networks map[string]*network.Summary
}{ }{
{ {
service: swarmService(withEndpointSpec(modeDNSSR)), service: swarmService(withEndpointSpec(modeDNSSR)),
expected: "", expected: "",
networks: map[string]*docker.NetworkResource{}, networks: map[string]*network.Summary{},
}, },
{ {
service: swarmService( service: swarmService(
@ -3985,7 +3986,7 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
withEndpoint(virtualIP("1", "10.11.12.13/24")), withEndpoint(virtualIP("1", "10.11.12.13/24")),
), ),
expected: "10.11.12.13", expected: "10.11.12.13",
networks: map[string]*docker.NetworkResource{ networks: map[string]*network.Summary{
"1": { "1": {
Name: "foo", Name: "foo",
}, },
@ -4003,7 +4004,7 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
), ),
), ),
expected: "10.11.12.99", expected: "10.11.12.99",
networks: map[string]*docker.NetworkResource{ networks: map[string]*network.Summary{
"1": { "1": {
Name: "foonet", Name: "foonet",
}, },

View file

@ -6,7 +6,7 @@ import (
"testing" "testing"
"time" "time"
dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -18,7 +18,7 @@ func TestListTasks(t *testing.T) {
tasks []swarm.Task tasks []swarm.Task
isGlobalSVC bool isGlobalSVC bool
expectedTasks []string expectedTasks []string
networks map[string]*dockertypes.NetworkResource networks map[string]*network.Summary
}{ }{
{ {
service: swarmService(serviceName("container")), service: swarmService(serviceName("container")),
@ -53,7 +53,7 @@ func TestListTasks(t *testing.T) {
"container.1", "container.1",
"container.4", "container.4",
}, },
networks: map[string]*dockertypes.NetworkResource{ networks: map[string]*network.Summary{
"1": { "1": {
Name: "foo", Name: "foo",
}, },
@ -91,7 +91,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
services []swarm.Service services []swarm.Service
tasks []swarm.Task tasks []swarm.Task
dockerVersion string dockerVersion string
networks []dockertypes.NetworkResource networks []network.Summary
expectedServices []string expectedServices []string
}{ }{
{ {
@ -117,7 +117,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
withEndpointSpec(modeDNSSR)), withEndpointSpec(modeDNSSR)),
}, },
dockerVersion: "1.30", dockerVersion: "1.30",
networks: []dockertypes.NetworkResource{}, networks: []network.Summary{},
expectedServices: []string{}, expectedServices: []string{},
}, },
{ {
@ -143,7 +143,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
withEndpointSpec(modeDNSSR)), withEndpointSpec(modeDNSSR)),
}, },
dockerVersion: "1.30", dockerVersion: "1.30",
networks: []dockertypes.NetworkResource{ networks: []network.Summary{
{ {
Name: "network_name", Name: "network_name",
ID: "yk6l57rfwizjzxxzftn4amaot", ID: "yk6l57rfwizjzxxzftn4amaot",
@ -198,7 +198,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
), ),
}, },
dockerVersion: "1.30", dockerVersion: "1.30",
networks: []dockertypes.NetworkResource{ networks: []network.Summary{
{ {
Name: "network_name", Name: "network_name",
ID: "yk6l57rfwizjzxxzftn4amaot", ID: "yk6l57rfwizjzxxzftn4amaot",
@ -255,7 +255,7 @@ func TestSwarmProvider_parseService_task(t *testing.T) {
tasks []swarm.Task tasks []swarm.Task
isGlobalSVC bool isGlobalSVC bool
expected map[string]dockerData expected map[string]dockerData
networks map[string]*dockertypes.NetworkResource networks map[string]*network.Summary
}{ }{
{ {
service: swarmService(serviceName("container")), service: swarmService(serviceName("container")),
@ -276,7 +276,7 @@ func TestSwarmProvider_parseService_task(t *testing.T) {
Name: "container.3", Name: "container.3",
}, },
}, },
networks: map[string]*dockertypes.NetworkResource{ networks: map[string]*network.Summary{
"1": { "1": {
Name: "foo", Name: "foo",
}, },
@ -301,7 +301,7 @@ func TestSwarmProvider_parseService_task(t *testing.T) {
Name: "container.id3", Name: "container.id3",
}, },
}, },
networks: map[string]*dockertypes.NetworkResource{ networks: map[string]*network.Summary{
"1": { "1": {
Name: "foo", Name: "foo",
}, },
@ -339,7 +339,7 @@ func TestSwarmProvider_parseService_task(t *testing.T) {
}, },
}, },
}, },
networks: map[string]*dockertypes.NetworkResource{ networks: map[string]*network.Summary{
"1": { "1": {
Name: "vlan", Name: "vlan",
}, },

View file

@ -2,6 +2,7 @@ package docker
import ( import (
"context" "context"
"encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"text/template" "text/template"
@ -101,6 +102,8 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData {
type ClientConfig struct { type ClientConfig struct {
apiVersion string apiVersion string
Username string `description:"Username for Basic HTTP authentication." json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty"`
Password string `description:"Password for Basic HTTP authentication." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty"`
Endpoint string `description:"Docker server endpoint. Can be a TCP or a Unix socket endpoint." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` Endpoint string `description:"Docker server endpoint. Can be a TCP or a Unix socket endpoint." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
TLS *types.ClientTLS `description:"Enable Docker TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` TLS *types.ClientTLS `description:"Enable Docker TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
HTTPClientTimeout ptypes.Duration `description:"Client timeout for HTTP connections." json:"httpClientTimeout,omitempty" toml:"httpClientTimeout,omitempty" yaml:"httpClientTimeout,omitempty" export:"true"` HTTPClientTimeout ptypes.Duration `description:"Client timeout for HTTP connections." json:"httpClientTimeout,omitempty" toml:"httpClientTimeout,omitempty" yaml:"httpClientTimeout,omitempty" export:"true"`
@ -115,6 +118,9 @@ func createClient(ctx context.Context, cfg ClientConfig) (*client.Client, error)
httpHeaders := map[string]string{ httpHeaders := map[string]string{
"User-Agent": "Traefik " + version.Version, "User-Agent": "Traefik " + version.Version,
} }
if cfg.Username != "" && cfg.Password != "" {
httpHeaders["Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(cfg.Username+":"+cfg.Password))
}
opts = append(opts, opts = append(opts,
client.WithHTTPHeaders(httpHeaders), client.WithHTTPHeaders(httpHeaders),

View file

@ -57,7 +57,9 @@ type clientWrapper struct {
csCrd traefikclientset.Interface csCrd traefikclientset.Interface
csKube kclientset.Interface csKube kclientset.Interface
factoryClusterScope kinformers.SharedInformerFactory clusterScopeFactory kinformers.SharedInformerFactory
disableClusterScopeInformer bool
factoriesCrd map[string]traefikinformers.SharedInformerFactory factoriesCrd map[string]traefikinformers.SharedInformerFactory
factoriesKube map[string]kinformers.SharedInformerFactory factoriesKube map[string]kinformers.SharedInformerFactory
factoriesSecret map[string]kinformers.SharedInformerFactory factoriesSecret map[string]kinformers.SharedInformerFactory
@ -237,18 +239,11 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
c.factoriesSecret[ns] = factorySecret c.factoriesSecret[ns] = factorySecret
} }
c.factoryClusterScope = kinformers.NewSharedInformerFactory(c.csKube, resyncPeriod)
_, err := c.factoryClusterScope.Core().V1().Nodes().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
for _, ns := range namespaces { for _, ns := range namespaces {
c.factoriesCrd[ns].Start(stopCh) c.factoriesCrd[ns].Start(stopCh)
c.factoriesKube[ns].Start(stopCh) c.factoriesKube[ns].Start(stopCh)
c.factoriesSecret[ns].Start(stopCh) c.factoriesSecret[ns].Start(stopCh)
} }
c.factoryClusterScope.Start(stopCh)
for _, ns := range namespaces { for _, ns := range namespaces {
for t, ok := range c.factoriesCrd[ns].WaitForCacheSync(stopCh) { for t, ok := range c.factoriesCrd[ns].WaitForCacheSync(stopCh) {
@ -270,11 +265,21 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
} }
} }
for t, ok := range c.factoryClusterScope.WaitForCacheSync(stopCh) { if !c.disableClusterScopeInformer {
c.clusterScopeFactory = kinformers.NewSharedInformerFactory(c.csKube, resyncPeriod)
_, err := c.clusterScopeFactory.Core().V1().Nodes().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
c.clusterScopeFactory.Start(stopCh)
for t, ok := range c.clusterScopeFactory.WaitForCacheSync(stopCh) {
if !ok { if !ok {
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", t.String()) return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", t.String())
} }
} }
}
return eventCh, nil return eventCh, nil
} }
@ -474,7 +479,7 @@ func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool,
} }
func (c *clientWrapper) GetNodes() ([]*corev1.Node, bool, error) { func (c *clientWrapper) GetNodes() ([]*corev1.Node, bool, error) {
nodes, err := c.factoryClusterScope.Core().V1().Nodes().Lister().List(labels.Everything()) nodes, err := c.clusterScopeFactory.Core().V1().Nodes().Lister().List(labels.Everything())
exist, err := translateNotFoundError(err) exist, err := translateNotFoundError(err)
return nodes, exist, err return nodes, exist, err
} }

View file

@ -61,6 +61,7 @@ type Provider struct {
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"` NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"`
DisableClusterScopeResources bool `description:"Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services)." json:"disableClusterScopeResources,omitempty" toml:"disableClusterScopeResources,omitempty" yaml:"disableClusterScopeResources,omitempty" export:"true"`
lastConfiguration safe.Safe lastConfiguration safe.Safe
@ -112,6 +113,7 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
} }
client.labelSelector = p.LabelSelector client.labelSelector = p.LabelSelector
client.disableClusterScopeInformer = p.DisableClusterScopeResources
return client, nil return client, nil
} }
@ -302,7 +304,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
InFlightReq: middleware.Spec.InFlightReq, InFlightReq: middleware.Spec.InFlightReq,
Buffering: middleware.Spec.Buffering, Buffering: middleware.Spec.Buffering,
CircuitBreaker: circuitBreaker, CircuitBreaker: circuitBreaker,
Compress: middleware.Spec.Compress, Compress: createCompressMiddleware(middleware.Spec.Compress),
PassTLSClientCert: middleware.Spec.PassTLSClientCert, PassTLSClientCert: middleware.Spec.PassTLSClientCert,
Retry: retry, Retry: retry,
ContentType: middleware.Spec.ContentType, ContentType: middleware.Spec.ContentType,
@ -656,14 +658,49 @@ func createCircuitBreakerMiddleware(circuitBreaker *traefikv1alpha1.CircuitBreak
return cb, nil return cb, nil
} }
func createCompressMiddleware(compress *traefikv1alpha1.Compress) *dynamic.Compress {
if compress == nil {
return nil
}
c := &dynamic.Compress{}
c.SetDefaults()
if compress.ExcludedContentTypes != nil {
c.ExcludedContentTypes = compress.ExcludedContentTypes
}
if compress.IncludedContentTypes != nil {
c.IncludedContentTypes = compress.IncludedContentTypes
}
if compress.MinResponseBodyBytes != nil {
c.MinResponseBodyBytes = *compress.MinResponseBodyBytes
}
if compress.Encodings != nil {
c.Encodings = compress.Encodings
}
if compress.DefaultEncoding != nil {
c.DefaultEncoding = *compress.DefaultEncoding
}
return c
}
func createRateLimitMiddleware(rateLimit *traefikv1alpha1.RateLimit) (*dynamic.RateLimit, error) { func createRateLimitMiddleware(rateLimit *traefikv1alpha1.RateLimit) (*dynamic.RateLimit, error) {
if rateLimit == nil { if rateLimit == nil {
return nil, nil return nil, nil
} }
rl := &dynamic.RateLimit{Average: rateLimit.Average} rl := &dynamic.RateLimit{}
rl.SetDefaults() rl.SetDefaults()
if rateLimit.Average != nil {
rl.Average = *rateLimit.Average
}
if rateLimit.Burst != nil { if rateLimit.Burst != nil {
rl.Burst = *rateLimit.Burst rl.Burst = *rateLimit.Burst
} }

View file

@ -55,7 +55,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
allowCrossNamespace: p.AllowCrossNamespace, allowCrossNamespace: p.AllowCrossNamespace,
allowExternalNameServices: p.AllowExternalNameServices, allowExternalNameServices: p.AllowExternalNameServices,
allowEmptyServices: p.AllowEmptyServices, allowEmptyServices: p.AllowEmptyServices,
NativeLBByDefault: p.NativeLBByDefault, nativeLBByDefault: p.NativeLBByDefault,
disableClusterScopeResources: p.DisableClusterScopeResources,
} }
for _, route := range ingressRoute.Spec.Routes { for _, route := range ingressRoute.Spec.Routes {
@ -203,7 +204,8 @@ type configBuilder struct {
allowCrossNamespace bool allowCrossNamespace bool
allowExternalNameServices bool allowExternalNameServices bool
allowEmptyServices bool allowEmptyServices bool
NativeLBByDefault bool nativeLBByDefault bool
disableClusterScopeResources bool
} }
// buildTraefikService creates the configuration for the traefik service defined in tService, // buildTraefikService creates the configuration for the traefik service defined in tService,
@ -428,7 +430,7 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, hostPort)}}, nil return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, hostPort)}}, nil
} }
nativeLB := c.NativeLBByDefault nativeLB := c.nativeLBByDefault
if svc.NativeLB != nil { if svc.NativeLB != nil {
nativeLB = *svc.NativeLB nativeLB = *svc.NativeLB
} }
@ -448,6 +450,10 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
var servers []dynamic.Server var servers []dynamic.Server
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB { if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
if c.disableClusterScopeResources {
return nil, errors.New("nodes lookup is disabled")
}
nodes, nodesExists, nodesErr := c.client.GetNodes() nodes, nodesExists, nodesErr := c.client.GetNodes()
if nodesErr != nil { if nodesErr != nil {
return nil, nodesErr return nil, nodesErr

View file

@ -239,6 +239,10 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
var servers []dynamic.TCPServer var servers []dynamic.TCPServer
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB { if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
if p.DisableClusterScopeResources {
return nil, errors.New("nodes lookup is disabled")
}
nodes, nodesExists, nodesErr := client.GetNodes() nodes, nodesExists, nodesErr := client.GetNodes()
if nodesErr != nil { if nodesErr != nil {
return nil, nodesErr return nil, nodesErr

View file

@ -7447,8 +7447,8 @@ func TestNativeLB(t *testing.T) {
func TestNodePortLB(t *testing.T) { func TestNodePortLB(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
ingressClass string
paths []string paths []string
disableClusterScope bool
expected *dynamic.Configuration expected *dynamic.Configuration
}{ }{
{ {
@ -7594,6 +7594,90 @@ func TestNodePortLB(t *testing.T) {
TLS: &dynamic.TLSConfiguration{}, TLS: &dynamic.TLSConfiguration{},
}, },
}, },
{
desc: "HTTP with node port LB, cluster scope resources disabled",
paths: []string{"services.yml", "with_node_port_lb.yml"},
disableClusterScope: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "TCP with native Service LB, cluster scope resources disabled",
paths: []string{"tcp/services.yml", "tcp/with_node_port_service_lb.yml"},
disableClusterScope: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-test.route-fdd3e9338e47a45efefc",
Rule: "HostSNI(`foo.com`)",
},
},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "UDP with native Service LB, cluster scope resources disabled",
paths: []string{"udp/services.yml", "udp/with_node_port_service_lb.yml"},
disableClusterScope: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"default-test.route-0": {
EntryPoints: []string{"foo"},
Service: "default-test.route-0",
},
},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
ServersTransports: map[string]*dynamic.TCPServersTransport{},
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
ServersTransports: map[string]*dynamic.ServersTransport{},
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
} }
for _, test := range testCases { for _, test := range testCases {
@ -7617,7 +7701,9 @@ func TestNodePortLB(t *testing.T) {
<-eventCh <-eventCh
} }
p := Provider{} p := Provider{
DisableClusterScopeResources: test.disableClusterScope,
}
conf := p.loadConfigurationFromCRD(context.Background(), client) conf := p.loadConfigurationFromCRD(context.Background(), client)
assert.Equal(t, test.expected, conf) assert.Equal(t, test.expected, conf)

View file

@ -123,6 +123,10 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1
var servers []dynamic.UDPServer var servers []dynamic.UDPServer
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB { if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
if p.DisableClusterScopeResources {
return nil, errors.New("nodes lookup is disabled")
}
nodes, nodesExists, nodesErr := client.GetNodes() nodes, nodesExists, nodesErr := client.GetNodes()
if nodesErr != nil { if nodesErr != nil {
return nil, nodesErr return nil, nodesErr

View file

@ -46,7 +46,7 @@ type MiddlewareSpec struct {
InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"` InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"`
Buffering *dynamic.Buffering `json:"buffering,omitempty"` Buffering *dynamic.Buffering `json:"buffering,omitempty"`
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"` CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
Compress *dynamic.Compress `json:"compress,omitempty"` Compress *Compress `json:"compress,omitempty"`
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"` PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
Retry *Retry `json:"retry,omitempty"` Retry *Retry `json:"retry,omitempty"`
ContentType *dynamic.ContentType `json:"contentType,omitempty"` ContentType *dynamic.ContentType `json:"contentType,omitempty"`
@ -188,7 +188,7 @@ type RateLimit struct {
// It defaults to 0, which means no rate limiting. // It defaults to 0, which means no rate limiting.
// The rate is actually defined by dividing Average by Period. So for a rate below 1req/s, // The rate is actually defined by dividing Average by Period. So for a rate below 1req/s,
// one needs to define a Period larger than a second. // one needs to define a Period larger than a second.
Average int64 `json:"average,omitempty"` Average *int64 `json:"average,omitempty"`
// Period, in combination with Average, defines the actual maximum rate, such as: // Period, in combination with Average, defines the actual maximum rate, such as:
// r = Average / Period. It defaults to a second. // r = Average / Period. It defaults to a second.
Period *intstr.IntOrString `json:"period,omitempty"` Period *intstr.IntOrString `json:"period,omitempty"`
@ -203,6 +203,26 @@ type RateLimit struct {
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// Compress holds the compress middleware configuration.
// This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression.
// More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
type Compress struct {
// ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
// `application/grpc` is always excluded.
ExcludedContentTypes []string `json:"excludedContentTypes,omitempty"`
// IncludedContentTypes defines the list of content types to compare the Content-Type header of the responses before compressing.
IncludedContentTypes []string `json:"includedContentTypes,omitempty"`
// MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed.
// Default: 1024.
MinResponseBodyBytes *int `json:"minResponseBodyBytes,omitempty"`
// Encodings defines the list of supported compression algorithms.
Encodings []string `json:"encodings,omitempty"`
// DefaultEncoding specifies the default encoding if the `Accept-Encoding` header is not in the request or contains a wildcard (`*`).
DefaultEncoding *string `json:"defaultEncoding,omitempty"`
}
// +k8s:deepcopy-gen=true
// Retry holds the retry middleware configuration. // Retry holds the retry middleware configuration.
// This middleware reissues requests a given number of times to a backend server if that server does not reply. // This middleware reissues requests a given number of times to a backend server if that server does not reply.
// As soon as the server answers, the middleware stops retrying, regardless of the response status. // As soon as the server answers, the middleware stops retrying, regardless of the response status.

View file

@ -164,6 +164,47 @@ func (in *ClientTLS) DeepCopy() *ClientTLS {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Compress) DeepCopyInto(out *Compress) {
*out = *in
if in.ExcludedContentTypes != nil {
in, out := &in.ExcludedContentTypes, &out.ExcludedContentTypes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.IncludedContentTypes != nil {
in, out := &in.IncludedContentTypes, &out.IncludedContentTypes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.MinResponseBodyBytes != nil {
in, out := &in.MinResponseBodyBytes, &out.MinResponseBodyBytes
*out = new(int)
**out = **in
}
if in.Encodings != nil {
in, out := &in.Encodings, &out.Encodings
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DefaultEncoding != nil {
in, out := &in.DefaultEncoding, &out.DefaultEncoding
*out = new(string)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Compress.
func (in *Compress) DeepCopy() *Compress {
if in == nil {
return nil
}
out := new(Compress)
in.DeepCopyInto(out)
return out
}
// 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 *DigestAuth) DeepCopyInto(out *DigestAuth) { func (in *DigestAuth) DeepCopyInto(out *DigestAuth) {
*out = *in *out = *in
@ -776,7 +817,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
} }
if in.Compress != nil { if in.Compress != nil {
in, out := &in.Compress, &out.Compress in, out := &in.Compress, &out.Compress
*out = new(dynamic.Compress) *out = new(Compress)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.PassTLSClientCert != nil { if in.PassTLSClientCert != nil {
@ -975,6 +1016,11 @@ func (in *ObjectReference) DeepCopy() *ObjectReference {
// 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 *RateLimit) DeepCopyInto(out *RateLimit) { func (in *RateLimit) DeepCopyInto(out *RateLimit) {
*out = *in *out = *in
if in.Average != nil {
in, out := &in.Average, &out.Average
*out = new(int64)
**out = **in
}
if in.Period != nil { if in.Period != nil {
in, out := &in.Period, &out.Period in, out := &in.Period, &out.Period
*out = new(intstr.IntOrString) *out = new(intstr.IntOrString)

View file

@ -684,32 +684,9 @@ func translateNotFoundError(err error) (bool, error) {
} }
func gatewayStatusEqual(statusA, statusB gatev1.GatewayStatus) bool { func gatewayStatusEqual(statusA, statusB gatev1.GatewayStatus) bool {
if len(statusA.Listeners) != len(statusB.Listeners) { return reflect.DeepEqual(statusA.Addresses, statusB.Addresses) &&
return false listenersStatusEqual(statusA.Listeners, statusB.Listeners) &&
} conditionsEqual(statusA.Conditions, statusB.Conditions)
if !conditionsEqual(statusA.Conditions, statusB.Conditions) {
return false
}
listenerMatches := 0
for _, newListener := range statusB.Listeners {
for _, oldListener := range statusA.Listeners {
if newListener.Name == oldListener.Name {
if !conditionsEqual(newListener.Conditions, oldListener.Conditions) {
return false
}
if newListener.AttachedRoutes != oldListener.AttachedRoutes {
return false
}
listenerMatches++
}
}
}
return listenerMatches == len(statusA.Listeners)
} }
func routeParentStatusesEqual(routeParentStatusesA, routeParentStatusesB []gatev1alpha2.RouteParentStatus) bool { func routeParentStatusesEqual(routeParentStatusesA, routeParentStatusesB []gatev1alpha2.RouteParentStatus) bool {
@ -737,15 +714,17 @@ func routeParentStatusesEqual(routeParentStatusesA, routeParentStatusesB []gatev
} }
func routeParentStatusEqual(sA, sB gatev1alpha2.RouteParentStatus) bool { func routeParentStatusEqual(sA, sB gatev1alpha2.RouteParentStatus) bool {
if !reflect.DeepEqual(sA.ParentRef, sB.ParentRef) { return sA.ControllerName == sB.ControllerName &&
return false reflect.DeepEqual(sA.ParentRef, sB.ParentRef) &&
} conditionsEqual(sA.Conditions, sB.Conditions)
}
if sA.ControllerName != sB.ControllerName { func listenersStatusEqual(listenerA, listenerB []gatev1.ListenerStatus) bool {
return false return slices.EqualFunc(listenerA, listenerB, func(lA gatev1.ListenerStatus, lB gatev1.ListenerStatus) bool {
} return lA.Name == lB.Name &&
lA.AttachedRoutes == lB.AttachedRoutes &&
return conditionsEqual(sA.Conditions, sB.Conditions) conditionsEqual(lA.Conditions, lB.Conditions)
})
} }
func conditionsEqual(conditionsA, conditionsB []metav1.Condition) bool { func conditionsEqual(conditionsA, conditionsB []metav1.Condition) bool {

View file

@ -0,0 +1,62 @@
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: http
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: http-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
hostnames:
- "example.org"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: whoami
port: 80
weight: 1
kind: Service
group: ""
filters:
- type: ResponseHeaderModifier
responseHeaderModifier:
set:
- name: X-Foo
value: Bar
add:
- name: X-Bar
value: Foo
remove:
- X-Baz

View file

@ -316,6 +316,9 @@ func (p *Provider) loadMiddlewares(conf *dynamic.Configuration, namespace, route
case gatev1.HTTPRouteFilterRequestHeaderModifier: case gatev1.HTTPRouteFilterRequestHeaderModifier:
middlewares[name] = createRequestHeaderModifier(filter.RequestHeaderModifier) middlewares[name] = createRequestHeaderModifier(filter.RequestHeaderModifier)
case gatev1.HTTPRouteFilterResponseHeaderModifier:
middlewares[name] = createResponseHeaderModifier(filter.ResponseHeaderModifier)
case gatev1.HTTPRouteFilterExtensionRef: case gatev1.HTTPRouteFilterExtensionRef:
name, middleware, err := p.loadHTTPRouteFilterExtensionRef(namespace, filter.ExtensionRef) name, middleware, err := p.loadHTTPRouteFilterExtensionRef(namespace, filter.ExtensionRef)
if err != nil { if err != nil {
@ -599,7 +602,29 @@ func createRequestHeaderModifier(filter *gatev1.HTTPHeaderFilter) *dynamic.Middl
} }
return &dynamic.Middleware{ return &dynamic.Middleware{
RequestHeaderModifier: &dynamic.RequestHeaderModifier{ RequestHeaderModifier: &dynamic.HeaderModifier{
Set: sets,
Add: adds,
Remove: filter.Remove,
},
}
}
// createResponseHeaderModifier does not enforce/check the configuration,
// as the spec indicates that either the webhook or CEL (since v1.0 GA Release) should enforce that.
func createResponseHeaderModifier(filter *gatev1.HTTPHeaderFilter) *dynamic.Middleware {
sets := map[string]string{}
for _, header := range filter.Set {
sets[string(header.Name)] = header.Value
}
adds := map[string]string{}
for _, header := range filter.Add {
adds[string(header.Name)] = header.Value
}
return &dynamic.Middleware{
ResponseHeaderModifier: &dynamic.HeaderModifier{
Set: sets, Set: sets,
Add: adds, Add: adds,
Remove: filter.Remove, Remove: filter.Remove,

View file

@ -1722,7 +1722,77 @@ func TestLoadHTTPRoutes(t *testing.T) {
}, },
Middlewares: map[string]*dynamic.Middleware{ Middlewares: map[string]*dynamic.Middleware{
"default-http-app-1-my-gateway-web-0-364ce6ec04c3d49b19c4-requestheadermodifier-0": { "default-http-app-1-my-gateway-web-0-364ce6ec04c3d49b19c4-requestheadermodifier-0": {
RequestHeaderModifier: &dynamic.RequestHeaderModifier{ RequestHeaderModifier: &dynamic.HeaderModifier{
Set: map[string]string{"X-Foo": "Bar"},
Add: map[string]string{"X-Bar": "Foo"},
Remove: []string{"X-Baz"},
},
},
},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-0-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-80",
Weight: ptr.To(1),
},
},
},
},
"default-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple HTTPRoute, response header modifier",
paths: []string{"services.yml", "httproute/filter_response_header_modifier.yml"},
entryPoints: map[string]Entrypoint{"web": {
Address: ":80",
}},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-http-app-1-my-gateway-web-0-364ce6ec04c3d49b19c4": {
EntryPoints: []string{"web"},
Service: "default-http-app-1-my-gateway-web-0-wrr",
Rule: "Host(`example.org`) && PathPrefix(`/`)",
Priority: 13,
RuleSyntax: "v3",
Middlewares: []string{"default-http-app-1-my-gateway-web-0-364ce6ec04c3d49b19c4-responseheadermodifier-0"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-http-app-1-my-gateway-web-0-364ce6ec04c3d49b19c4-responseheadermodifier-0": {
ResponseHeaderModifier: &dynamic.HeaderModifier{
Set: map[string]string{"X-Foo": "Bar"}, Set: map[string]string{"X-Foo": "Bar"},
Add: map[string]string{"X-Bar": "Foo"}, Add: map[string]string{"X-Bar": "Foo"},
Remove: []string{"X-Baz"}, Remove: []string{"X-Baz"},

View file

@ -49,14 +49,14 @@ type Client interface {
type clientWrapper struct { type clientWrapper struct {
clientset kclientset.Interface clientset kclientset.Interface
factoryClusterScope kinformers.SharedInformerFactory clusterScopeFactory kinformers.SharedInformerFactory
factoriesKube map[string]kinformers.SharedInformerFactory factoriesKube map[string]kinformers.SharedInformerFactory
factoriesSecret map[string]kinformers.SharedInformerFactory factoriesSecret map[string]kinformers.SharedInformerFactory
factoriesIngress map[string]kinformers.SharedInformerFactory factoriesIngress map[string]kinformers.SharedInformerFactory
clusterFactory kinformers.SharedInformerFactory
ingressLabelSelector string ingressLabelSelector string
isNamespaceAll bool isNamespaceAll bool
disableIngressClassInformer bool disableIngressClassInformer bool // Deprecated.
disableClusterScopeInformer bool
watchedNamespaces []string watchedNamespaces []string
serverVersion *version.Version serverVersion *version.Version
} }
@ -201,58 +201,52 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
c.factoriesSecret[ns] = factorySecret c.factoriesSecret[ns] = factorySecret
} }
c.factoryClusterScope = kinformers.NewSharedInformerFactory(c.clientset, resyncPeriod)
_, err = c.factoryClusterScope.Core().V1().Nodes().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
for _, ns := range namespaces { for _, ns := range namespaces {
c.factoriesIngress[ns].Start(stopCh) c.factoriesIngress[ns].Start(stopCh)
c.factoriesKube[ns].Start(stopCh) c.factoriesKube[ns].Start(stopCh)
c.factoriesSecret[ns].Start(stopCh) c.factoriesSecret[ns].Start(stopCh)
} }
c.factoryClusterScope.Start(stopCh)
for _, ns := range namespaces { for _, ns := range namespaces {
for typ, ok := range c.factoriesIngress[ns].WaitForCacheSync(stopCh) { for t, ok := range c.factoriesIngress[ns].WaitForCacheSync(stopCh) {
if !ok { if !ok {
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", typ, ns) return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns)
} }
} }
for typ, ok := range c.factoriesKube[ns].WaitForCacheSync(stopCh) { for t, ok := range c.factoriesKube[ns].WaitForCacheSync(stopCh) {
if !ok { if !ok {
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", typ, ns) return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns)
} }
} }
for typ, ok := range c.factoriesSecret[ns].WaitForCacheSync(stopCh) { for t, ok := range c.factoriesSecret[ns].WaitForCacheSync(stopCh) {
if !ok { if !ok {
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", typ, ns) return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns)
} }
} }
} }
for t, ok := range c.factoryClusterScope.WaitForCacheSync(stopCh) { if !c.disableIngressClassInformer || !c.disableClusterScopeInformer {
if !ok { c.clusterScopeFactory = kinformers.NewSharedInformerFactory(c.clientset, resyncPeriod)
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", t.String())
}
}
if !c.disableIngressClassInformer { _, err = c.clusterScopeFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler)
c.clusterFactory = kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod)
_, err = c.clusterFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler)
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.clusterFactory.Start(stopCh) if !c.disableClusterScopeInformer {
_, err = c.clusterScopeFactory.Core().V1().Nodes().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
}
for typ, ok := range c.clusterFactory.WaitForCacheSync(stopCh) { c.clusterScopeFactory.Start(stopCh)
for t, ok := range c.clusterScopeFactory.WaitForCacheSync(stopCh) {
if !ok { if !ok {
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", typ) return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", t.String())
} }
} }
} }
@ -370,18 +364,18 @@ func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool,
} }
func (c *clientWrapper) GetNodes() ([]*corev1.Node, bool, error) { func (c *clientWrapper) GetNodes() ([]*corev1.Node, bool, error) {
nodes, err := c.factoryClusterScope.Core().V1().Nodes().Lister().List(labels.Everything()) nodes, err := c.clusterScopeFactory.Core().V1().Nodes().Lister().List(labels.Everything())
exist, err := translateNotFoundError(err) exist, err := translateNotFoundError(err)
return nodes, exist, err return nodes, exist, err
} }
func (c *clientWrapper) GetIngressClasses() ([]*netv1.IngressClass, error) { func (c *clientWrapper) GetIngressClasses() ([]*netv1.IngressClass, error) {
if c.clusterFactory == nil { if c.clusterScopeFactory == nil {
return nil, errors.New("cluster factory not loaded") return nil, errors.New("cluster factory not loaded")
} }
var ics []*netv1.IngressClass var ics []*netv1.IngressClass
ingressClasses, err := c.clusterFactory.Networking().V1().IngressClasses().Lister().List(labels.Everything()) ingressClasses, err := c.clusterScopeFactory.Networking().V1().IngressClasses().Lister().List(labels.Everything())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -0,0 +1,45 @@
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: ""
namespace: testing
spec:
rules:
- host: traefik.tchouk
http:
paths:
- path: /bar
backend:
service:
name: service1
port:
number: 8080
pathType: Prefix
---
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
annotations:
traefik.ingress.kubernetes.io/service.nodeportlb: "true"
spec:
ports:
- port: 8080
nodePort: 32456
clusterIP: 10.0.0.1
type: NodePort
externalName: traefik.wtf
---
kind: Node
apiVersion: v1
metadata:
name: traefik-node
status:
addresses:
- type: InternalIP
address: 172.16.4.4

View file

@ -51,7 +51,9 @@ type Provider struct {
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"` AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"`
DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"` // Deprecated: please use DisableClusterScopeResources.
DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses (Deprecated, please use DisableClusterScopeResources)." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"`
DisableClusterScopeResources bool `description:"Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services)." json:"disableClusterScopeResources,omitempty" toml:"disableClusterScopeResources,omitempty" yaml:"disableClusterScopeResources,omitempty" export:"true"`
NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"` NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"`
lastConfiguration safe.Safe lastConfiguration safe.Safe
@ -114,7 +116,8 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
} }
cl.ingressLabelSelector = p.LabelSelector cl.ingressLabelSelector = p.LabelSelector
cl.disableIngressClassInformer = p.DisableIngressClassLookup cl.disableIngressClassInformer = p.DisableIngressClassLookup || p.DisableClusterScopeResources
cl.disableClusterScopeInformer = p.DisableClusterScopeResources
return cl, nil return cl, nil
} }
@ -212,7 +215,6 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{}, Services: map[string]*dynamic.Service{},
}, },
TCP: &dynamic.TCPConfiguration{},
} }
var ingressClasses []*netv1.IngressClass var ingressClasses []*netv1.IngressClass
@ -591,6 +593,10 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
} }
if svcConfig.Service.NodePortLB && service.Spec.Type == corev1.ServiceTypeNodePort { if svcConfig.Service.NodePortLB && service.Spec.Type == corev1.ServiceTypeNodePort {
if p.DisableClusterScopeResources {
return nil, errors.New("nodes lookup is disabled")
}
nodes, nodesExists, nodesErr := client.GetNodes() nodes, nodesExists, nodesErr := client.GetNodes()
if nodesErr != nil { if nodesErr != nil {
return nil, nodesErr return nil, nodesErr

View file

@ -35,7 +35,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Empty ingresses", desc: "Empty ingresses",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
@ -46,7 +45,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress one rule host only", desc: "Ingress one rule host only",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
@ -57,7 +55,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with a basic rule on one path", desc: "Ingress with a basic rule on one path",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -90,7 +87,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with annotations", desc: "Ingress with annotations",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -148,7 +144,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with two different rules with one path", desc: "Ingress with two different rules with one path",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -185,7 +180,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with conflicting routers on host", desc: "Ingress with conflicting routers on host",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -222,7 +216,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with conflicting routers on path", desc: "Ingress with conflicting routers on path",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -259,7 +252,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress one rule with two paths", desc: "Ingress one rule with two paths",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -296,7 +288,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress one rule with one path and one host", desc: "Ingress one rule with one path and one host",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -329,7 +320,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with one host without path", desc: "Ingress with one host without path",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -359,7 +349,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress one rule with one host and two paths", desc: "Ingress one rule with one host and two paths",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -396,7 +385,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress Two rules with one host and one path", desc: "Ingress Two rules with one host and one path",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -433,7 +421,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with two services", desc: "Ingress with two services",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -487,7 +474,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
desc: "Ingress with one service without endpoints subset", desc: "Ingress with one service without endpoints subset",
allowEmptyServices: true, allowEmptyServices: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -512,7 +498,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with one service without endpoint", desc: "Ingress with one service without endpoint",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -523,7 +508,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Single Service Ingress (without any rules)", desc: "Single Service Ingress (without any rules)",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -558,7 +542,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with port value in backend and no pod replica", desc: "Ingress with port value in backend and no pod replica",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -591,7 +574,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with port name in backend and no pod replica", desc: "Ingress with port name in backend and no pod replica",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -624,7 +606,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with port name in backend and 2 pod replica", desc: "Ingress with port name in backend and 2 pod replica",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -657,7 +638,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with two paths using same service and different port name", desc: "Ingress with two paths using same service and different port name",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -710,7 +690,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with a named port matching subset of service pods", desc: "Ingress with a named port matching subset of service pods",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -743,7 +722,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "2 ingresses in different namespace with same service name", desc: "2 ingresses in different namespace with same service name",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -796,7 +774,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with unknown service port name", desc: "Ingress with unknown service port name",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -807,7 +784,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with unknown service port", desc: "Ingress with unknown service port",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -818,7 +794,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with port invalid for one service", desc: "Ingress with port invalid for one service",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -848,7 +823,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "TLS support", desc: "TLS support",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -889,7 +863,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with a basic rule on one path with https (port == 443)", desc: "Ingress with a basic rule on one path with https (port == 443)",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -922,7 +895,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with a basic rule on one path with https (portname == https)", desc: "Ingress with a basic rule on one path with https (portname == https)",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -955,7 +927,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with a basic rule on one path with https (portname starts with https)", desc: "Ingress with a basic rule on one path with https (portname starts with https)",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
@ -989,7 +960,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Double Single Service Ingress", desc: "Double Single Service Ingress",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1024,7 +994,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with default traefik ingressClass", desc: "Ingress with default traefik ingressClass",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1054,7 +1023,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress without provider traefik ingressClass and unknown annotation", desc: "Ingress without provider traefik ingressClass and unknown annotation",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -1066,7 +1034,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
desc: "Ingress with non matching provider traefik ingressClass and annotation", desc: "Ingress with non matching provider traefik ingressClass and annotation",
ingressClass: "tchouk", ingressClass: "tchouk",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -1078,7 +1045,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
desc: "Ingress with ingressClass without annotation", desc: "Ingress with ingressClass without annotation",
ingressClass: "tchouk", ingressClass: "tchouk",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -1090,7 +1056,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
desc: "Ingress with ingressClass without annotation", desc: "Ingress with ingressClass without annotation",
ingressClass: "toto", ingressClass: "toto",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -1101,7 +1066,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with wildcard host", desc: "Ingress with wildcard host",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1133,7 +1097,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with multiple ingressClasses", desc: "Ingress with multiple ingressClasses",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1168,7 +1131,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
desc: "Ingress with ingressClasses filter", desc: "Ingress with ingressClasses filter",
ingressClass: "traefik-lb2", ingressClass: "traefik-lb2",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1198,7 +1160,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with prefix pathType", desc: "Ingress with prefix pathType",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1228,7 +1189,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with empty pathType", desc: "Ingress with empty pathType",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1258,7 +1218,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with exact pathType", desc: "Ingress with exact pathType",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1288,7 +1247,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with implementationSpecific pathType", desc: "Ingress with implementationSpecific pathType",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1318,7 +1276,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with ingress annotation", desc: "Ingress with ingress annotation",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1351,7 +1308,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
desc: "Ingress with ingress annotation", desc: "Ingress with ingress annotation",
disableIngressClassLookup: true, disableIngressClassLookup: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1381,7 +1337,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with ingressClass", desc: "Ingress with ingressClass",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1414,7 +1369,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
desc: "Ingress with ingressClass", desc: "Ingress with ingressClass",
disableIngressClassLookup: true, disableIngressClassLookup: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -1425,7 +1379,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with named port", desc: "Ingress with named port",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1455,7 +1408,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with missing ingressClass", desc: "Ingress with missing ingressClass",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -1466,7 +1418,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
{ {
desc: "Ingress with defaultbackend", desc: "Ingress with defaultbackend",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1524,7 +1475,6 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
{ {
desc: "Ingress with service with externalName", desc: "Ingress with service with externalName",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},
@ -1536,7 +1486,6 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
desc: "Ingress with service with externalName enabled", desc: "Ingress with service with externalName enabled",
allowExternalNameServices: true, allowExternalNameServices: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1566,7 +1515,6 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
{ {
desc: "Ingress with IPv6 endpoints", desc: "Ingress with IPv6 endpoints",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1597,7 +1545,6 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
desc: "Ingress with IPv6 endpoints externalname enabled", desc: "Ingress with IPv6 endpoints externalname enabled",
allowExternalNameServices: true, allowExternalNameServices: true,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1650,7 +1597,6 @@ func TestLoadConfigurationFromIngressesWithNativeLB(t *testing.T) {
{ {
desc: "Ingress with native service lb", desc: "Ingress with native service lb",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1694,13 +1640,12 @@ func TestLoadConfigurationFromIngressesWithNativeLB(t *testing.T) {
func TestLoadConfigurationFromIngressesWithNodePortLB(t *testing.T) { func TestLoadConfigurationFromIngressesWithNodePortLB(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
ingressClass string clusterScopeDisabled bool
expected *dynamic.Configuration expected *dynamic.Configuration
}{ }{
{ {
desc: "Ingress with node port lb", desc: "Ingress with node port lb",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1725,6 +1670,17 @@ func TestLoadConfigurationFromIngressesWithNodePortLB(t *testing.T) {
}, },
}, },
}, },
{
desc: "Ingress with node port lb cluster scope disabled",
clusterScopeDisabled: true,
expected: &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{},
Services: map[string]*dynamic.Service{},
},
},
},
} }
for _, test := range testCases { for _, test := range testCases {
@ -1733,7 +1689,7 @@ func TestLoadConfigurationFromIngressesWithNodePortLB(t *testing.T) {
clientMock := newClientMock(generateTestFilename(test.desc)) clientMock := newClientMock(generateTestFilename(test.desc))
p := Provider{IngressClass: test.ingressClass} p := Provider{DisableClusterScopeResources: test.clusterScopeDisabled}
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
assert.Equal(t, test.expected, conf) assert.Equal(t, test.expected, conf)
@ -1927,7 +1883,6 @@ func TestLoadConfigurationFromIngressesWithNativeLBByDefault(t *testing.T) {
{ {
desc: "Ingress with native service lb", desc: "Ingress with native service lb",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -1955,7 +1910,6 @@ func TestLoadConfigurationFromIngressesWithNativeLBByDefault(t *testing.T) {
{ {
desc: "Ingress with native lb by default", desc: "Ingress with native lb by default",
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{

View file

@ -207,6 +207,7 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/http/middlewares/Middleware02/buffering/retryExpression": "foobar", "traefik/http/middlewares/Middleware02/buffering/retryExpression": "foobar",
"traefik/http/middlewares/Middleware02/buffering/maxRequestBodyBytes": "42", "traefik/http/middlewares/Middleware02/buffering/maxRequestBodyBytes": "42",
"traefik/http/middlewares/Middleware02/buffering/memRequestBodyBytes": "42", "traefik/http/middlewares/Middleware02/buffering/memRequestBodyBytes": "42",
"traefik/http/middlewares/Middleware05/compress/encodings": "foobar, foobar",
"traefik/http/middlewares/Middleware05/compress/minResponseBodyBytes": "42", "traefik/http/middlewares/Middleware05/compress/minResponseBodyBytes": "42",
"traefik/http/middlewares/Middleware18/retry/attempts": "42", "traefik/http/middlewares/Middleware18/retry/attempts": "42",
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0": "foobar", "traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0": "foobar",
@ -412,6 +413,10 @@ func Test_buildConfiguration(t *testing.T) {
"Middleware05": { "Middleware05": {
Compress: &dynamic.Compress{ Compress: &dynamic.Compress{
MinResponseBodyBytes: 42, MinResponseBodyBytes: 42,
Encodings: []string{
"foobar",
"foobar",
},
}, },
}, },
"Middleware08": { "Middleware08": {

View file

@ -397,6 +397,15 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
} }
} }
if config.ResponseHeaderModifier != nil {
if middleware != nil {
return nil, badConf
}
middleware = func(next http.Handler) (http.Handler, error) {
return headermodifier.NewResponseHeaderModifier(ctx, next, *config.ResponseHeaderModifier, middlewareName), nil
}
}
if config.RequestRedirect != nil { if config.RequestRedirect != nil {
if middleware != nil { if middleware != nil {
return nil, badConf return nil, badConf

View file

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

View file

@ -7,7 +7,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"transfer": "node dev/scripts/transfer.js", "transfer": "node dev/scripts/transfer.js",
"lint": "eslint --ext .js,.vue src", "lint": "eslint src/**/*.{js,vue}",
"dev": "APP_ENV=development quasar dev", "dev": "APP_ENV=development quasar dev",
"build-quasar": "quasar build", "build-quasar": "quasar build",
"build-staging": "NODE_ENV=production APP_ENV=development yarn build-quasar", "build-staging": "NODE_ENV=production APP_ENV=development yarn build-quasar",
@ -18,8 +18,8 @@
"test:unit:ci": "vitest run" "test:unit:ci": "vitest run"
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "^1.16.9", "@quasar/extras": "^1.16.12",
"axios": "^1.6.7", "axios": "^1.7.2",
"bowser": "^2.11.0", "bowser": "^2.11.0",
"chart.js": "^4.4.1", "chart.js": "^4.4.1",
"core-js": "^3.35.1", "core-js": "^3.35.1",
@ -27,7 +27,7 @@
"iframe-resizer": "^4.3.9", "iframe-resizer": "^4.3.9",
"lodash.isequal": "4.5.0", "lodash.isequal": "4.5.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"quasar": "^2.14.3", "quasar": "^2.16.6",
"query-string": "^8.1.0", "query-string": "^8.1.0",
"vh-check": "^2.0.5", "vh-check": "^2.0.5",
"vue": "^3.0.0", "vue": "^3.0.0",
@ -39,8 +39,8 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.9", "@babel/core": "^7.23.9",
"@babel/eslint-parser": "^7.23.10", "@babel/eslint-parser": "^7.23.10",
"@quasar/app-vite": "^1.4.3", "@quasar/app-vite": "^2.0.0-beta.15",
"@quasar/babel-preset-app": "^2.0.2", "@quasar/babel-preset-app": "^2.0.3",
"@quasar/quasar-app-extension-testing-unit-vitest": "^1.0.0", "@quasar/quasar-app-extension-testing-unit-vitest": "^1.0.0",
"@vue/test-utils": "^2.4.4", "@vue/test-utils": "^2.4.4",
"autoprefixer": "^10.4.2", "autoprefixer": "^10.4.2",
@ -51,7 +51,7 @@
"eslint-plugin-promise": "^6.0.0", "eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^9.0.0", "eslint-plugin-vue": "^9.0.0",
"postcss": "^8.4.14", "postcss": "^8.4.14",
"vitest": "^1.3.1" "vitest": "^1.6.0"
}, },
"engines": { "engines": {
"node": "^20 || ^18 || ^16", "node": "^20 || ^18 || ^16",

File diff suppressed because it is too large Load diff