Compare commits
38 commits
5482418b84
...
64e14d47a7
Author | SHA1 | Date | |
---|---|---|---|
64e14d47a7 | |||
|
a696f7c654 | ||
|
3ca667a3d4 | ||
|
27af1fb478 | ||
|
e322184a98 | ||
|
69424a16a5 | ||
|
f9f22b7b70 | ||
|
b795f128d7 | ||
|
6706bb1612 | ||
|
3f48e6f8ef | ||
|
8ea339816a | ||
|
00b1d8b0bc | ||
|
21c6edcf58 | ||
|
5c48e3c96c | ||
|
c23c3e0ed3 | ||
|
b37aaea36d | ||
|
67f0700377 | ||
|
778dc22e14 | ||
|
cdf0c8b3ec | ||
|
359477c583 | ||
|
28d40e7f3c | ||
|
b368e71337 | ||
|
dc752c7847 | ||
|
6155c900be | ||
|
6ca4c5da5c | ||
|
7eac92f49c | ||
|
e6b1b05fdf | ||
|
b452f37e08 | ||
|
8cff718c53 | ||
|
bfda5e607f | ||
|
7fc56454ea | ||
|
c0a2e6b4b6 | ||
|
0f0cc420e1 | ||
|
9250b5937d | ||
|
4406c337d4 | ||
|
ed10bc5833 | ||
|
e33bd6874f | ||
|
05828bab07 |
135 changed files with 6399 additions and 2771 deletions
|
@ -155,23 +155,16 @@ linters-settings:
|
|||
checks:
|
||||
- all
|
||||
- -SA1019
|
||||
errcheck:
|
||||
exclude-functions:
|
||||
- fmt.Fprintln
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- deadcode # deprecated
|
||||
- exhaustivestruct # deprecated
|
||||
- golint # deprecated
|
||||
- ifshort # deprecated
|
||||
- interfacer # deprecated
|
||||
- maligned # deprecated
|
||||
- nosnakecase # deprecated
|
||||
- scopelint # deprecated
|
||||
- scopelint # deprecated
|
||||
- structcheck # deprecated
|
||||
- varcheck # deprecated
|
||||
- execinquery # deprecated
|
||||
- gomnd # deprecated
|
||||
- sqlclosecheck # not relevant (SQL)
|
||||
- rowserrcheck # not relevant (SQL)
|
||||
- execinquery # not relevant (SQL)
|
||||
- cyclop # duplicate of gocyclo
|
||||
- lll # Not relevant
|
||||
- gocyclo # FIXME must be fixed
|
||||
|
@ -185,14 +178,14 @@ linters:
|
|||
- gochecknoglobals
|
||||
- wsl # Too strict
|
||||
- nlreturn # Not relevant
|
||||
- gomnd # Too strict
|
||||
- mnd # Too strict
|
||||
- stylecheck # skip because report issues related to some generated files.
|
||||
- testpackage # Too strict
|
||||
- tparallel # Not relevant
|
||||
- paralleltest # Not relevant
|
||||
- exhaustive # Not relevant
|
||||
- exhaustruct # Not relevant
|
||||
- goerr113 # Too strict
|
||||
- err113 # Too strict
|
||||
- wrapcheck # Too strict
|
||||
- noctx # Too strict
|
||||
- bodyclose # too many false-positive
|
||||
|
@ -208,7 +201,6 @@ linters:
|
|||
- gosmopolitan # not relevant
|
||||
- exportloopref # Useless with go1.22
|
||||
- musttag
|
||||
- intrange # bug (fixed in golangci-lint v1.58)
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
|
@ -226,6 +218,8 @@ issues:
|
|||
- goconst
|
||||
- funlen
|
||||
- godot
|
||||
- canonicalheader
|
||||
- fatcontext
|
||||
- path: '(.+)_test.go'
|
||||
text: ' always receives '
|
||||
linters:
|
||||
|
|
|
@ -25,7 +25,7 @@ global_job_config:
|
|||
- export "PATH=${GOPATH}/bin:${PATH}"
|
||||
- mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin"
|
||||
- export GOPROXY=https://proxy.golang.org,direct
|
||||
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.57.0
|
||||
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.59.0
|
||||
- curl -sSfL https://gist.githubusercontent.com/traefiker/6d7ac019c11d011e4f131bb2cca8900e/raw/goreleaser.sh | bash -s -- -b "${GOPATH}/bin"
|
||||
- checkout
|
||||
- cache restore traefik-$(checksum go.sum)
|
||||
|
|
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -1,3 +1,32 @@
|
|||
## [v3.0.2](https://github.com/traefik/traefik/tree/v3.0.2) (2024-06-10)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.0.1...v3.0.2)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[logs]** Bump OTel dependencies ([#10763](https://github.com/traefik/traefik/pull/10763) by [DrFaust92](https://github.com/DrFaust92))
|
||||
- **[logs]** Append to log file if it exists ([#10756](https://github.com/traefik/traefik/pull/10756) by [lbenguigui](https://github.com/lbenguigui))
|
||||
- **[metrics]** Fix service name label_replace in Grafana ([#10758](https://github.com/traefik/traefik/pull/10758) by [xdavidwu](https://github.com/xdavidwu))
|
||||
- **[middleware]** Forward the correct status code when compression is disabled within the Brotli handler ([#10780](https://github.com/traefik/traefik/pull/10780) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[middleware]** Support Accept-Encoding header weights with Compress middleware ([#10777](https://github.com/traefik/traefik/pull/10777) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- Update v2 > v3 migration guide ([#10728](https://github.com/traefik/traefik/pull/10728) by [0anas01](https://github.com/0anas01))
|
||||
|
||||
**Misc:**
|
||||
- Merge current v2.11 into v3.0 ([#10796](https://github.com/traefik/traefik/pull/10796) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- Merge current v2.11 into v3.0 ([#10781](https://github.com/traefik/traefik/pull/10781) by [ldez](https://github.com/ldez))
|
||||
|
||||
## [v2.11.4](https://github.com/traefik/traefik/tree/v2.11.4) (2024-06-10)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.11.3...v2.11.4)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to v4.17.3 ([#10768](https://github.com/traefik/traefik/pull/10768) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** Fix .com and .org domain examples ([#10635](https://github.com/traefik/traefik/pull/10635) by [rptaylor](https://github.com/rptaylor))
|
||||
- **[middleware]** Add a note about the Ratelimit middleware's behavior when the sourceCriterion header is missing ([#10752](https://github.com/traefik/traefik/pull/10752) by [dgutzmann](https://github.com/dgutzmann))
|
||||
- Add user guides link to getting started ([#10785](https://github.com/traefik/traefik/pull/10785) by [norlinhenrik](https://github.com/norlinhenrik))
|
||||
- Remove helm default repo warning as repo has been long deprecated ([#10772](https://github.com/traefik/traefik/pull/10772) by [corneliusroemer](https://github.com/corneliusroemer))
|
||||
|
||||
## [v3.0.1](https://github.com/traefik/traefik/tree/v3.0.1) (2024-05-22)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.0.0...v3.0.1)
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# syntax=docker/dockerfile:1.2
|
||||
FROM alpine:3.19
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk --no-cache --no-progress add ca-certificates tzdata \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
RUN apk add --no-cache --no-progress ca-certificates tzdata
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
COPY ./dist/$TARGETPLATFORM/traefik /
|
||||
|
|
9
Makefile
9
Makefile
|
@ -88,7 +88,7 @@ crossbinary-default: generate generate-webui
|
|||
|
||||
.PHONY: test
|
||||
#? test: Run the unit and integration tests
|
||||
test: test-unit test-integration
|
||||
test: test-ui-unit test-unit test-integration
|
||||
|
||||
.PHONY: test-unit
|
||||
#? test-unit: Run the unit tests
|
||||
|
@ -105,6 +105,13 @@ test-integration: binary
|
|||
test-gateway-api-conformance: build-image-dirty
|
||||
GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -test.run K8sConformanceSuite -k8sConformance $(TESTFLAGS)
|
||||
|
||||
.PHONY: test-ui-unit
|
||||
#? test-ui-unit: Run the unit tests for the webui
|
||||
test-ui-unit:
|
||||
$(MAKE) build-webui-image
|
||||
docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui yarn --cwd webui install
|
||||
docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui yarn --cwd webui test:unit:ci
|
||||
|
||||
.PHONY: pull-images
|
||||
#? pull-images: Pull all Docker images to avoid timeout during integration tests
|
||||
pull-images:
|
||||
|
|
|
@ -49,7 +49,7 @@ func getLogWriter(staticConfiguration *static.Configuration) io.Writer {
|
|||
var w io.Writer = os.Stderr
|
||||
|
||||
if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 {
|
||||
_, _ = os.Create(staticConfiguration.Log.FilePath)
|
||||
_, _ = os.OpenFile(staticConfiguration.Log.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)
|
||||
w = &lumberjack.Logger{
|
||||
Filename: staticConfiguration.Log.FilePath,
|
||||
MaxSize: staticConfiguration.Log.MaxSize,
|
||||
|
|
|
@ -507,7 +507,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "topk(15,\n label_replace(\n traefik_service_request_duration_seconds_sum{service=~\"$service.*\",protocol=\"http\"} / \n traefik_service_request_duration_seconds_count{service=~\"$service.*\",protocol=\"http\"},\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")\n)\n\n",
|
||||
"expr": "topk(15,\n label_replace(\n traefik_service_request_duration_seconds_sum{service=~\"$service.*\",protocol=\"http\"} / \n traefik_service_request_duration_seconds_count{service=~\"$service.*\",protocol=\"http\"},\n \"service\", \"$1\", \"service\", \"([^@]+)@.*\")\n)\n\n",
|
||||
"legendFormat": "{{method}}[{{code}}] on {{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -606,7 +606,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"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\"}[5m])) > 0,\n \"service\", \"$1\", \"service\", \"([^@]+)@.*\")\n)",
|
||||
"legendFormat": "[{{code}}] on {{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -711,7 +711,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"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.*\"}[5m])) / sum by (service) \n (rate(traefik_service_request_duration_seconds_count{service=~\"$service.*\"}[5m]))\n ) > 0,\n \"service\", \"$1\", \"service\", \"([^@]+)@.*\"\n)",
|
||||
"legendFormat": "{{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -806,7 +806,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"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.*\"}[5m])) / sum by (service) \n (rate(traefik_service_request_duration_seconds_count{service=~\"$service.*\"}[5m]))\n ) > 0,\n \"service\", \"$1\", \"service\", \"([^@]+)@.*\"\n)",
|
||||
"legendFormat": "{{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -922,7 +922,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"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\"}[5m])) > 0,\n \"service\", \"$1\", \"service\", \"([^@]+)@.*\")\n)",
|
||||
"legendFormat": "{{method}}[{{code}}] on {{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -1022,7 +1022,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"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\"}[5m])) > 0,\n \"service\", \"$1\", \"service\", \"([^@]+)@.*\")\n)",
|
||||
"legendFormat": "{{method}}[{{code}}] on {{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -1122,7 +1122,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"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\"}[5m])) > 0,\n \"service\", \"$1\", \"service\", \"([^@]+)@.*\")\n)",
|
||||
"legendFormat": "{{method}}[{{code}}] on {{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -1222,7 +1222,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"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\"}[1m])) > 0,\n \"service\", \"$1\", \"service\", \"([^@]+)@.*\")\n)",
|
||||
"legendFormat": "{{method}} on {{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -1322,7 +1322,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"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\"}[1m])) > 0,\n \"service\", \"$1\", \"service\", \"([^@]+)@.*\")\n)",
|
||||
"legendFormat": "{{method}} on {{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -1331,105 +1331,6 @@
|
|||
"title": "Responses Size",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 39
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"mean",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "right",
|
||||
"showLegend": true,
|
||||
"sortBy": "Max",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "label_replace(\n sum(traefik_service_open_connections{service=~\"$service.*\"}) by (service),\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")",
|
||||
"legendFormat": "{{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Connections per Service",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
|
@ -1520,7 +1421,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(traefik_entrypoint_open_connections{entrypoint=~\"$entrypoint\"}) by (entrypoint)\n",
|
||||
"expr": "sum(traefik_open_connections{entrypoint=~\"$entrypoint\"}) by (entrypoint)\n",
|
||||
"legendFormat": "{{entrypoint}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -1560,14 +1461,14 @@
|
|||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(traefik_entrypoint_open_connections, entrypoint)",
|
||||
"definition": "label_values(traefik_open_connections, entrypoint)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": false,
|
||||
"name": "entrypoint",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(traefik_entrypoint_open_connections, entrypoint)",
|
||||
"query": "label_values(traefik_open_connections, entrypoint)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
|
@ -1582,18 +1483,18 @@
|
|||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(traefik_service_open_connections, service)",
|
||||
"definition": "label_values(traefik_service_requests_total, service)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": false,
|
||||
"name": "service",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(traefik_service_open_connections, service)",
|
||||
"query": "label_values(traefik_service_requests_total, service)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 2,
|
||||
"regex": "/([^-]+-[^-]+).*/",
|
||||
"regex": "/([^@]+)@.*/",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"type": "query"
|
||||
|
@ -1608,6 +1509,6 @@
|
|||
"timezone": "",
|
||||
"title": "Traefik Official Kubernetes Dashboard",
|
||||
"uid": "n5bu_kv4k",
|
||||
"version": 6,
|
||||
"version": 7,
|
||||
"weekStart": ""
|
||||
}
|
||||
|
|
|
@ -1321,104 +1321,6 @@
|
|||
"title": "Responses Size",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 39
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"mean",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "right",
|
||||
"showLegend": true,
|
||||
"sortBy": "Max",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "label_replace(\n sum(traefik_service_open_connections{service=~\"$service.*\"}) by (service),\n \"service\", \"$1\", \"service\", \"([^-]+-[^-]+).*\")",
|
||||
"legendFormat": "{{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Connections per Service",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
|
@ -1508,7 +1410,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(traefik_entrypoint_open_connections{entrypoint=~\"$entrypoint\"}) by (entrypoint)\n",
|
||||
"expr": "sum(traefik_open_connections{entrypoint=~\"$entrypoint\"}) by (entrypoint)\n",
|
||||
"legendFormat": "{{entrypoint}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
|
@ -1552,14 +1454,14 @@
|
|||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(traefik_entrypoint_open_connections, entrypoint)",
|
||||
"definition": "label_values(traefik_open_connections, entrypoint)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": false,
|
||||
"name": "entrypoint",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(traefik_entrypoint_open_connections, entrypoint)",
|
||||
"query": "label_values(traefik_open_connections, entrypoint)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
|
@ -1574,14 +1476,14 @@
|
|||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(traefik_service_open_connections, service)",
|
||||
"definition": "label_values(traefik_service_requests_total, service)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": false,
|
||||
"name": "service",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(traefik_service_open_connections, service)",
|
||||
"query": "label_values(traefik_service_requests_total, service)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 2,
|
||||
|
@ -1600,6 +1502,6 @@
|
|||
"timezone": "",
|
||||
"title": "Traefik Official Standalone Dashboard",
|
||||
"uid": "n5bu_kv45",
|
||||
"version": 6,
|
||||
"version": 7,
|
||||
"weekStart": ""
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM alpine:3.18 as alpine
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk --no-cache --no-progress add \
|
||||
build-base \
|
||||
|
|
|
@ -4,30 +4,26 @@
|
|||
|
||||
Below is a non-exhaustive list of versions and their maintenance status:
|
||||
|
||||
| Version | Release Date | Active Support | Security Support |
|
||||
|---------|--------------|--------------------|------------------|
|
||||
| 2.11 | Feb 12, 2024 | Yes | Yes |
|
||||
| 2.10 | Apr 24, 2023 | Ended Feb 12, 2024 | No |
|
||||
| 2.9 | Oct 03, 2022 | Ended Apr 24, 2023 | No |
|
||||
| 2.8 | Jun 29, 2022 | Ended Oct 03, 2022 | No |
|
||||
| 2.7 | May 24, 2022 | Ended Jun 29, 2022 | No |
|
||||
| 2.6 | Jan 24, 2022 | Ended May 24, 2022 | No |
|
||||
| 2.5 | Aug 17, 2021 | Ended Jan 24, 2022 | No |
|
||||
| 2.4 | Jan 19, 2021 | Ended Aug 17, 2021 | No |
|
||||
| 2.3 | Sep 23, 2020 | Ended Jan 19, 2021 | No |
|
||||
| 2.2 | Mar 25, 2020 | Ended Sep 23, 2020 | No |
|
||||
| 2.1 | Dec 11, 2019 | Ended Mar 25, 2020 | No |
|
||||
| 2.0 | Sep 16, 2019 | Ended Dec 11, 2019 | No |
|
||||
| 1.7 | Sep 24, 2018 | Ended Dec 31, 2021 | Contact Support |
|
||||
|
||||
??? example "Active Support / Security Support"
|
||||
|
||||
**Active support**: receives any bug fixes.
|
||||
**Security support**: receives only critical bug and security fixes.
|
||||
| Version | Release Date | Community Support |
|
||||
|---------|--------------|--------------------|
|
||||
| 3.0 | Apr 29, 2024 | Yes |
|
||||
| 2.11 | Feb 12, 2024 | Ends Apr 29, 2025 |
|
||||
| 2.10 | Apr 24, 2023 | Ended Feb 12, 2024 |
|
||||
| 2.9 | Oct 03, 2022 | Ended Apr 24, 2023 |
|
||||
| 2.8 | Jun 29, 2022 | Ended Oct 03, 2022 |
|
||||
| 2.7 | May 24, 2022 | Ended Jun 29, 2022 |
|
||||
| 2.6 | Jan 24, 2022 | Ended May 24, 2022 |
|
||||
| 2.5 | Aug 17, 2021 | Ended Jan 24, 2022 |
|
||||
| 2.4 | Jan 19, 2021 | Ended Aug 17, 2021 |
|
||||
| 2.3 | Sep 23, 2020 | Ended Jan 19, 2021 |
|
||||
| 2.2 | Mar 25, 2020 | Ended Sep 23, 2020 |
|
||||
| 2.1 | Dec 11, 2019 | Ended Mar 25, 2020 |
|
||||
| 2.0 | Sep 16, 2019 | Ended Dec 11, 2019 |
|
||||
| 1.7 | Sep 24, 2018 | Ended Dec 31, 2021 |
|
||||
|
||||
This page is maintained and updated periodically to reflect our roadmap and any decisions affecting the end of support for Traefik Proxy.
|
||||
|
||||
Please refer to our migration guides for specific instructions on upgrading between versions, an example is the [v1 to v2 migration guide](../migration/v1-to-v2.md).
|
||||
Please refer to our migration guides for specific instructions on upgrading between versions, an example is the [v2 to v3 migration guide](../migration/v2-to-v3.md).
|
||||
|
||||
!!! important "All target dates for end of support or feature removal announcements may be subject to change."
|
||||
|
||||
|
|
|
@ -35,11 +35,6 @@ For more details, go to the [Docker provider documentation](../providers/docker.
|
|||
|
||||
## Use the Helm Chart
|
||||
|
||||
!!! warning
|
||||
|
||||
The Traefik Chart from
|
||||
[Helm's default charts repository](https://github.com/helm/charts/tree/master/stable/traefik) is still using [Traefik v1.7](https://doc.traefik.io/traefik/v1.7).
|
||||
|
||||
Traefik can be installed in Kubernetes using the Helm chart from <https://github.com/traefik/traefik-helm-chart>.
|
||||
|
||||
Ensure that the following requirements are met:
|
||||
|
|
|
@ -119,6 +119,6 @@ IP: 172.27.0.4
|
|||
|
||||
!!! question "Where to Go Next?"
|
||||
|
||||
Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it is time to dive into [the documentation](/ "Link to the docs landing page") and let Traefik work for you!
|
||||
Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it is time to dive into [the user guides](../../user-guides/docker-compose/basic-example/ "Link to the user guides") and [the documentation](/ "Link to the docs landing page") and let Traefik work for you!
|
||||
|
||||
{!traefik-for-business-applications.md!}
|
||||
|
|
|
@ -406,7 +406,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
|||
| [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/otc) |
|
||||
| [Openstack Designate](https://docs.openstack.org/designate) | `designate` | `OS_AUTH_URL`, `OS_USERNAME`, `OS_PASSWORD`, `OS_TENANT_NAME`, `OS_REGION_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/designate) |
|
||||
| [Oracle Cloud](https://cloud.oracle.com/home) | `oraclecloud` | `OCI_COMPARTMENT_OCID`, `OCI_PRIVKEY_FILE`, `OCI_PRIVKEY_PASS`, `OCI_PUBKEY_FINGERPRINT`, `OCI_REGION`, `OCI_TENANCY_OCID`, `OCI_USER_OCID` | [Additional configuration](https://go-acme.github.io/lego/dns/oraclecloud) |
|
||||
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ovh) |
|
||||
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY`, `OVH_CLIENT_ID`, `OVH_CLIENT_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/ovh) |
|
||||
| [Plesk](https://www.plesk.com) | `plesk` | `PLESK_SERVER_BASE_URL`, `PLESK_USERNAME`, `PLESK_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/plesk) |
|
||||
| [Porkbun](https://porkbun.com/) | `porkbun` | `PORKBUN_SECRET_API_KEY`, `PORKBUN_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/porkbun) |
|
||||
| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/pdns) |
|
||||
|
@ -417,8 +417,9 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
|||
| [RimuHosting](https://rimuhosting.com) | `rimuhosting` | `RIMUHOSTING_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/rimuhosting) |
|
||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | [Additional configuration](https://go-acme.github.io/lego/dns/route53) |
|
||||
| [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/sakuracloud) |
|
||||
| [Scaleway](https://www.scaleway.com) | `scaleway` | `SCALEWAY_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/scaleway) |
|
||||
| [Scaleway](https://www.scaleway.com) | `scaleway` | `SCW_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/scaleway) |
|
||||
| [Selectel](https://selectel.ru/en/) | `selectel` | `SELECTEL_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/selectel) |
|
||||
| [Selectel v2](https://selectel.ru/en/) | `selectelv2` | `SELECTELV2_ACCOUNT_ID`, `SELECTELV2_PASSWORD`, `SELECTELV2_PROJECT_ID`, `SELECTELV2_USERNAME` | [Additional configuration](https://go-acme.github.io/lego/dns/selectelv2) |
|
||||
| [Servercow](https://servercow.de) | `servercow` | `SERVERCOW_USERNAME`, `SERVERCOW_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/servercow) |
|
||||
| [Shellrent](https://www.shellrent.com) | `shellrent` | `SHELLRENT_USERNAME`, `SHELLRENT_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/shellrent) |
|
||||
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
|
||||
|
|
|
@ -5,22 +5,10 @@ labels:
|
|||
- traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`)
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=myresolver
|
||||
- traefik.http.routers.blog.tls.domains[0].main=example.org
|
||||
- traefik.http.routers.blog.tls.domains[0].main=example.com
|
||||
- traefik.http.routers.blog.tls.domains[0].sans=*.example.org
|
||||
```
|
||||
|
||||
```yaml tab="Docker (Swarm)"
|
||||
## Dynamic configuration
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`)
|
||||
- traefik.http.services.blog-svc.loadbalancer.server.port=8080"
|
||||
- traefik.http.routers.blog.tls=true
|
||||
- traefik.http.routers.blog.tls.certresolver=myresolver
|
||||
- traefik.http.routers.blog.tls.domains[0].main=example.org
|
||||
- traefik.http.routers.blog.tls.domains[0].sans=*.example.org
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
|
@ -38,7 +26,7 @@ spec:
|
|||
tls:
|
||||
certResolver: myresolver
|
||||
domains:
|
||||
- main: example.org
|
||||
- main: example.com
|
||||
sans:
|
||||
- '*.example.org'
|
||||
```
|
||||
|
@ -52,7 +40,7 @@ http:
|
|||
tls:
|
||||
certResolver: myresolver
|
||||
domains:
|
||||
- main: "example.org"
|
||||
- main: "example.com"
|
||||
sans:
|
||||
- "*.example.org"
|
||||
```
|
||||
|
@ -65,6 +53,6 @@ http:
|
|||
[http.routers.blog.tls]
|
||||
certResolver = "myresolver" # From static configuration
|
||||
[[http.routers.blog.tls.domains]]
|
||||
main = "example.org"
|
||||
main = "example.com"
|
||||
sans = ["*.example.org"]
|
||||
```
|
||||
|
|
|
@ -10,7 +10,7 @@ Compress Allows Compressing Responses before Sending them to the Client
|
|||
|
||||
![Compress](../../assets/img/middleware/compress.png)
|
||||
|
||||
The Compress middleware supports gzip and Brotli compression.
|
||||
The Compress middleware supports Gzip, Brotli and Zstandard compression.
|
||||
The activation of compression, and the compression method choice rely (among other things) on the request's `Accept-Encoding` header.
|
||||
|
||||
## Configuration Examples
|
||||
|
@ -54,8 +54,8 @@ http:
|
|||
|
||||
Responses are compressed when the following criteria are all met:
|
||||
|
||||
* The `Accept-Encoding` request header contains `gzip`, `*`, and/or `br` with or without [quality values](https://developer.mozilla.org/en-US/docs/Glossary/Quality_values).
|
||||
If the `Accept-Encoding` request header is absent, the response won't be encoded.
|
||||
* The `Accept-Encoding` request header contains `gzip`, and/or `*`, and/or `br`, and/or `zstd` with or without [quality values](https://developer.mozilla.org/en-US/docs/Glossary/Quality_values).
|
||||
If the `Accept-Encoding` request header is absent and no [defaultEncoding](#defaultencoding) is configured, the response won't be encoded.
|
||||
If it is present, but its value is the empty string, then compression is disabled.
|
||||
* The response is not already compressed, i.e. the `Content-Encoding` response header is not already set.
|
||||
* The response`Content-Type` header is not one among the [excludedContentTypes options](#excludedcontenttypes), or is one among the [includedContentTypes options](#includedcontenttypes).
|
||||
|
@ -214,3 +214,44 @@ http:
|
|||
[http.middlewares.test-compress.compress]
|
||||
minResponseBodyBytes = 1200
|
||||
```
|
||||
|
||||
### `defaultEncoding`
|
||||
|
||||
_Optional, Default=""_
|
||||
|
||||
`defaultEncoding` specifies the default encoding if the `Accept-Encoding` header is not in the request or contains a wildcard (`*`).
|
||||
|
||||
There is no fallback on the `defaultEncoding` when the header value is empty or unsupported.
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-compress.compress.defaultEncoding=gzip"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-compress
|
||||
spec:
|
||||
compress:
|
||||
defaultEncoding: gzip
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.middlewares.test-compress.compress.defaultEncoding=gzip"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
middlewares:
|
||||
test-compress:
|
||||
compress:
|
||||
defaultEncoding: gzip
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.middlewares]
|
||||
[http.middlewares.test-compress.compress]
|
||||
defaultEncoding = "gzip"
|
||||
```
|
||||
|
|
|
@ -394,6 +394,10 @@ This overrides the `BrowserXssFilter` option.
|
|||
|
||||
The `contentSecurityPolicy` option allows the `Content-Security-Policy` header value to be set with a custom value.
|
||||
|
||||
### `contentSecurityPolicyReportOnly`
|
||||
|
||||
The `contentSecurityPolicyReportOnly` option allows the `Content-Security-Policy-Report-Only` header value to be set with a custom value.
|
||||
|
||||
### `publicKey`
|
||||
|
||||
The `publicKey` implements HPKP to prevent MITM attacks with forged certificates.
|
||||
|
|
|
@ -359,6 +359,8 @@ http:
|
|||
|
||||
Name of the header used to group incoming requests.
|
||||
|
||||
!!! important "If the header is not present, rate limiting will still be applied, but all requests without the specified header will be grouped together."
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername=username"
|
||||
|
|
723
docs/content/migration/v2-to-v3-details.md
Normal file
723
docs/content/migration/v2-to-v3-details.md
Normal file
|
@ -0,0 +1,723 @@
|
|||
---
|
||||
title: "Traefik V3 Migration Details"
|
||||
description: "Configuration changes and their details to successfully migrate from Traefik v2 to v3."
|
||||
---
|
||||
|
||||
# Configuration Details for Migrating from Traefik v2 to v3
|
||||
|
||||
## Static Configuration Changes
|
||||
|
||||
### SwarmMode
|
||||
|
||||
In v3, the provider Docker has been split into 2 providers:
|
||||
|
||||
- Docker provider (without Swarm support)
|
||||
- Swarm provider (Swarm support only)
|
||||
|
||||
??? example "An example usage of v2 Docker provider with Swarm"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
swarmMode: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker]
|
||||
swarmMode=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.swarmMode=true
|
||||
```
|
||||
|
||||
This configuration is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
|
||||
In v3, the `swarmMode` should not be used with the Docker provider, and, to use Swarm, the Swarm provider should be used instead.
|
||||
|
||||
??? example "An example usage of the Swarm provider"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
endpoint: "tcp://127.0.0.1:2377"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
endpoint="tcp://127.0.0.1:2377"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.endpoint=tcp://127.0.0.1:2377
|
||||
```
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
Docker provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the Docker provider static configuration.
|
||||
|
||||
### Kubernetes Gateway API
|
||||
|
||||
#### Experimental Channel Resources (TLSRoute and TCPRoute)
|
||||
|
||||
In v3, the Kubernetes Gateway API provider does not enable support for the experimental channel API resources by default.
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `experimentalChannel` option should be used to enable the support for the experimental channel API resources.
|
||||
|
||||
??? example "An example usage of the Kubernetes Gateway API provider with experimental channel support enabled"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
kubernetesGateway:
|
||||
experimentalChannel: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.kubernetesGateway]
|
||||
experimentalChannel = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesgateway.experimentalchannel=true
|
||||
```
|
||||
|
||||
### Experimental Configuration
|
||||
|
||||
#### HTTP3
|
||||
|
||||
In v3, HTTP/3 is no longer an experimental feature.
|
||||
It can be enabled on entry points without the associated `experimental.http3` option, which is now removed.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
??? example "An example usage of v2 Experimental `http3` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
experimental:
|
||||
http3: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[experimental]
|
||||
http3=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--experimental.http3=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `http3` option should be removed from the static configuration experimental section.
|
||||
To configure `http3`, please checkout the [entrypoint configuration documentation](https://doc.traefik.io/traefik/v3.0/routing/entrypoints/#http3_1).
|
||||
|
||||
### Consul provider
|
||||
|
||||
#### namespace
|
||||
|
||||
The Consul provider `namespace` option was deprecated in v2 and is now removed in v3.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
??? example "An example usage of v2 Consul `namespace` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
consul:
|
||||
namespace: foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[consul]
|
||||
namespace=foobar
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--consul.namespace=foobar
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
In v3, the `namespaces` option should be used instead of the `namespace` option.
|
||||
|
||||
??? example "An example usage of Consul `namespaces` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
consul:
|
||||
namespaces:
|
||||
- foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[consul]
|
||||
namespaces=["foobar"]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--consul.namespaces=foobar
|
||||
```
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
Consul provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
consul:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consul.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.consul.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the Consul provider static configuration.
|
||||
|
||||
### ConsulCatalog provider
|
||||
|
||||
#### namespace
|
||||
|
||||
The ConsulCatalog provider `namespace` option was deprecated in v2 and is now removed in v3.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
??? example "An example usage of v2 ConsulCatalog `namespace` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
consulCatalog:
|
||||
namespace: foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[consulCatalog]
|
||||
namespace=foobar
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--consulCatalog.namespace=foobar
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
In v3, the `namespaces` option should be used instead of the `namespace` option.
|
||||
|
||||
??? example "An example usage of ConsulCatalog `namespaces` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
consulCatalog:
|
||||
namespaces:
|
||||
- foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[consulCatalog]
|
||||
namespaces=["foobar"]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--consulCatalog.namespaces=foobar
|
||||
```
|
||||
|
||||
#### Endpoint.TLS.CAOptional
|
||||
|
||||
ConsulCatalog provider `endpoint.tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the Endpoint.TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
consulCatalog:
|
||||
endpoint:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consulCatalog.endpoint.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.consulCatalog.endpoint.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `endpoint.tls.caOptional` option should be removed from the ConsulCatalog provider static configuration.
|
||||
|
||||
### Nomad provider
|
||||
|
||||
#### namespace
|
||||
|
||||
The Nomad provider `namespace` option was deprecated in v2 and is now removed in v3.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
??? example "An example usage of v2 Nomad `namespace` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
nomad:
|
||||
namespace: foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[nomad]
|
||||
namespace=foobar
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--nomad.namespace=foobar
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
In v3, the `namespaces` option should be used instead of the `namespace` option.
|
||||
|
||||
??? example "An example usage of Nomad `namespaces` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
nomad:
|
||||
namespaces:
|
||||
- foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[nomad]
|
||||
namespaces=["foobar"]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--nomad.namespaces=foobar
|
||||
```
|
||||
|
||||
#### Endpoint.TLS.CAOptional
|
||||
|
||||
Nomad provider `endpoint.tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the Endpoint.TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
nomad:
|
||||
endpoint:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.nomad.endpoint.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.nomad.endpoint.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `endpoint.tls.caOptional` option should be removed from the Nomad provider static configuration.
|
||||
|
||||
### Rancher v1 Provider
|
||||
|
||||
In v3, the Rancher v1 provider has been removed because Rancher v1 is [no longer actively maintained](https://rancher.com/docs/os/v1.x/en/support/),
|
||||
and Rancher v2 is supported as a standard Kubernetes provider.
|
||||
|
||||
??? example "An example of Traefik v2 Rancher v1 configuration"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
rancher: {}
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.rancher]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.rancher=true
|
||||
```
|
||||
|
||||
This configuration is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
|
||||
Rancher 2.x requires Kubernetes and does not have a metadata endpoint of its own for Traefik to query.
|
||||
As such, Rancher 2.x users should utilize the [Kubernetes CRD provider](../providers/kubernetes-crd.md) directly.
|
||||
|
||||
Also, all Rancher provider related configuration should be removed from the static configuration.
|
||||
|
||||
### Marathon provider
|
||||
|
||||
Marathon maintenance [ended on October 31, 2021](https://github.com/mesosphere/marathon/blob/master/README.md).
|
||||
In v3, the Marathon provider has been removed.
|
||||
|
||||
??? example "An example of v2 Marathon provider configuration"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
marathon: {}
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.marathon]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.marathon=true
|
||||
```
|
||||
|
||||
This configuration is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
|
||||
All Marathon provider related configuration should be removed from the static configuration.
|
||||
|
||||
### HTTP Provider
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
HTTP provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the HTTP provider static configuration.
|
||||
|
||||
### ETCD Provider
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
ETCD provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
etcd:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.etcd.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.etcd.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the ETCD provider static configuration.
|
||||
|
||||
### Redis Provider
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
Redis provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
redis:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.redis.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.redis.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the Redis provider static configuration.
|
||||
|
||||
### InfluxDB v1
|
||||
|
||||
InfluxDB v1.x maintenance [ended in 2021](https://www.influxdata.com/blog/influxdb-oss-and-enterprise-roadmap-update-from-influxdays-emea/).
|
||||
In v3, the InfluxDB v1 metrics provider has been removed.
|
||||
|
||||
??? example "An example of Traefik v2 InfluxDB v1 metrics configuration"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB: {}
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics.influxDB]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxDB=true
|
||||
```
|
||||
|
||||
This configuration is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
|
||||
All InfluxDB v1 metrics provider related configuration should be removed from the static configuration.
|
||||
|
||||
### Pilot
|
||||
|
||||
Traefik Pilot is no longer available since October 4th, 2022.
|
||||
|
||||
??? example "An example of v2 Pilot configuration"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
pilot:
|
||||
token: foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[pilot]
|
||||
token=foobar
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--pilot.token=foobar
|
||||
```
|
||||
|
||||
In v2, Pilot configuration was deprecated and ineffective,
|
||||
it is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
|
||||
All Pilot related configuration should be removed from the static configuration.
|
||||
|
||||
## Operations Changes
|
||||
|
||||
### Traefik RBAC Update
|
||||
|
||||
In v3, the support of `TCPServersTransport` has been introduced.
|
||||
When using the KubernetesCRD provider, it is therefore necessary to update [RBAC](../reference/dynamic-configuration/kubernetes-crd.md#rbac) and [CRD](../reference/dynamic-configuration/kubernetes-crd.md) manifests.
|
||||
|
||||
### Content-Type Auto-Detection
|
||||
|
||||
In v3, the `Content-Type` header is not auto-detected anymore when it is not set by the backend.
|
||||
One should use the `ContentType` middleware to enable the `Content-Type` header value auto-detection.
|
||||
|
||||
### Observability
|
||||
|
||||
#### gRPC Metrics
|
||||
|
||||
In v3, the reported status code for gRPC requests is now the value of the `Grpc-Status` header.
|
||||
|
||||
#### Tracing
|
||||
|
||||
In v3, the tracing feature has been revamped and is now powered exclusively by [OpenTelemetry](https://opentelemetry.io/ "Link to website of OTel") (OTel).
|
||||
!!! warning "Important"
|
||||
Traefik v3 **no** longer supports direct output formats for specific vendors such as Instana, Jaeger, Zipkin, Haystack, Datadog, and Elastic.
|
||||
Instead, it focuses on pure OpenTelemetry implementation, providing a unified and standardized approach for observability.
|
||||
|
||||
Here are two possible transition strategies:
|
||||
|
||||
1. OTLP Ingestion Endpoints:
|
||||
|
||||
Most vendors now offer OpenTelemetry Protocol (OTLP) ingestion endpoints.
|
||||
You can seamlessly integrate Traefik v3 with these endpoints to continue leveraging tracing capabilities.
|
||||
|
||||
2. Legacy Stack Compatibility:
|
||||
|
||||
For legacy stacks that cannot immediately upgrade to the latest vendor agents supporting OTLP ingestion,
|
||||
using OpenTelemetry (OTel) collectors with appropriate exporters configuration is a viable solution.
|
||||
This allows continued compatibility with the existing infrastructure.
|
||||
|
||||
Please check the [OpenTelemetry Tracing provider documention](../observability/tracing/opentelemetry.md) for more information.
|
||||
|
||||
#### Internal Resources Observability
|
||||
|
||||
In v3, observability for internal routers or services (e.g.: `ping@internal`) is disabled by default.
|
||||
To enable it one should use the new `addInternals` option for AccessLogs, Metrics or Tracing.
|
||||
Please take a look at the observability documentation for more information:
|
||||
|
||||
- [AccessLogs](../observability/access-logs.md#addinternals)
|
||||
- [Metrics](../observability/metrics/overview.md#addinternals)
|
||||
- [Tracing](../observability/tracing/overview.md#addinternals)
|
||||
|
||||
## Dynamic Configuration Changes
|
||||
|
||||
### Router Rule Matchers
|
||||
|
||||
In v3, a new rule matchers syntax has been introduced for HTTP and TCP routers.
|
||||
The default rule matchers syntax is now the v3 one, but for backward compatibility this can be configured.
|
||||
The v2 rule matchers syntax is deprecated and its support will be removed in the next major version.
|
||||
For this reason, we encourage migrating to the new syntax.
|
||||
|
||||
By default, the `defaultRuleSyntax` static option is automatically set to `v3`, meaning that the default rule is the new one.
|
||||
|
||||
#### New V3 Syntax Notable Changes
|
||||
|
||||
The `Headers` and `HeadersRegexp` matchers have been renamed to `Header` and `HeaderRegexp` respectively.
|
||||
|
||||
`PathPrefix` no longer uses regular expressions to match path prefixes.
|
||||
|
||||
`QueryRegexp` has been introduced to match query values using a regular expression.
|
||||
|
||||
`HeaderRegexp`, `HostRegexp`, `PathRegexp`, `QueryRegexp`, and `HostSNIRegexp` matchers now uses the [Go regexp syntax](https://golang.org/pkg/regexp/syntax/).
|
||||
|
||||
All matchers now take a single value (except `Header`, `HeaderRegexp`, `Query`, and `QueryRegexp` which take two)
|
||||
and should be explicitly combined using logical operators to mimic previous behavior.
|
||||
|
||||
`Query` can take a single value to match is the query value that has no value (e.g. `/search?mobile`).
|
||||
|
||||
`HostHeader` has been removed, use `Host` instead.
|
||||
|
||||
#### Remediation
|
||||
|
||||
##### Configure the Default Syntax In Static Configuration
|
||||
|
||||
The default rule matchers syntax is the expected syntax for any router that is not self opt-out from this default value.
|
||||
It can be configured in the static configuration.
|
||||
|
||||
??? example "An example configuration for the default rule matchers syntax"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
# static configuration
|
||||
core:
|
||||
defaultRuleSyntax: v2
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
# static configuration
|
||||
[core]
|
||||
defaultRuleSyntax="v2"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# static configuration
|
||||
--core.defaultRuleSyntax=v2
|
||||
```
|
||||
|
||||
##### Configure the Syntax Per Router
|
||||
|
||||
The rule syntax can also be configured on a per-router basis.
|
||||
This allows to have heterogeneous router configurations and ease migration.
|
||||
|
||||
??? example "An example router with syntax configuration"
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.routers.test.ruleSyntax=v2"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
routes:
|
||||
- match: PathPrefix(`/foo`, `/bar`)
|
||||
syntax: v2
|
||||
kind: Rule
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.routers.test.ruleSyntax=v2"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
routers:
|
||||
test:
|
||||
ruleSyntax: v2
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.routers]
|
||||
[http.routers.test]
|
||||
ruleSyntax = "v2"
|
||||
```
|
||||
|
||||
### IPWhiteList
|
||||
|
||||
In v3, we renamed the `IPWhiteList` middleware to `IPAllowList` without changing anything to the configuration.
|
||||
|
||||
### Deprecated Options Removal
|
||||
|
||||
- The `tracing.datadog.globaltag` option has been removed.
|
||||
- The `tls.caOptional` option has been removed from the ForwardAuth middleware, as well as from the HTTP, Consul, Etcd, Redis, ZooKeeper, Consul Catalog, and Docker providers.
|
||||
- `sslRedirect`, `sslTemporaryRedirect`, `sslHost`, `sslForceHost` and `featurePolicy` options of the Headers middleware have been removed.
|
||||
- The `forceSlash` option of the StripPrefix middleware has been removed.
|
||||
- The `preferServerCipherSuites` option has been removed.
|
||||
|
||||
### TCP LoadBalancer `terminationDelay` option
|
||||
|
||||
The TCP LoadBalancer `terminationDelay` option has been removed.
|
||||
This option can now be configured directly on the `TCPServersTransport` level, please take a look at this [documentation](../routing/services/index.md#terminationdelay)
|
||||
|
||||
### Kubernetes CRDs API Group `traefik.containo.us`
|
||||
|
||||
In v3, the Kubernetes CRDs API Group `traefik.containo.us` has been removed.
|
||||
Please use the API Group `traefik.io` instead.
|
||||
|
||||
### Kubernetes Ingress API Group `networking.k8s.io/v1beta1`
|
||||
|
||||
In v3, the Kubernetes Ingress API Group `networking.k8s.io/v1beta1` ([removed since Kubernetes v1.22](https://kubernetes.io/docs/reference/using-api/deprecation-guide/#ingress-v122)) support has been removed.
|
||||
|
||||
Please use the API Group `networking.k8s.io/v1` instead.
|
||||
|
||||
### Traefik CRD API Version `apiextensions.k8s.io/v1beta1`
|
||||
|
||||
In v3, the Traefik CRD API Version `apiextensions.k8s.io/v1beta1` ([removed since Kubernetes v1.22](https://kubernetes.io/docs/reference/using-api/deprecation-guide/#customresourcedefinition-v122)) support has been removed.
|
||||
|
||||
Please use the CRD definition with the API Version `apiextensions.k8s.io/v1` instead.
|
|
@ -8,729 +8,70 @@ description: "Migrate from Traefik Proxy v2 to v3 and update all the necessary c
|
|||
How to Migrate from Traefik v2 to Traefik v3.
|
||||
{: .subtitle }
|
||||
|
||||
The version 3 of Traefik introduces a number of breaking changes,
|
||||
which require one to update their configuration when they migrate from v2 to v3.
|
||||
The goal of this page is to recapitulate all of these changes,
|
||||
and in particular to give examples, feature by feature,
|
||||
of how the configuration looked like in v2,
|
||||
and how it now looks like in v3.
|
||||
With Traefik v3, we are introducing a streamlined transition process from v2. Minimal breaking changes have been made to specific options in the [static configuration](./v2-to-v3-details.md#static-configuration-changes "Link to static configuration changes"), and we are ensuring backward compatibility with v2 syntax in the [dynamic configuration](./v2-to-v3-details.md#dynamic-configuration-changes "Link to dynamic configuration changes"). This will offer a gradual path for adopting the v3 syntax, allowing users to progressively migrate their Kubernetes ingress resources, Docker labels, etc., to the new format.
|
||||
|
||||
## Static configuration
|
||||
Here are the steps to progressively migrate from Traefik v2 to v3:
|
||||
|
||||
### Docker & Docker Swarm
|
||||
1. [Prepare configurations and test v3](#step-1-prepare-configurations-and-test-v3)
|
||||
1. [Migrate production instances to Traefik v3](#step-2-migrate-production-instances-to-traefik-v3)
|
||||
1. [Progressively migrate dynamic configuration](#step-3-progressively-migrate-dynamic-configuration)
|
||||
|
||||
#### SwarmMode
|
||||
## Step 1: Prepare Configurations and Test v3
|
||||
|
||||
In v3, the provider Docker has been split into 2 providers:
|
||||
Check the changes in [static configurations](./v2-to-v3-details.md#static-configuration-changes "Link to static configuration changes") and [operations](./v2-to-v3-details.md#operations-changes "Link to operations changes") brought by Traefik v3.
|
||||
Modify your configurations accordingly.
|
||||
|
||||
- Docker provider (without Swarm support)
|
||||
- Swarm provider (Swarm support only)
|
||||
Then, add the following snippet to the static configuration:
|
||||
|
||||
??? example "An example usage of v2 Docker provider with Swarm"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
swarmMode: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker]
|
||||
swarmMode=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.swarmMode=true
|
||||
```
|
||||
|
||||
This configuration is now unsupported and would prevent Traefik to start.
|
||||
|
||||
##### Remediation
|
||||
|
||||
In v3, the `swarmMode` should not be used with the Docker provider, and, to use Swarm, the Swarm provider should be used instead.
|
||||
|
||||
??? example "An example usage of the Swarm provider"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
swarm:
|
||||
endpoint: "tcp://127.0.0.1:2377"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.swarm]
|
||||
endpoint="tcp://127.0.0.1:2377"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.swarm.endpoint=tcp://127.0.0.1:2377
|
||||
```
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
Docker provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
docker:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.docker.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.docker.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the Docker provider static configuration.
|
||||
|
||||
### Kubernetes Gateway API
|
||||
|
||||
#### Experimental Channel Resources (TLSRoute and TCPRoute)
|
||||
|
||||
In v3, the Kubernetes Gateway API provider does not enable support for the experimental channel API resources by default.
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `experimentalChannel` option should be used to enable the support for the experimental channel API resources.
|
||||
|
||||
??? example "An example usage of the Kubernetes Gateway API provider with experimental channel support enabled"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
kubernetesGateway:
|
||||
experimentalChannel: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.kubernetesGateway]
|
||||
experimentalChannel = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesgateway.experimentalchannel=true
|
||||
```
|
||||
|
||||
### Experimental Configuration
|
||||
|
||||
#### HTTP3
|
||||
|
||||
In v3, HTTP/3 is no longer an experimental feature.
|
||||
It can be enabled on entry points without the associated `experimental.http3` option, which is now removed.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
??? example "An example usage of v2 Experimental `http3` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
experimental:
|
||||
http3: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[experimental]
|
||||
http3=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--experimental.http3=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `http3` option should be removed from the static configuration experimental section.
|
||||
To configure `http3`, please checkout the [entrypoint configuration documentation](https://doc.traefik.io/traefik/v3.0/routing/entrypoints/#http3_1).
|
||||
|
||||
### Consul provider
|
||||
|
||||
#### namespace
|
||||
|
||||
The Consul provider `namespace` option was deprecated in v2 and is now removed in v3.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
??? example "An example usage of v2 Consul `namespace` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
consul:
|
||||
namespace: foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[consul]
|
||||
namespace=foobar
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--consul.namespace=foobar
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
In v3, the `namespaces` option should be used instead of the `namespace` option.
|
||||
|
||||
??? example "An example usage of Consul `namespaces` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
consul:
|
||||
namespaces:
|
||||
- foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[consul]
|
||||
namespaces=["foobar"]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--consul.namespaces=foobar
|
||||
```
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
Consul provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
consul:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consul.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.consul.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the Consul provider static configuration.
|
||||
|
||||
### ConsulCatalog provider
|
||||
|
||||
#### namespace
|
||||
|
||||
The ConsulCatalog provider `namespace` option was deprecated in v2 and is now removed in v3.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
??? example "An example usage of v2 ConsulCatalog `namespace` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
consulCatalog:
|
||||
namespace: foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[consulCatalog]
|
||||
namespace=foobar
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--consulCatalog.namespace=foobar
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
In v3, the `namespaces` option should be used instead of the `namespace` option.
|
||||
|
||||
??? example "An example usage of ConsulCatalog `namespaces` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
consulCatalog:
|
||||
namespaces:
|
||||
- foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[consulCatalog]
|
||||
namespaces=["foobar"]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--consulCatalog.namespaces=foobar
|
||||
```
|
||||
|
||||
#### Endpoint.TLS.CAOptional
|
||||
|
||||
ConsulCatalog provider `endpoint.tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the Endpoint.TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
consulCatalog:
|
||||
endpoint:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.consulCatalog.endpoint.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.consulCatalog.endpoint.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `endpoint.tls.caOptional` option should be removed from the ConsulCatalog provider static configuration.
|
||||
|
||||
### Nomad provider
|
||||
|
||||
#### namespace
|
||||
|
||||
The Nomad provider `namespace` option was deprecated in v2 and is now removed in v3.
|
||||
It is now unsupported and would prevent Traefik to start.
|
||||
|
||||
??? example "An example usage of v2 Nomad `namespace` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
nomad:
|
||||
namespace: foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[nomad]
|
||||
namespace=foobar
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--nomad.namespace=foobar
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
In v3, the `namespaces` option should be used instead of the `namespace` option.
|
||||
|
||||
??? example "An example usage of Nomad `namespaces` option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
nomad:
|
||||
namespaces:
|
||||
- foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[nomad]
|
||||
namespaces=["foobar"]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--nomad.namespaces=foobar
|
||||
```
|
||||
|
||||
#### Endpoint.TLS.CAOptional
|
||||
|
||||
Nomad provider `endpoint.tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the Endpoint.TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
nomad:
|
||||
endpoint:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.nomad.endpoint.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.nomad.endpoint.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `endpoint.tls.caOptional` option should be removed from the Nomad provider static configuration.
|
||||
|
||||
### Rancher v1 Provider
|
||||
|
||||
In v3, the Rancher v1 provider has been removed because Rancher v1 is [no longer actively maintained](https://rancher.com/docs/os/v1.x/en/support/),
|
||||
and Rancher v2 is supported as a standard Kubernetes provider.
|
||||
|
||||
??? example "An example of Traefik v2 Rancher v1 configuration"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
rancher: {}
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.rancher]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.rancher=true
|
||||
```
|
||||
|
||||
This configuration is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
|
||||
Rancher 2.x requires Kubernetes and does not have a metadata endpoint of its own for Traefik to query.
|
||||
As such, Rancher 2.x users should utilize the [Kubernetes CRD provider](../providers/kubernetes-crd.md) directly.
|
||||
|
||||
Also, all Rancher provider related configuration should be removed from the static configuration.
|
||||
|
||||
### Marathon provider
|
||||
|
||||
Marathon maintenance [ended on October 31, 2021](https://github.com/mesosphere/marathon/blob/master/README.md).
|
||||
In v3, the Marathon provider has been removed.
|
||||
|
||||
??? example "An example of v2 Marathon provider configuration"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
marathon: {}
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.marathon]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.marathon=true
|
||||
```
|
||||
|
||||
This configuration is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
|
||||
All Marathon provider related configuration should be removed from the static configuration.
|
||||
|
||||
### HTTP Provider
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
HTTP provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
http:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.http.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.http.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the HTTP provider static configuration.
|
||||
|
||||
### ETCD Provider
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
ETCD provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
etcd:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.etcd.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.etcd.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the ETCD provider static configuration.
|
||||
|
||||
### Redis Provider
|
||||
|
||||
#### TLS.CAOptional
|
||||
|
||||
Redis provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://pkg.go.dev/crypto/tls#ClientAuthType).
|
||||
|
||||
??? example "An example usage of the TLS.CAOptional option"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
redis:
|
||||
tls:
|
||||
caOptional: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.redis.tls]
|
||||
caOptional=true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.redis.tls.caOptional=true
|
||||
```
|
||||
|
||||
##### Remediation
|
||||
|
||||
The `tls.caOptional` option should be removed from the Redis provider static configuration.
|
||||
|
||||
### InfluxDB v1
|
||||
|
||||
InfluxDB v1.x maintenance [ended in 2021](https://www.influxdata.com/blog/influxdb-oss-and-enterprise-roadmap-update-from-influxdays-emea/).
|
||||
In v3, the InfluxDB v1 metrics provider has been removed.
|
||||
|
||||
??? example "An example of Traefik v2 InfluxDB v1 metrics configuration"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
metrics:
|
||||
influxDB: {}
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[metrics.influxDB]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--metrics.influxDB=true
|
||||
```
|
||||
|
||||
This configuration is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
|
||||
All InfluxDB v1 metrics provider related configuration should be removed from the static configuration.
|
||||
|
||||
### Pilot
|
||||
|
||||
Traefik Pilot is no longer available since October 4th, 2022.
|
||||
|
||||
??? example "An example of v2 Pilot configuration"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
pilot:
|
||||
token: foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[pilot]
|
||||
token=foobar
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--pilot.token=foobar
|
||||
```
|
||||
|
||||
In v2, Pilot configuration was deprecated and ineffective,
|
||||
it is now unsupported and would prevent Traefik to start.
|
||||
|
||||
#### Remediation
|
||||
|
||||
All Pilot related configuration should be removed from the static configuration.
|
||||
|
||||
## Dynamic configuration
|
||||
|
||||
### Router Rule Matchers
|
||||
|
||||
In v3, a new rule matchers syntax has been introduced for HTTP and TCP routers.
|
||||
The default rule matchers syntax is now the v3 one, but for backward compatibility this can be configured.
|
||||
The v2 rule matchers syntax is deprecated and its support will be removed in the next major version.
|
||||
For this reason, we encourage migrating to the new syntax.
|
||||
|
||||
By default, the `defaultRuleSyntax` static option is automatically set to `v3`, meaning that the default rule is the new one.
|
||||
|
||||
#### New V3 Syntax Notable Changes
|
||||
|
||||
The `Headers` and `HeadersRegexp` matchers have been renamed to `Header` and `HeaderRegexp` respectively.
|
||||
|
||||
`PathPrefix` no longer uses regular expressions to match path prefixes.
|
||||
|
||||
`QueryRegexp` has been introduced to match query values using a regular expression.
|
||||
|
||||
`HeaderRegexp`, `HostRegexp`, `PathRegexp`, `QueryRegexp`, and `HostSNIRegexp` matchers now uses the [Go regexp syntax](https://golang.org/pkg/regexp/syntax/).
|
||||
|
||||
All matchers now take a single value (except `Header`, `HeaderRegexp`, `Query`, and `QueryRegexp` which take two)
|
||||
and should be explicitly combined using logical operators to mimic previous behavior.
|
||||
|
||||
`Query` can take a single value to match is the query value that has no value (e.g. `/search?mobile`).
|
||||
|
||||
`HostHeader` has been removed, use `Host` instead.
|
||||
|
||||
#### Remediation
|
||||
|
||||
##### Configure the Default Syntax In Static Configuration
|
||||
|
||||
The default rule matchers syntax is the expected syntax for any router that is not self opt-out from this default value.
|
||||
It can be configured in the static configuration.
|
||||
|
||||
??? example "An example configuration for the default rule matchers syntax"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
# static configuration
|
||||
core:
|
||||
defaultRuleSyntax: v2
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
# static configuration
|
||||
[core]
|
||||
defaultRuleSyntax="v2"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# static configuration
|
||||
--core.defaultRuleSyntax=v2
|
||||
```
|
||||
|
||||
##### Configure the Syntax Per Router
|
||||
|
||||
The rule syntax can also be configured on a per-router basis.
|
||||
This allows to have heterogeneous router configurations and ease migration.
|
||||
|
||||
??? example "An example router with syntax configuration"
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.routers.test.ruleSyntax=v2"
|
||||
```yaml
|
||||
# static configuration
|
||||
core:
|
||||
defaultRuleSyntax: v2
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
This snippet in the static configuration makes the [v2 format](https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/?ref=traefik.io#configure-the-default-syntax-in-static-configuration "Link to configure default syntax in static config") the default rule matchers syntax.
|
||||
|
||||
spec:
|
||||
routes:
|
||||
- match: PathPrefix(`/foo`, `/bar`)
|
||||
syntax: v2
|
||||
kind: Rule
|
||||
Start Traefik v3 with this new configuration to test it.
|
||||
|
||||
If you don’t get any error logs while testing, you are good to go!
|
||||
Otherwise, follow the remaining migration options highlighted in the logs.
|
||||
|
||||
Once your Traefik test instances are starting and routing to your applications, proceed to the next step.
|
||||
|
||||
## Step 2: Migrate Production Instances to Traefik v3
|
||||
|
||||
We strongly advise you to follow a progressive migration strategy ([Kubernetes rolling update mechanism](https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/ "Link to the Kubernetes rolling update documentation"), for example) to migrate your production instances to v3.
|
||||
|
||||
!!! Warning
|
||||
Ensure you have a [real-time monitoring solution](https://traefik.io/blog/capture-traefik-metrics-for-apps-on-kubernetes-with-prometheus/ "Link to the blog on capturing Traefik metrics with Prometheus") for your ingress traffic to detect issues instantly.
|
||||
|
||||
During the progressive migration, monitor your ingress traffic for any errors. Be prepared to rollback to a working state in case of any issues.
|
||||
|
||||
If you encounter any issues, leverage debug and access logs provided by Traefik to understand what went wrong and how to fix it.
|
||||
|
||||
Once every Traefik instance is updated, you will be on Traefik v3!
|
||||
|
||||
## Step 3: Progressively Migrate Dynamic Configuration
|
||||
|
||||
!!! info
|
||||
This step can be done later in the process, as Traefik v3 is compatible with the v2 format for [dynamic configuration](./v2-to-v3-details.md#dynamic-configuration-changes "Link to dynamic configuration changes").
|
||||
Enable Traefik logs to get some help if any deprecated option is in use.
|
||||
|
||||
Check the changes in [dynamic configuration](./v2-to-v3-details.md#dynamic-configuration-changes "Link to dynamic configuration changes").
|
||||
|
||||
Then, progressively [switch each router to the v3 syntax](./v2-to-v3-details.md#configure-the-syntax-per-router "Link to configuring the syntax per router").
|
||||
|
||||
Test and update each Ingress resource and ensure that ingress traffic is not impacted.
|
||||
|
||||
Once a v3 Ingress resource migration is validated, deploy the resource and delete the v2 Ingress resource.
|
||||
Repeat it until all Ingress resources are migrated.
|
||||
|
||||
Now, remove the following snippet added to the static configuration in Step 1:
|
||||
|
||||
```yaml
|
||||
# static configuration
|
||||
core:
|
||||
defaultRuleSyntax: v2
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.routers.test.ruleSyntax=v2"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
routers:
|
||||
test:
|
||||
ruleSyntax: v2
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.routers]
|
||||
[http.routers.test]
|
||||
ruleSyntax = "v2"
|
||||
```
|
||||
|
||||
### IPWhiteList
|
||||
|
||||
In v3, we renamed the `IPWhiteList` middleware to `IPAllowList` without changing anything to the configuration.
|
||||
|
||||
### Deprecated Options Removal
|
||||
|
||||
- The `tracing.datadog.globaltag` option has been removed.
|
||||
- The `tls.caOptional` option has been removed from the ForwardAuth middleware, as well as from the HTTP, Consul, Etcd, Redis, ZooKeeper, Consul Catalog, and Docker providers.
|
||||
- `sslRedirect`, `sslTemporaryRedirect`, `sslHost`, `sslForceHost` and `featurePolicy` options of the Headers middleware have been removed.
|
||||
- The `forceSlash` option of the StripPrefix middleware has been removed.
|
||||
- The `preferServerCipherSuites` option has been removed.
|
||||
|
||||
### TCP LoadBalancer `terminationDelay` option
|
||||
|
||||
The TCP LoadBalancer `terminationDelay` option has been removed.
|
||||
This option can now be configured directly on the `TCPServersTransport` level, please take a look at this [documentation](../routing/services/index.md#terminationdelay)
|
||||
|
||||
### Kubernetes CRDs API Group `traefik.containo.us`
|
||||
|
||||
In v3, the Kubernetes CRDs API Group `traefik.containo.us` has been removed.
|
||||
Please use the API Group `traefik.io` instead.
|
||||
|
||||
### Kubernetes Ingress API Group `networking.k8s.io/v1beta1`
|
||||
|
||||
In v3, the Kubernetes Ingress API Group `networking.k8s.io/v1beta1` ([removed since Kubernetes v1.22](https://kubernetes.io/docs/reference/using-api/deprecation-guide/#ingress-v122)) support has been removed.
|
||||
|
||||
Please use the API Group `networking.k8s.io/v1` instead.
|
||||
|
||||
### Traefik CRD API Version `apiextensions.k8s.io/v1beta1`
|
||||
|
||||
In v3, the Traefik CRD API Version `apiextensions.k8s.io/v1beta1` ([removed since Kubernetes v1.22](https://kubernetes.io/docs/reference/using-api/deprecation-guide/#customresourcedefinition-v122)) support has been removed.
|
||||
|
||||
Please use the CRD definition with the API Version `apiextensions.k8s.io/v1` instead.
|
||||
|
||||
## Operations
|
||||
|
||||
### Traefik RBAC Update
|
||||
|
||||
In v3, the support of `TCPServersTransport` has been introduced.
|
||||
When using the KubernetesCRD provider, it is therefore necessary to update [RBAC](../reference/dynamic-configuration/kubernetes-crd.md#rbac) and [CRD](../reference/dynamic-configuration/kubernetes-crd.md) manifests.
|
||||
|
||||
### Content-Type Auto-Detection
|
||||
|
||||
In v3, the `Content-Type` header is not auto-detected anymore when it is not set by the backend.
|
||||
One should use the `ContentType` middleware to enable the `Content-Type` header value auto-detection.
|
||||
|
||||
### Observability
|
||||
|
||||
#### gRPC Metrics
|
||||
|
||||
In v3, the reported status code for gRPC requests is now the value of the `Grpc-Status` header.
|
||||
|
||||
#### Tracing
|
||||
|
||||
In v3, the tracing feature has been revamped and is now powered exclusively by [OpenTelemetry](https://opentelemetry.io/ "Link to website of OTel") (OTel).
|
||||
!!! warning "Important"
|
||||
|
||||
Traefik v3 **no** longer supports direct output formats for specific vendors such as Instana, Jaeger, Zipkin, Haystack, Datadog, and Elastic.
|
||||
Instead, it focuses on pure OpenTelemetry implementation, providing a unified and standardized approach for observability.
|
||||
|
||||
Here are two possible transition strategies:
|
||||
|
||||
1. OTLP Ingestion Endpoints:
|
||||
|
||||
Most vendors now offer OpenTelemetry Protocol (OTLP) ingestion endpoints.
|
||||
You can seamlessly integrate Traefik v3 with these endpoints to continue leveraging tracing capabilities.
|
||||
|
||||
2. Legacy Stack Compatibility:
|
||||
|
||||
For legacy stacks that cannot immediately upgrade to the latest vendor agents supporting OTLP ingestion,
|
||||
using OpenTelemetry (OTel) collectors with appropriate exporters configuration is a viable solution.
|
||||
This allows continued compatibility with the existing infrastructure.
|
||||
|
||||
Please check the [OpenTelemetry Tracing provider documention](../observability/tracing/opentelemetry.md) for more information.
|
||||
|
||||
#### Internal Resources Observability
|
||||
|
||||
In v3, observability for internal routers or services (e.g.: `ping@internal`) is disabled by default.
|
||||
To enable it one should use the new `addInternals` option for AccessLogs, Metrics or Tracing.
|
||||
Please take a look at the observability documentation for more information:
|
||||
|
||||
- [AccessLogs](../observability/access-logs.md#addinternals)
|
||||
- [Metrics](../observability/metrics/overview.md#addinternals)
|
||||
- [Tracing](../observability/tracing/overview.md#addinternals)
|
||||
You are now fully migrated to Traefik v3 🎉
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
- "traefik.http.middlewares.middleware05.circuitbreaker.recoveryduration=42s"
|
||||
- "traefik.http.middlewares.middleware05.circuitbreaker.responsecode=42"
|
||||
- "traefik.http.middlewares.middleware06.compress=true"
|
||||
- "traefik.http.middlewares.middleware06.compress.defaultencoding=foobar"
|
||||
- "traefik.http.middlewares.middleware06.compress.excludedcontenttypes=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware06.compress.includedcontenttypes=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware06.compress.minresponsebodybytes=42"
|
||||
|
@ -54,6 +55,7 @@
|
|||
- "traefik.http.middlewares.middleware12.headers.allowedhosts=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.browserxssfilter=true"
|
||||
- "traefik.http.middlewares.middleware12.headers.contentsecuritypolicy=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.contentsecuritypolicyreportonly=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.contenttypenosniff=true"
|
||||
- "traefik.http.middlewares.middleware12.headers.custombrowserxssvalue=foobar"
|
||||
- "traefik.http.middlewares.middleware12.headers.customframeoptionsvalue=foobar"
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
excludedContentTypes = ["foobar", "foobar"]
|
||||
includedContentTypes = ["foobar", "foobar"]
|
||||
minResponseBodyBytes = 42
|
||||
defaultEncoding = "foobar"
|
||||
[http.middlewares.Middleware07]
|
||||
[http.middlewares.Middleware07.contentType]
|
||||
autoDetect = true
|
||||
|
@ -197,6 +198,7 @@
|
|||
browserXssFilter = true
|
||||
customBrowserXSSValue = "foobar"
|
||||
contentSecurityPolicy = "foobar"
|
||||
contentSecurityPolicyReportOnly = "foobar"
|
||||
publicKey = "foobar"
|
||||
referrerPolicy = "foobar"
|
||||
permissionsPolicy = "foobar"
|
||||
|
|
|
@ -152,6 +152,7 @@ http:
|
|||
- foobar
|
||||
- foobar
|
||||
minResponseBodyBytes: 42
|
||||
defaultEncoding: foobar
|
||||
Middleware07:
|
||||
contentType:
|
||||
autoDetect: true
|
||||
|
@ -241,6 +242,7 @@ http:
|
|||
browserXssFilter: true
|
||||
customBrowserXSSValue: foobar
|
||||
contentSecurityPolicy: foobar
|
||||
contentSecurityPolicyReportOnly: foobar
|
||||
publicKey: foobar
|
||||
referrerPolicy: foobar
|
||||
permissionsPolicy: foobar
|
||||
|
|
|
@ -98,6 +98,67 @@ spec:
|
|||
description: Service defines an upstream HTTP service to proxy
|
||||
traffic to.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be
|
||||
sent to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname
|
||||
in the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for
|
||||
the health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme
|
||||
for the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status
|
||||
code of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -846,6 +907,11 @@ spec:
|
|||
This middleware compresses responses before sending them to the client, using gzip compression.
|
||||
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/
|
||||
properties:
|
||||
defaultEncoding:
|
||||
description: DefaultEncoding specifies the default encoding if
|
||||
the `Accept-Encoding` header is not in the request or contains
|
||||
a wildcard (`*`).
|
||||
type: string
|
||||
excludedContentTypes:
|
||||
description: |-
|
||||
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
||||
|
@ -919,6 +985,67 @@ spec:
|
|||
Service defines the reference to a Kubernetes Service that will serve the error page.
|
||||
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent
|
||||
to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname in
|
||||
the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the
|
||||
health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status code
|
||||
of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -1182,6 +1309,10 @@ spec:
|
|||
description: ContentSecurityPolicy defines the Content-Security-Policy
|
||||
header value.
|
||||
type: string
|
||||
contentSecurityPolicyReportOnly:
|
||||
description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only
|
||||
header value.
|
||||
type: string
|
||||
contentTypeNosniff:
|
||||
description: ContentTypeNosniff defines whether to add the X-Content-Type-Options
|
||||
header with the nosniff value.
|
||||
|
@ -2295,6 +2426,67 @@ spec:
|
|||
mirroring:
|
||||
description: Mirroring defines the Mirroring service configuration.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent to
|
||||
the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname in the
|
||||
Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the health
|
||||
check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the health
|
||||
check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status code
|
||||
of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -2314,6 +2506,67 @@ spec:
|
|||
items:
|
||||
description: MirrorService holds the mirror configuration.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent
|
||||
to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname
|
||||
in the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the
|
||||
health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status
|
||||
code of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -2548,6 +2801,67 @@ spec:
|
|||
description: Service defines an upstream HTTP service to proxy
|
||||
traffic to.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent
|
||||
to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname
|
||||
in the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the
|
||||
health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status
|
||||
code of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
|
|
@ -21,6 +21,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware05/circuitBreaker/fallbackDuration` | `42s` |
|
||||
| `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` |
|
||||
| `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` |
|
||||
| `traefik/http/middlewares/Middleware06/compress/defaultEncoding` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` |
|
||||
|
@ -70,6 +71,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware12/headers/allowedHosts/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/browserXssFilter` | `true` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicy` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicyReportOnly` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/contentTypeNosniff` | `true` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/customBrowserXSSValue` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware12/headers/customFrameOptionsValue` | `foobar` |
|
||||
|
|
|
@ -98,6 +98,67 @@ spec:
|
|||
description: Service defines an upstream HTTP service to proxy
|
||||
traffic to.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be
|
||||
sent to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname
|
||||
in the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for
|
||||
the health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme
|
||||
for the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status
|
||||
code of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
|
|
@ -183,6 +183,11 @@ spec:
|
|||
This middleware compresses responses before sending them to the client, using gzip compression.
|
||||
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/
|
||||
properties:
|
||||
defaultEncoding:
|
||||
description: DefaultEncoding specifies the default encoding if
|
||||
the `Accept-Encoding` header is not in the request or contains
|
||||
a wildcard (`*`).
|
||||
type: string
|
||||
excludedContentTypes:
|
||||
description: |-
|
||||
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
||||
|
@ -256,6 +261,67 @@ spec:
|
|||
Service defines the reference to a Kubernetes Service that will serve the error page.
|
||||
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent
|
||||
to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname in
|
||||
the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the
|
||||
health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status code
|
||||
of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -519,6 +585,10 @@ spec:
|
|||
description: ContentSecurityPolicy defines the Content-Security-Policy
|
||||
header value.
|
||||
type: string
|
||||
contentSecurityPolicyReportOnly:
|
||||
description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only
|
||||
header value.
|
||||
type: string
|
||||
contentTypeNosniff:
|
||||
description: ContentTypeNosniff defines whether to add the X-Content-Type-Options
|
||||
header with the nosniff value.
|
||||
|
|
|
@ -47,6 +47,67 @@ spec:
|
|||
mirroring:
|
||||
description: Mirroring defines the Mirroring service configuration.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent to
|
||||
the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname in the
|
||||
Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the health
|
||||
check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the health
|
||||
check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status code
|
||||
of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -66,6 +127,67 @@ spec:
|
|||
items:
|
||||
description: MirrorService holds the mirror configuration.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent
|
||||
to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname
|
||||
in the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the
|
||||
health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status
|
||||
code of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -300,6 +422,67 @@ spec:
|
|||
description: Service defines an upstream HTTP service to proxy
|
||||
traffic to.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent
|
||||
to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname
|
||||
in the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the
|
||||
health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status
|
||||
code of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
|
|
@ -342,6 +342,9 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
|
|||
flushInterval: 1ms
|
||||
scheme: https
|
||||
serversTransport: transport # [10]
|
||||
healthCheck: # [11]
|
||||
path: /health
|
||||
interval: 15s
|
||||
sticky:
|
||||
cookie:
|
||||
httpOnly: true
|
||||
|
@ -351,17 +354,17 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
|
|||
maxAge: 42
|
||||
strategy: RoundRobin
|
||||
weight: 10
|
||||
nativeLB: true # [11]
|
||||
nodePortLB: true # [12]
|
||||
tls: # [13]
|
||||
secretName: supersecret # [14]
|
||||
options: # [15]
|
||||
name: opt # [16]
|
||||
namespace: default # [17]
|
||||
certResolver: foo # [18]
|
||||
domains: # [19]
|
||||
- main: example.net # [20]
|
||||
sans: # [21]
|
||||
nativeLB: true # [12]
|
||||
nodePortLB: true # [13]
|
||||
tls: # [14]
|
||||
secretName: supersecret # [15]
|
||||
options: # [16]
|
||||
name: opt # [17]
|
||||
namespace: default # [18]
|
||||
certResolver: foo # [19]
|
||||
domains: # [20]
|
||||
- main: example.net # [21]
|
||||
sans: # [22]
|
||||
- a.example.net
|
||||
- b.example.net
|
||||
```
|
||||
|
@ -378,17 +381,18 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
|
|||
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) |
|
||||
| [9] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. |
|
||||
| [10] | `services[n].serversTransport` | Defines the reference to a [ServersTransport](#kind-serverstransport). The ServersTransport namespace is assumed to be the [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) namespace (see [ServersTransport reference](#serverstransport-reference)). |
|
||||
| [11] | `services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. |
|
||||
| [12] | `services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. |
|
||||
| [13] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
|
||||
| [14] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
|
||||
| [15] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
|
||||
| [16] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
|
||||
| [17] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
|
||||
| [18] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
|
||||
| [19] | `tls.domains` | List of [domains](../routers/index.md#domains) |
|
||||
| [20] | `domains[n].main` | Defines the main domain name |
|
||||
| [21] | `domains[n].sans` | List of SANs (alternative domains) |
|
||||
| [11] | `services[n].healthCheck` | Defines the HealthCheck when service references a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName. |
|
||||
| [12] | `services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. |
|
||||
| [13] | `services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. |
|
||||
| [14] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
|
||||
| [15] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
|
||||
| [16] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
|
||||
| [17] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
|
||||
| [18] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
|
||||
| [19] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
|
||||
| [20] | `tls.domains` | List of [domains](../routers/index.md#domains) |
|
||||
| [21] | `domains[n].main` | Defines the main domain name |
|
||||
| [22] | `domains[n].sans` | List of SANs (alternative domains) |
|
||||
|
||||
??? example "Declaring an IngressRoute"
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
FROM alpine:3.14
|
||||
FROM alpine:3.20
|
||||
|
||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin
|
||||
ENV PATH="${PATH}:/venv/bin"
|
||||
|
||||
COPY requirements.txt /mkdocs/
|
||||
WORKDIR /mkdocs
|
||||
VOLUME /mkdocs
|
||||
|
||||
RUN apk --no-cache --no-progress add py3-pip gcc musl-dev python3-dev \
|
||||
&& pip3 install --user -r requirements.txt
|
||||
&& python3 -m venv /venv \
|
||||
&& source /venv/bin/activate \
|
||||
&& pip3 install -r requirements.txt
|
||||
|
|
|
@ -172,7 +172,9 @@ nav:
|
|||
- 'HTTP Challenge': 'user-guides/docker-compose/acme-http/index.md'
|
||||
- 'DNS Challenge': 'user-guides/docker-compose/acme-dns/index.md'
|
||||
- 'Migration':
|
||||
- 'Traefik v2 to v3': 'migration/v2-to-v3.md'
|
||||
- 'Traefik v2 to v3':
|
||||
- 'Migration guide': 'migration/v2-to-v3.md'
|
||||
- 'Configuration changes for v3': 'migration/v2-to-v3-details.md'
|
||||
- 'Traefik v2 minor migrations': 'migration/v2.md'
|
||||
- 'Traefik v1 to v2': 'migration/v1-to-v2.md'
|
||||
- 'Contributing':
|
||||
|
|
|
@ -1,45 +1,23 @@
|
|||
mkdocs==1.2.2
|
||||
markdown-include==0.5.1
|
||||
mkdocs==1.2.4
|
||||
mkdocs-exclude==1.0.2
|
||||
mkdocs-traefiklabs>=100.0.7
|
||||
|
||||
appdirs==1.4.4
|
||||
CacheControl==0.12.6
|
||||
certifi==2020.12.5
|
||||
chardet==4.0.0
|
||||
click==8.0.4
|
||||
colorama==0.4.4
|
||||
contextlib2==0.6.0
|
||||
distlib==0.3.1
|
||||
distro==1.5.0
|
||||
ghp-import==2.0.2
|
||||
html5lib==1.1
|
||||
idna==3.2
|
||||
importlib-metadata==4.11.3
|
||||
Jinja2==3.0.0
|
||||
lockfile==0.12.2
|
||||
click==8.1.7
|
||||
colorama==0.4.6
|
||||
ghp-import==2.1.0
|
||||
importlib_metadata==7.1.0
|
||||
Jinja2==3.1.3
|
||||
Markdown==3.3.6
|
||||
markdown-include==0.5.1
|
||||
MarkupSafe==2.1.1
|
||||
MarkupSafe==2.1.5
|
||||
mergedeep==1.3.4
|
||||
mkdocs-bootswatch==1.0
|
||||
mkdocs-exclude==1.0.2
|
||||
mkdocs-material-extensions==1.0.3
|
||||
msgpack==1.0.2
|
||||
ordered-set==4.0.2
|
||||
packaging==20.9
|
||||
pep517==0.10.0
|
||||
progress==1.5
|
||||
Pygments==2.11.2
|
||||
mkdocs-material-extensions==1.3.1
|
||||
packaging==24.0
|
||||
Pygments==2.18.0
|
||||
pymdown-extensions==7.0
|
||||
pyparsing==2.4.7
|
||||
python-dateutil==2.8.2
|
||||
python-dateutil==2.9.0.post0
|
||||
PyYAML==6.0.1
|
||||
pyyaml-env-tag==0.1
|
||||
requests==2.25.1
|
||||
retrying==1.3.3
|
||||
six==1.15.0
|
||||
toml==0.10.2
|
||||
urllib3==1.26.5
|
||||
watchdog==2.1.7
|
||||
webencodings==0.5.1
|
||||
zipp==3.7.0
|
||||
|
||||
pyyaml_env_tag==0.1
|
||||
six==1.16.0
|
||||
watchdog==4.0.0
|
||||
zipp==3.18.1
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
3.7
|
195
go.mod
195
go.mod
|
@ -3,12 +3,12 @@ module github.com/traefik/traefik/v3
|
|||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000
|
||||
github.com/andybalholm/brotli v1.0.6
|
||||
github.com/aws/aws-sdk-go v1.44.327
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
|
@ -17,16 +17,16 @@ require (
|
|||
github.com/docker/go-connections v0.5.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-acme/lego/v4 v4.16.1
|
||||
github.com/go-acme/lego/v4 v4.17.4
|
||||
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/go-github/v28 v28.1.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/consul/api v1.26.1
|
||||
github.com/hashicorp/go-hclog v1.5.0
|
||||
github.com/hashicorp/go-hclog v1.6.3
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/nomad/api v0.0.0-20240122103822-8a4bd61caf74
|
||||
github.com/http-wasm/http-wasm-host-go v0.6.0
|
||||
|
@ -39,7 +39,7 @@ require (
|
|||
github.com/kvtools/valkeyrie v1.0.0
|
||||
github.com/kvtools/zookeeper v1.0.2
|
||||
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/mitchellh/copystructure v1.2.0
|
||||
github.com/mitchellh/hashstructure v1.0.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
|
@ -68,25 +68,25 @@ require (
|
|||
github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c
|
||||
github.com/vulcand/predicate v1.2.0
|
||||
go.opentelemetry.io/collector/pdata v1.2.0
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.49.0
|
||||
go.opentelemetry.io/otel v1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0
|
||||
go.opentelemetry.io/otel/metric v1.24.0
|
||||
go.opentelemetry.io/otel/sdk v1.24.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.24.0
|
||||
go.opentelemetry.io/otel/trace v1.24.0
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
golang.org/x/mod v0.17.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.52.0
|
||||
go.opentelemetry.io/otel v1.27.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0
|
||||
go.opentelemetry.io/otel/metric v1.27.0
|
||||
go.opentelemetry.io/otel/sdk v1.27.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.27.0
|
||||
go.opentelemetry.io/otel/trace v1.27.0
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0
|
||||
golang.org/x/mod v0.18.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sys v0.21.0
|
||||
golang.org/x/text v0.16.0
|
||||
golang.org/x/time v0.5.0
|
||||
golang.org/x/tools v0.20.0
|
||||
google.golang.org/grpc v1.61.1
|
||||
golang.org/x/tools v0.22.0
|
||||
google.golang.org/grpc v1.64.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.29.2
|
||||
k8s.io/apiextensions-apiserver v0.28.3
|
||||
|
@ -99,27 +99,27 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
|
@ -128,30 +128,30 @@ require (
|
|||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||
github.com/VividCortex/gohistogram v1.0.0 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.38.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.10.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/civo/civogo v0.3.11 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.97.0 // indirect
|
||||
github.com/containerd/containerd v1.7.12 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
|
@ -163,18 +163,18 @@ require (
|
|||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
||||
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
|
||||
github.com/exoscale/egoscale v0.102.3 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
|
@ -183,15 +183,16 @@ require (
|
|||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-playground/validator/v10 v10.15.1 // indirect
|
||||
github.com/go-resty/resty/v2 v2.11.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
||||
github.com/go-zookeeper/zk v1.0.3 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
|
@ -201,16 +202,17 @@ require (
|
|||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||
github.com/gophercloud/gophercloud v1.12.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
|
||||
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/hashicorp/cronexpr v1.1.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/serf v0.10.1 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
|
@ -256,8 +258,8 @@ require (
|
|||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
||||
github.com/nrdcg/desec v0.7.0 // indirect
|
||||
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
|
||||
github.com/nrdcg/desec v0.8.0 // indirect
|
||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||
|
@ -270,10 +272,11 @@ require (
|
|||
github.com/onsi/ginkgo/v2 v2.17.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||
github.com/ovh/go-ovh v1.4.3 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||
github.com/oracle/oci-go-sdk/v65 v65.63.1 // indirect
|
||||
github.com/ovh/go-ovh v1.5.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/pquerna/otp v1.4.0 // indirect
|
||||
|
@ -282,22 +285,25 @@ require (
|
|||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.2.1 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/sacloud/api-client-go v0.2.8 // indirect
|
||||
github.com/sacloud/go-http v0.1.6 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.11.1 // indirect
|
||||
github.com/sacloud/packages-go v0.0.9 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 // indirect
|
||||
github.com/sacloud/api-client-go v0.2.10 // indirect
|
||||
github.com/sacloud/go-http v0.1.8 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.12.0 // indirect
|
||||
github.com/sacloud/packages-go v0.0.10 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 // indirect
|
||||
github.com/selectel/domains-go v1.1.0 // indirect
|
||||
github.com/selectel/go-selvpcclient/v3 v3.1.1 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/softlayer/softlayer-go v1.1.3 // indirect
|
||||
github.com/softlayer/softlayer-go v1.1.5 // indirect
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
|
@ -306,8 +312,8 @@ require (
|
|||
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect
|
||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
github.com/zeebo/errs v1.2.2 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.9 // indirect
|
||||
|
@ -315,31 +321,30 @@ require (
|
|||
go.etcd.io/etcd/client/v3 v3.5.9 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.24.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.27.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.27.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.27.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.27.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0 // indirect
|
||||
go.uber.org/ratelimit v0.3.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/oauth2 v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
google.golang.org/api v0.149.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
google.golang.org/api v0.172.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/h2non/gock.v1 v1.0.16 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.9.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.110.1 // indirect
|
||||
|
|
431
go.sum
431
go.sum
|
@ -6,10 +6,8 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK
|
|||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
|
@ -23,34 +21,36 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs
|
|||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo=
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2/go.mod h1:FbdwsQ2EzwvXxOPcMFYO8ogEc9uMMIj3YkmCdXdAFmk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 h1:9Eih8XcEeQnFD0ntMlUDleKMzfeCeUfa+VbnDCI4AZs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0/go.mod h1:wGPyTi+aURdqPAGMZDQqnNs9IrShADF8w2WZb6bKeq0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 h1:zLzoX5+W2l95UJoVwiyNS4dX8vHyQ6x2xRLoBBL9wMk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
|
||||
github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
|
||||
github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 h1:Ov8avRZi2vmrE2JcXw+tu5K/yB41r7xK9GZDiBF7NdM=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13/go.mod h1:5BAVfWLWXihP47vYrPuBKKf4cS0bXI+KM9Qx6ETDJYo=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
|
@ -62,11 +62,11 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
|
|||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
|
||||
|
@ -100,10 +100,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
|||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 h1:lM7JnA9dEdDFH9XOgRNQMDTQnOjlLkDTNA7c0aWTQ30=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
|
@ -123,37 +121,39 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
|
|||
github.com/aws/aws-sdk-go v1.44.327 h1:ZS8oO4+7MOBLhkdwIhgtVeDzCeWOlTfKJS7EgggbIEY=
|
||||
github.com/aws/aws-sdk-go v1.44.327/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0 h1:LvWkxBi/bsWHqj3bFTUuDLl4OAlbaM1HDZ9YPhj5+jg=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.34.0/go.mod h1:35MKNS46RX7Lb9EIFP2bPy3WrJu+bxU6QgLis8K1aa4=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0 h1:f3hBZWtpn9clZGXJoqahQeec9ZPZnu22g8pg+zNyif0=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.37.0/go.mod h1:8qqfpG4mug2JLlEyWPSFhEGvJiaZ9iPmMDDMYc5Xtas=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.38.3 h1:YdA5QgoYa2wNblkWyZfPlLLYsAEKCwLfdMxpWu16wpM=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.38.3/go.mod h1:T0LiPG5vKHZ7DmOq4Cmw0Kku3tMkaR9AknskS2hUXvI=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.10 h1:J9uHribwEgHmesH5r0enxsZYyiGBWd2AaExSW2SydqE=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.10/go.mod h1:tdzmlLwRjsHJjd4XXoSSnubCkVdRa39y4jCp4RACMkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
|
@ -174,8 +174,8 @@ github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7F
|
|||
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
|
@ -196,8 +196,8 @@ github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM=
|
|||
github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI=
|
||||
github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
|
||||
github.com/cloudflare/cloudflare-go v0.97.0 h1:feZRGiRF1EbljnNIYdt8014FnOLtC3CCvgkLXu915ks=
|
||||
github.com/cloudflare/cloudflare-go v0.97.0/go.mod h1:JXRwuTfHpe5xFg8xytc2w0XC6LcrFsBVMS4WlVaiGg8=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
|
@ -265,10 +265,8 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi
|
|||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM=
|
||||
github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U=
|
||||
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/docker/cli v24.0.9+incompatible h1:OxbimnP/z+qVjDLpq9wbeFU3Nc30XhSe+LkwYQisD50=
|
||||
github.com/docker/cli v24.0.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
|
||||
|
@ -304,8 +302,8 @@ github.com/exoscale/egoscale v0.102.3/go.mod h1:RPf2Gah6up+6kAEayHTQwqapzXlm93f0
|
|||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
|
@ -332,15 +330,15 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv
|
|||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
|
||||
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
|
||||
github.com/go-acme/lego/v4 v4.17.4 h1:h0nePd3ObP6o7kAkndtpTzCw8shOZuWckNYeUQwo36Q=
|
||||
github.com/go-acme/lego/v4 v4.17.4/go.mod h1:dU94SvPNqimEeb7EVilGGSnS0nU1O5Exir0pQ4QFL4U=
|
||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea h1:CnEQOUv4ilElSwFB9g/lVmz206oLE4aNZDYngIY1Gvg=
|
||||
|
@ -383,8 +381,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
|
|||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
|
||||
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
|
||||
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
|
@ -393,8 +391,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
|||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
|
||||
github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
|
||||
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
|
||||
|
@ -409,9 +407,11 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E
|
|||
github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
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/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/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
|
@ -427,6 +427,8 @@ github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
|
|||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -453,8 +455,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
|
@ -505,13 +507,13 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF
|
|||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
|
||||
github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k=
|
||||
github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c=
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15Kfovd8MTZrcana+UlQqNbOif8dLpA0=
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
|
||||
github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
|
||||
github.com/gophercloud/gophercloud v1.12.0 h1:Jrz16vPAL93l80q16fp8NplrTCp93y7rZh2P3Q4Yq7g=
|
||||
github.com/gophercloud/gophercloud v1.12.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
|
||||
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE=
|
||||
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
|
@ -527,8 +529,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
|
|||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
|
@ -548,9 +550,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
|
|||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
|
@ -562,8 +563,8 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+
|
|||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
|
@ -573,7 +574,6 @@ github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjG
|
|||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
|
@ -619,8 +619,8 @@ github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1
|
|||
github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
|
||||
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
|
||||
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
|
||||
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
|
@ -632,7 +632,6 @@ github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST
|
|||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
@ -762,8 +761,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N
|
|||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34=
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
|
@ -832,10 +831,10 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uY
|
|||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
|
||||
github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 h1:qpB3wZR4+MPK92cTC9zZPnndkJgDgPvQqPUAgVc1NXU=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9/go.mod h1:HUoHXDrFvidN1NK9Wb/mZKNOfDNutKkzF2Pg71M9hHA=
|
||||
github.com/nrdcg/desec v0.7.0 h1:iuGhi4pstF3+vJWwt292Oqe2+AsSPKDynQna/eu1fDs=
|
||||
github.com/nrdcg/desec v0.7.0/go.mod h1:e1uRqqKv1mJdd5+SQROAhmy75lKMphLzWIuASLkpeFY=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8=
|
||||
github.com/nrdcg/desec v0.8.0 h1:FJbRWUAluTCUi9nHFnhqPhLSIHiNnB9elZVWYgFtIqA=
|
||||
github.com/nrdcg/desec v0.8.0/go.mod h1:BsnYPtSlBttJL3Gyzv0kDH7zkk60obwThlnqiiKzn+o=
|
||||
github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/freemyip v0.2.0 h1:/GscavT4GVqAY13HExl5UyoB4wlchv6Cg5NYDGsUoJ8=
|
||||
|
@ -875,8 +874,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
|||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
|
||||
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
|
@ -886,14 +885,16 @@ github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go
|
|||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0=
|
||||
github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.63.1 h1:dYL7sk9L1+C9LCmoq+zjPMNteuJJfk54YExq/4pV9xQ=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.63.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
|
||||
github.com/ovh/go-ovh v1.5.1 h1:P8O+7H+NQuFK9P/j4sFW5C0fvSS2DnHYGPwdVCp45wI=
|
||||
github.com/ovh/go-ovh v1.5.1/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
|
@ -910,8 +911,8 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0
|
|||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
|
||||
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -976,8 +977,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
|
|||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
|
@ -985,21 +986,25 @@ github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
|
|||
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sacloud/api-client-go v0.2.8 h1:tIY6PZNBX900K66TqEPa4d6UIbedUczfCBnPJkzi8kw=
|
||||
github.com/sacloud/api-client-go v0.2.8/go.mod h1:0CV/kWNYlS1hCNdnk6Wx7Wdg8DPFCnv0zOIzdXjeAeY=
|
||||
github.com/sacloud/go-http v0.1.6 h1:lJGXDt9xrxJiDszRPaN9NIP8MVj10YKMzmnyzdSfI8w=
|
||||
github.com/sacloud/go-http v0.1.6/go.mod h1:oLAHoDJRkptf8sq4fE8oERLkdCh0kJWfWu+paoJY7I0=
|
||||
github.com/sacloud/iaas-api-go v1.11.1 h1:2MsFZ4H1uRdRVx2nVXuERWQ3swoFc3XreIV5hJ3Nsws=
|
||||
github.com/sacloud/iaas-api-go v1.11.1/go.mod h1:uBDSa06F/V0OnoR66jGdbH0PVnCJw+NeE9RVbVgMfss=
|
||||
github.com/sacloud/packages-go v0.0.9 h1:GbinkBLC/eirFhHpLjoDW6JV7+95Rnd2d8RWj7Afeks=
|
||||
github.com/sacloud/packages-go v0.0.9/go.mod h1:k+EEUMF2LlncjbNIJNOqLyZ9wjTESPIWIk1OA7x9j2Q=
|
||||
github.com/sacloud/api-client-go v0.2.10 h1:+rv3jDohD+pkdYwOTBiB+jZsM0xK3AxadXRzhp3q66c=
|
||||
github.com/sacloud/api-client-go v0.2.10/go.mod h1:Jj3CTy2+O4bcMedVDXlbHuqqche85HEPuVXoQFhLaRc=
|
||||
github.com/sacloud/go-http v0.1.8 h1:ynreWA/vnM8G2ksbMlmefBHsXURKPz49qlPRqQ9IQdw=
|
||||
github.com/sacloud/go-http v0.1.8/go.mod h1:7TL7TN1fnPKHsMifIqURDkGujnKViCgEz5Ei/LQdFK8=
|
||||
github.com/sacloud/iaas-api-go v1.12.0 h1:kqXFn3HzCiawlX6hVJb1GVqcSJqcmiGHB4Zp14sxiI8=
|
||||
github.com/sacloud/iaas-api-go v1.12.0/go.mod h1:SZLXeWOdXk3WReIS557sbU1gkOgrE4rseIBQV1B3b7o=
|
||||
github.com/sacloud/packages-go v0.0.10 h1:UiQGjy8LretewkRhsuna1TBM9Vz/l9FoYpQx+D+AOck=
|
||||
github.com/sacloud/packages-go v0.0.10/go.mod h1:f8QITBh9z4IZc4yE9j21Q8b0sXEMwRlRmhhjWeDVTYs=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 h1:wJrcTdddKOI8TFxs8cemnhKP2EmKy3yfUKHj3ZdfzYo=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 h1:yGAraK1uUjlhSXgNMIy8o/J4LFNcy7yeipBqt9N9mVg=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
|
||||
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
|
||||
github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo=
|
||||
github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA=
|
||||
github.com/selectel/go-selvpcclient/v3 v3.1.1 h1:C1q2LqqosiapoLpnGITGmysg0YCSQYDo2Gh69CioevM=
|
||||
github.com/selectel/go-selvpcclient/v3 v3.1.1/go.mod h1:NM7IXhh1IzqZ88DOw1Qc5Ez3tULLViXo95l5+rKPuyQ=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
|
@ -1007,8 +1012,9 @@ github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg
|
|||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk=
|
||||
github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
|
@ -1023,12 +1029,14 @@ github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h
|
|||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA=
|
||||
github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
|
||||
github.com/softlayer/softlayer-go v1.1.3 h1:dfFzt5eOKIAyB/b78fHMyDu5ICx0ZtxL9NRhBlf831A=
|
||||
github.com/softlayer/softlayer-go v1.1.3/go.mod h1:Pc7F57OgUKaAam7TtpqkUeqL7QyKknfiUI4R49h41/U=
|
||||
github.com/softlayer/softlayer-go v1.1.5 h1:UFFtgKxiw0yIuUw93XBCFIiIMYR5eLgmm4a5DqMHXGg=
|
||||
github.com/softlayer/softlayer-go v1.1.5/go.mod h1:WeJrBLoTJcaT8nO1azeyHyNpo/fDLtbpbvh+pzts+Qw=
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ=
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
|
||||
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
|
@ -1075,10 +1083,10 @@ github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxm
|
|||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 h1:8rUlviSVOEe7TMk7W0gIPrW8MqEzYfZHpsNWSf8s2vg=
|
||||
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898 h1:ERwcXqhc94L9cFxtiI0pvt7IJtlHl/p/Jayl3mLw+ms=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898 h1:LoYv5u+gUoFpU/AmIuTRG/2KiEkdm9gCC0dTvk8WITQ=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898/go.mod h1:c1j6YQ+vCbeA8kJ59Im4UnMd1GxovlpPBDhGZoewfn8=
|
||||
github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E=
|
||||
github.com/testcontainers/testcontainers-go v0.30.0/go.mod h1:K+kHNGiM5zjklKjgTtcrEetF3uhWbMUyqAQoyoh8Pf0=
|
||||
github.com/testcontainers/testcontainers-go/modules/k3s v0.30.0 h1:Mk47J0WcLoY2ig72lPl+/w8GTPYbRCdHoWcPjV2mVr8=
|
||||
|
@ -1109,6 +1117,10 @@ github.com/transip/gotransip/v6 v6.23.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgs
|
|||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
|
||||
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
|
||||
|
@ -1141,10 +1153,10 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2
|
|||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f h1:cG+ehPRJSlqljSufLf1KXeXpUd1dLNjnzA18mZcB/O0=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 h1:2wzke3JH7OtN20WsNDZx2VH/TCmsbqtDEbXzjF+i05E=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997/go.mod h1:2CHKs/YGbCcNn/BPaCkEBwKz/FNCELi+MLILjR9RaTA=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e h1:jLIqA7M9qY31g/Nw/5htVD0DFbxmLnlFZcHKJiG3osI=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 h1:wtzLQJmghkSUb1YkeFphIh7ST7NNVDaVOJZSAJcjMdw=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4/go.mod h1:9d1MV6u4lK715YXnZceKqhP4L0bKBKmv4mSLnVSjJaM=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
|
@ -1173,43 +1185,44 @@ go.opentelemetry.io/collector/pdata v1.2.0 h1:N6VdyEFYJyoHIKqHd0F372eNVD5b+AbH0Z
|
|||
go.opentelemetry.io/collector/pdata v1.2.0/go.mod h1:mKXb6527Syb8PT4P9CZOJNbkuHOHjjGTZNNwSKESJhc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.49.0 h1:Jbr/9/jv1QpINge/fvJD4kUkW9/TqRNWU7H2GSK/Vb8=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.49.0/go.mod h1:aZTdrjEnMOr6ODgjCQ955njFMLRDo1IJdTNS+agSPjA=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.24.0 h1:cuwQmy9nGJi99fbwUfZSygCL3d347ddnSCWRuiVjhJ8=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.24.0/go.mod h1:7HbFx8Hiiuce72QONjbOtU+3QU+Scs9VOHZIrdmi1rw=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.24.0 h1:n4xwCdTx3pZqZs2CjS/CUZAs03y3dZcGhC/FepKtEUY=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.24.0/go.mod h1:k5wRxKRU2uXx2F8uNJ4TaonuEO/V7/5xoz7kdsDACT8=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.24.0 h1:CKtIfwSgDvJmaWsZROcHzONZgmQdMYn9mVYWypOWT5o=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.24.0/go.mod h1:Q5JA/Cfdy/ta+5VeEhrMJRWGyS6UNRwFbl+yS3W1h5I=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.24.0 h1:6lf4HoYefKDOTUSCatwkpzliUYihAvlN0omfpOn5IDs=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.24.0/go.mod h1:A406hNQ7A0EWsOFzWI1p53YaYQXe12C9f6wGHUxfh0g=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0 h1:f2jriWfOdldanBwS9jNBdeOKAQN7b4ugAMaNu1/1k9g=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0/go.mod h1:B+bcQI1yTY+N0vqMpoZbEN7+XU4tNM0DmUiOwebFJWI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.24.0 h1:mM8nKi6/iFQ0iqst80wDHU2ge198Ye/TfN0WBS5U24Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.24.0/go.mod h1:0PrIIzDteLSmNyxqcGYRL4mDIo8OTuBAOI/Bn1URxac=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.52.0 h1:xyRih6jMB0vroMSRdBE+uyKx20BclB/bybJt/LaCxmY=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.52.0/go.mod h1:L67tQgHPIOrZEraNfzidjljS9o+yLha0Y3UY4jXfs5w=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.27.0 h1:RJexJi4R0S9CpxzuhhzGlTCIpaaK9SJH9g9BFrCWfPE=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.27.0/go.mod h1:bqU5Ma1dEQ7VtRbPMUsH8UDTuTMiLJN4W+eUmyNVayc=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.27.0 h1:IjgxbomVrV9za6bRi8fWCNXENs0co37SZedQilP2hm0=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.27.0/go.mod h1:Dv9obQz25lCisDvvs4dy28UPh974CxkahRDUPsY7y9E=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.27.0 h1:tJPpZAEsihJgRTnXrPjY3rjED8Av3EJdi1kvKCi1yMc=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.27.0/go.mod h1:5uPAMHJnlTktQbCCdWSX5PfK8CocD25mycIsZV/iFiU=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.27.0 h1:xFPqk7ntRR87dqvl6RfeHiq9UlE8mPSuL6Dtr/zysL8=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.27.0/go.mod h1:nVLTPrDlSZPoVdeWRmpWBwxA73TYL6XLkC4bj72jvmg=
|
||||
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
|
||||
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 h1:bFgvUr3/O4PHj3VQcFEuYKvRZJX1SJDQ+11JXuSB3/w=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0/go.mod h1:xJntEd2KL6Qdg5lwp97HMLQDVeAhrYxmzFseAMDPQ8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=
|
||||
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
|
||||
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw=
|
||||
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
|
||||
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
|
||||
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
|
@ -1222,8 +1235,8 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+
|
|||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
|
||||
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
|
||||
go.uber.org/ratelimit v0.3.0 h1:IdZd9wqvFXnvLvSEBo0KPcGfkoBGNkpTHlrE3Rcjkjw=
|
||||
go.uber.org/ratelimit v0.3.0/go.mod h1:So5LG7CV1zWpY1sHe+DXTJqQvOx+FFPFaAs2SnoyBaI=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
|
@ -1251,14 +1264,14 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP
|
|||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -1267,8 +1280,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL
|
|||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
|
@ -1290,8 +1303,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -1333,14 +1346,14 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
|||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -1404,7 +1417,6 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -1426,8 +1438,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -1436,8 +1448,9 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
|||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
@ -1446,13 +1459,13 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -1498,8 +1511,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -1517,15 +1530,13 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
|
|||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY=
|
||||
google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI=
|
||||
google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk=
|
||||
google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
|
@ -1540,12 +1551,12 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
|
|||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
|
@ -1566,8 +1577,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
|
|||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
||||
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
@ -1583,8 +1594,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
|||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -1605,11 +1616,10 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.13 h1:r07CLALg18f/L1KIK1ZJdbirBV349UtYT1rDWGjnaTk=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.13/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.9.1 h1:3/QYzUazRCSE49d3sh1Q+X7IrDp/I7OqR/M7dKA0Oks=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.9.1/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
|
@ -1629,6 +1639,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
|
||||
|
|
|
@ -82,7 +82,7 @@ func (s *DockerSuite) TestWRRServer() {
|
|||
require.NoError(s.T(), err)
|
||||
|
||||
repartition := map[string]int{}
|
||||
for i := 0; i < 4; i++ {
|
||||
for range 4 {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
|
||||
req.Host = "my.wrr.host"
|
||||
require.NoError(s.T(), err)
|
||||
|
|
|
@ -48,6 +48,8 @@ spec:
|
|||
- --api.insecure
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.web8080.address=:8080
|
||||
- --entrypoints.traefik.address=:9000
|
||||
- --experimental.kubernetesgateway
|
||||
- --providers.kubernetesgateway.experimentalChannel
|
||||
- --providers.kubernetesgateway.statusaddress.service.namespace=traefik
|
||||
|
@ -55,10 +57,12 @@ spec:
|
|||
ports:
|
||||
- name: web
|
||||
containerPort: 80
|
||||
- name: admin
|
||||
containerPort: 8080
|
||||
- name: websecure
|
||||
containerPort: 443
|
||||
- name: web8080
|
||||
containerPort: 8080
|
||||
- name: traefik
|
||||
containerPort: 9000
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
|
@ -78,5 +82,8 @@ spec:
|
|||
name: websecure
|
||||
targetPort: websecure
|
||||
- port: 8080
|
||||
name: admin
|
||||
targetPort: admin
|
||||
name: web8080
|
||||
targetPort: web8080
|
||||
- port: 9000
|
||||
name: traefik
|
||||
targetPort: traefik
|
||||
|
|
|
@ -98,6 +98,67 @@ spec:
|
|||
description: Service defines an upstream HTTP service to proxy
|
||||
traffic to.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be
|
||||
sent to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname
|
||||
in the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for
|
||||
the health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme
|
||||
for the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status
|
||||
code of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -846,6 +907,11 @@ spec:
|
|||
This middleware compresses responses before sending them to the client, using gzip compression.
|
||||
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/
|
||||
properties:
|
||||
defaultEncoding:
|
||||
description: DefaultEncoding specifies the default encoding if
|
||||
the `Accept-Encoding` header is not in the request or contains
|
||||
a wildcard (`*`).
|
||||
type: string
|
||||
excludedContentTypes:
|
||||
description: |-
|
||||
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
||||
|
@ -919,6 +985,67 @@ spec:
|
|||
Service defines the reference to a Kubernetes Service that will serve the error page.
|
||||
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent
|
||||
to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname in
|
||||
the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the
|
||||
health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status code
|
||||
of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -1182,6 +1309,10 @@ spec:
|
|||
description: ContentSecurityPolicy defines the Content-Security-Policy
|
||||
header value.
|
||||
type: string
|
||||
contentSecurityPolicyReportOnly:
|
||||
description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only
|
||||
header value.
|
||||
type: string
|
||||
contentTypeNosniff:
|
||||
description: ContentTypeNosniff defines whether to add the X-Content-Type-Options
|
||||
header with the nosniff value.
|
||||
|
@ -2295,6 +2426,67 @@ spec:
|
|||
mirroring:
|
||||
description: Mirroring defines the Mirroring service configuration.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent to
|
||||
the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname in the
|
||||
Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the health
|
||||
check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the health
|
||||
check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status code
|
||||
of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -2314,6 +2506,67 @@ spec:
|
|||
items:
|
||||
description: MirrorService holds the mirror configuration.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent
|
||||
to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname
|
||||
in the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the
|
||||
health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status
|
||||
code of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
@ -2548,6 +2801,67 @@ spec:
|
|||
description: Service defines an upstream HTTP service to proxy
|
||||
traffic to.
|
||||
properties:
|
||||
healthCheck:
|
||||
description: Healthcheck defines health checks for ExternalName
|
||||
services.
|
||||
properties:
|
||||
followRedirects:
|
||||
description: |-
|
||||
FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
Default: true
|
||||
type: boolean
|
||||
headers:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Headers defines custom headers to be sent
|
||||
to the health check endpoint.
|
||||
type: object
|
||||
hostname:
|
||||
description: Hostname defines the value of hostname
|
||||
in the Host header of the health check request.
|
||||
type: string
|
||||
interval:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Interval defines the frequency of the health check calls.
|
||||
Default: 30s
|
||||
x-kubernetes-int-or-string: true
|
||||
method:
|
||||
description: Method defines the healthcheck method.
|
||||
type: string
|
||||
mode:
|
||||
description: |-
|
||||
Mode defines the health check mode.
|
||||
If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
Default: http
|
||||
type: string
|
||||
path:
|
||||
description: Path defines the server URL path for the
|
||||
health check endpoint.
|
||||
type: string
|
||||
port:
|
||||
description: Port defines the server URL port for the
|
||||
health check endpoint.
|
||||
type: integer
|
||||
scheme:
|
||||
description: Scheme replaces the server URL scheme for
|
||||
the health check endpoint.
|
||||
type: string
|
||||
status:
|
||||
description: Status defines the expected HTTP status
|
||||
code of the response to the health check request.
|
||||
type: integer
|
||||
timeout:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
Default: 5s
|
||||
x-kubernetes-int-or-string: true
|
||||
type: object
|
||||
kind:
|
||||
description: Kind defines the kind of the Service.
|
||||
enum:
|
||||
|
|
|
@ -166,7 +166,7 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
|||
k3sContainerIP, err := s.k3sContainer.ContainerIP(context.Background())
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://"+k3sContainerIP+":8080/api/entrypoints", 10*time.Second, try.BodyContains(`"name":"web"`))
|
||||
err = try.GetRequest("http://"+k3sContainerIP+":9000/api/entrypoints", 10*time.Second, try.BodyContains(`"name":"web"`))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
opts := ksuite.Options{
|
||||
|
@ -195,17 +195,30 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
|||
LatestObservedGenerationSet: 5 * time.Second,
|
||||
RequiredConsecutiveSuccesses: 0,
|
||||
},
|
||||
SupportedFeatures: sets.New(ksuite.SupportGateway,
|
||||
ksuite.SupportGatewayPort8080,
|
||||
ksuite.SupportHTTPRoute,
|
||||
ksuite.SupportHTTPRouteQueryParamMatching,
|
||||
ksuite.SupportHTTPRouteMethodMatching,
|
||||
ksuite.SupportHTTPRoutePortRedirect,
|
||||
ksuite.SupportHTTPRouteSchemeRedirect,
|
||||
ksuite.SupportHTTPRouteHostRewrite,
|
||||
ksuite.SupportHTTPRoutePathRewrite,
|
||||
ksuite.SupportHTTPRoutePathRedirect,
|
||||
),
|
||||
ExemptFeatures: sets.New(
|
||||
ksuite.SupportHTTPRouteRequestTimeout,
|
||||
ksuite.SupportHTTPRouteBackendTimeout,
|
||||
ksuite.SupportHTTPRouteResponseHeaderModification,
|
||||
ksuite.SupportHTTPRouteRequestMirror,
|
||||
ksuite.SupportHTTPRouteRequestMultipleMirrors,
|
||||
),
|
||||
EnableAllSupportedFeatures: false,
|
||||
RunTest: *k8sConformanceRunTest,
|
||||
// Until the feature are all supported, following tests are skipped.
|
||||
SkipTests: []string{
|
||||
tests.HTTPRouteListenerHostnameMatching.ShortName,
|
||||
tests.HTTPRouteInvalidCrossNamespaceParentRef.ShortName,
|
||||
tests.HTTPRouteMatchingAcrossRoutes.ShortName,
|
||||
tests.HTTPRoutePartiallyInvalidViaInvalidReferenceGrant.ShortName,
|
||||
tests.HTTPRoutePathMatchOrder.ShortName,
|
||||
tests.HTTPRouteHeaderMatching.ShortName,
|
||||
tests.HTTPRouteReferenceGrant.ShortName,
|
||||
tests.HTTPRouteMethodMatching.ShortName,
|
||||
tests.HTTPRouteQueryParamMatching.ShortName,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
version: "3.8"
|
||||
services:
|
||||
etcd:
|
||||
image: quay.io/coreos/etcd:v3.3.18
|
||||
image: quay.io/coreos/etcd:v3.5.14
|
||||
command:
|
||||
- etcd
|
||||
- --listen-client-urls
|
||||
|
|
|
@ -833,7 +833,7 @@ func (s *SimpleSuite) TestWRRServer() {
|
|||
require.NoError(s.T(), err)
|
||||
|
||||
repartition := map[string]int{}
|
||||
for i := 0; i < 4; i++ {
|
||||
for range 4 {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
|
|
36
integration/testdata/rawdata-gateway.json
vendored
36
integration/testdata/rawdata-gateway.json
vendored
|
@ -30,27 +30,27 @@
|
|||
"traefik"
|
||||
]
|
||||
},
|
||||
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06@kubernetesgateway": {
|
||||
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06@kubernetesgateway": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"service": "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||
"service": "default-http-app-1-my-gateway-web-0-wrr",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": 31,
|
||||
"priority": 100008,
|
||||
"status": "enabled",
|
||||
"using": [
|
||||
"web"
|
||||
]
|
||||
},
|
||||
"default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06@kubernetesgateway": {
|
||||
"default-http-app-1-my-https-gateway-websecure-0-1c0cf64bde37d9d0df06@kubernetesgateway": {
|
||||
"entryPoints": [
|
||||
"websecure"
|
||||
],
|
||||
"service": "default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06-wrr",
|
||||
"service": "default-http-app-1-my-https-gateway-websecure-0-wrr",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": 31,
|
||||
"priority": 100008,
|
||||
"tls": {},
|
||||
"status": "enabled",
|
||||
"using": [
|
||||
|
@ -96,7 +96,7 @@
|
|||
"dashboard@internal"
|
||||
]
|
||||
},
|
||||
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr@kubernetesgateway": {
|
||||
"default-http-app-1-my-gateway-web-0-wrr@kubernetesgateway": {
|
||||
"weighted": {
|
||||
"services": [
|
||||
{
|
||||
|
@ -107,10 +107,10 @@
|
|||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
"default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06@kubernetesgateway"
|
||||
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06@kubernetesgateway"
|
||||
]
|
||||
},
|
||||
"default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06-wrr@kubernetesgateway": {
|
||||
"default-http-app-1-my-https-gateway-websecure-0-wrr@kubernetesgateway": {
|
||||
"weighted": {
|
||||
"services": [
|
||||
{
|
||||
|
@ -121,7 +121,7 @@
|
|||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
"default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06@kubernetesgateway"
|
||||
"default-http-app-1-my-https-gateway-websecure-0-1c0cf64bde37d9d0df06@kubernetesgateway"
|
||||
]
|
||||
},
|
||||
"default-whoami-80@kubernetesgateway": {
|
||||
|
@ -150,11 +150,11 @@
|
|||
}
|
||||
},
|
||||
"tcpRouters": {
|
||||
"default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb@kubernetesgateway": {
|
||||
"default-tcp-app-1-my-tcp-gateway-footcp@kubernetesgateway": {
|
||||
"entryPoints": [
|
||||
"footcp"
|
||||
],
|
||||
"service": "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr-0",
|
||||
"service": "default-tcp-app-1-my-tcp-gateway-footcp-wrr-0",
|
||||
"rule": "HostSNI(`*`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": -1,
|
||||
|
@ -163,11 +163,11 @@
|
|||
"footcp"
|
||||
]
|
||||
},
|
||||
"default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb@kubernetesgateway": {
|
||||
"default-tcp-app-1-my-tls-gateway-footlsterminate@kubernetesgateway": {
|
||||
"entryPoints": [
|
||||
"footlsterminate"
|
||||
],
|
||||
"service": "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr-0",
|
||||
"service": "default-tcp-app-1-my-tls-gateway-footlsterminate-wrr-0",
|
||||
"rule": "HostSNI(`*`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": -1,
|
||||
|
@ -197,7 +197,7 @@
|
|||
}
|
||||
},
|
||||
"tcpServices": {
|
||||
"default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr-0@kubernetesgateway": {
|
||||
"default-tcp-app-1-my-tcp-gateway-footcp-wrr-0@kubernetesgateway": {
|
||||
"weighted": {
|
||||
"services": [
|
||||
{
|
||||
|
@ -208,10 +208,10 @@
|
|||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
"default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb@kubernetesgateway"
|
||||
"default-tcp-app-1-my-tcp-gateway-footcp@kubernetesgateway"
|
||||
]
|
||||
},
|
||||
"default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr-0@kubernetesgateway": {
|
||||
"default-tcp-app-1-my-tls-gateway-footlsterminate-wrr-0@kubernetesgateway": {
|
||||
"weighted": {
|
||||
"services": [
|
||||
{
|
||||
|
@ -222,7 +222,7 @@
|
|||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
"default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb@kubernetesgateway"
|
||||
"default-tcp-app-1-my-tls-gateway-footlsterminate@kubernetesgateway"
|
||||
]
|
||||
},
|
||||
"default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr-0@kubernetesgateway": {
|
||||
|
|
|
@ -162,7 +162,7 @@ func findTypedField(rType reflect.Type, node *parser.Node) (reflect.StructField,
|
|||
return reflect.StructField{}, false
|
||||
}
|
||||
|
||||
for i := 0; i < rType.NumField(); i++ {
|
||||
for i := range rType.NumField() {
|
||||
cField := rType.Field(i)
|
||||
|
||||
// ignore unexported fields.
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
browserXssFilter = true
|
||||
customBrowserXSSValue = "foobar"
|
||||
contentSecurityPolicy = "foobar"
|
||||
contentSecurityPolicyReportOnly = "foobar"
|
||||
publicKey = "foobar"
|
||||
referrerPolicy = "foobar"
|
||||
isDevelopment = true
|
||||
|
|
|
@ -254,7 +254,7 @@ type ServerHealthCheck struct {
|
|||
Interval ptypes.Duration `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty" export:"true"`
|
||||
Timeout ptypes.Duration `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty" export:"true"`
|
||||
Hostname string `json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"`
|
||||
FollowRedirects *bool `json:"followRedirects" toml:"followRedirects" yaml:"followRedirects" export:"true"`
|
||||
FollowRedirects *bool `json:"followRedirects,omitempty" toml:"followRedirects,omitempty" yaml:"followRedirects,omitempty" export:"true"`
|
||||
Headers map[string]string `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,8 @@ type Middleware struct {
|
|||
|
||||
// Gateway API HTTPRoute filters middlewares.
|
||||
RequestHeaderModifier *RequestHeaderModifier `json:"requestHeaderModifier,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"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -174,6 +176,8 @@ type Compress struct {
|
|||
// MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed.
|
||||
// Default: 1024.
|
||||
MinResponseBodyBytes int `json:"minResponseBodyBytes,omitempty" toml:"minResponseBodyBytes,omitempty" yaml:"minResponseBodyBytes,omitempty" export:"true"`
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -310,6 +314,8 @@ type Headers struct {
|
|||
CustomBrowserXSSValue string `json:"customBrowserXSSValue,omitempty" toml:"customBrowserXSSValue,omitempty" yaml:"customBrowserXSSValue,omitempty"`
|
||||
// ContentSecurityPolicy defines the Content-Security-Policy header value.
|
||||
ContentSecurityPolicy string `json:"contentSecurityPolicy,omitempty" toml:"contentSecurityPolicy,omitempty" yaml:"contentSecurityPolicy,omitempty"`
|
||||
// ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only header value.
|
||||
ContentSecurityPolicyReportOnly string `json:"contentSecurityPolicyReportOnly,omitempty" toml:"contentSecurityPolicyReportOnly,omitempty" yaml:"contentSecurityPolicyReportOnly,omitempty"`
|
||||
// PublicKey is the public key that implements HPKP to prevent MITM attacks with forged certificates.
|
||||
PublicKey string `json:"publicKey,omitempty" toml:"publicKey,omitempty" yaml:"publicKey,omitempty"`
|
||||
// ReferrerPolicy defines the Referrer-Policy header value.
|
||||
|
@ -373,6 +379,7 @@ func (h *Headers) HasSecureHeadersDefined() bool {
|
|||
h.BrowserXSSFilter ||
|
||||
h.CustomBrowserXSSValue != "" ||
|
||||
h.ContentSecurityPolicy != "" ||
|
||||
h.ContentSecurityPolicyReportOnly != "" ||
|
||||
h.PublicKey != "" ||
|
||||
h.ReferrerPolicy != "" ||
|
||||
(h.FeaturePolicy != nil && *h.FeaturePolicy != "") ||
|
||||
|
@ -685,3 +692,24 @@ type RequestHeaderModifier struct {
|
|||
Add map[string]string `json:"add,omitempty"`
|
||||
Remove []string `json:"remove,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// RequestRedirect holds the request redirect middleware configuration.
|
||||
type RequestRedirect struct {
|
||||
Scheme *string `json:"scheme,omitempty"`
|
||||
Hostname *string `json:"hostname,omitempty"`
|
||||
Port *string `json:"port,omitempty"`
|
||||
Path *string `json:"path,omitempty"`
|
||||
PathPrefix *string `json:"pathPrefix,omitempty"`
|
||||
StatusCode int `json:"statusCode,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// URLRewrite holds the URL rewrite middleware configuration.
|
||||
type URLRewrite struct {
|
||||
Hostname *string `json:"hostname,omitempty"`
|
||||
Path *string `json:"path,omitempty"`
|
||||
PathPrefix *string `json:"pathPrefix,omitempty"`
|
||||
}
|
||||
|
|
|
@ -864,6 +864,16 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
|
|||
*out = new(RequestHeaderModifier)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.RequestRedirect != nil {
|
||||
in, out := &in.RequestRedirect, &out.RequestRedirect
|
||||
*out = new(RequestRedirect)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.URLRewrite != nil {
|
||||
in, out := &in.URLRewrite, &out.URLRewrite
|
||||
*out = new(URLRewrite)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1107,6 +1117,47 @@ func (in *RequestHeaderModifier) DeepCopy() *RequestHeaderModifier {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RequestRedirect) DeepCopyInto(out *RequestRedirect) {
|
||||
*out = *in
|
||||
if in.Scheme != nil {
|
||||
in, out := &in.Scheme, &out.Scheme
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Hostname != nil {
|
||||
in, out := &in.Hostname, &out.Hostname
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Port != nil {
|
||||
in, out := &in.Port, &out.Port
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Path != nil {
|
||||
in, out := &in.Path, &out.Path
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.PathPrefix != nil {
|
||||
in, out := &in.PathPrefix, &out.PathPrefix
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestRedirect.
|
||||
func (in *RequestRedirect) DeepCopy() *RequestRedirect {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RequestRedirect)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResponseForwarding) DeepCopyInto(out *ResponseForwarding) {
|
||||
*out = *in
|
||||
|
@ -2161,6 +2212,37 @@ func (in *UDPWeightedRoundRobin) DeepCopy() *UDPWeightedRoundRobin {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *URLRewrite) DeepCopyInto(out *URLRewrite) {
|
||||
*out = *in
|
||||
if in.Hostname != nil {
|
||||
in, out := &in.Hostname, &out.Hostname
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Path != nil {
|
||||
in, out := &in.Path, &out.Path
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.PathPrefix != nil {
|
||||
in, out := &in.PathPrefix, &out.PathPrefix
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new URLRewrite.
|
||||
func (in *URLRewrite) DeepCopy() *URLRewrite {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(URLRewrite)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in Users) DeepCopyInto(out *Users) {
|
||||
{
|
||||
|
|
|
@ -63,6 +63,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.http.middlewares.Middleware8.headers.addvaryheader": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.browserxssfilter": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicy": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicyreportonly": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.contenttypenosniff": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.custombrowserxssvalue": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.customframeoptionsvalue": "foobar",
|
||||
|
@ -611,22 +612,23 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
SSLForceHost: Bool(true),
|
||||
STSSeconds: 42,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
ForceSTSHeader: true,
|
||||
FrameDeny: true,
|
||||
CustomFrameOptionsValue: "foobar",
|
||||
ContentTypeNosniff: true,
|
||||
BrowserXSSFilter: true,
|
||||
CustomBrowserXSSValue: "foobar",
|
||||
ContentSecurityPolicy: "foobar",
|
||||
PublicKey: "foobar",
|
||||
ReferrerPolicy: "foobar",
|
||||
FeaturePolicy: String("foobar"),
|
||||
PermissionsPolicy: "foobar",
|
||||
IsDevelopment: true,
|
||||
SSLForceHost: Bool(true),
|
||||
STSSeconds: 42,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
ForceSTSHeader: true,
|
||||
FrameDeny: true,
|
||||
CustomFrameOptionsValue: "foobar",
|
||||
ContentTypeNosniff: true,
|
||||
BrowserXSSFilter: true,
|
||||
CustomBrowserXSSValue: "foobar",
|
||||
ContentSecurityPolicy: "foobar",
|
||||
ContentSecurityPolicyReportOnly: "foobar",
|
||||
PublicKey: "foobar",
|
||||
ReferrerPolicy: "foobar",
|
||||
FeaturePolicy: String("foobar"),
|
||||
PermissionsPolicy: "foobar",
|
||||
IsDevelopment: true,
|
||||
},
|
||||
},
|
||||
"Middleware9": {
|
||||
|
@ -1134,22 +1136,23 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
SSLForceHost: Bool(true),
|
||||
STSSeconds: 42,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
ForceSTSHeader: true,
|
||||
FrameDeny: true,
|
||||
CustomFrameOptionsValue: "foobar",
|
||||
ContentTypeNosniff: true,
|
||||
BrowserXSSFilter: true,
|
||||
CustomBrowserXSSValue: "foobar",
|
||||
ContentSecurityPolicy: "foobar",
|
||||
PublicKey: "foobar",
|
||||
ReferrerPolicy: "foobar",
|
||||
FeaturePolicy: String("foobar"),
|
||||
PermissionsPolicy: "foobar",
|
||||
IsDevelopment: true,
|
||||
SSLForceHost: Bool(true),
|
||||
STSSeconds: 42,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
ForceSTSHeader: true,
|
||||
FrameDeny: true,
|
||||
CustomFrameOptionsValue: "foobar",
|
||||
ContentTypeNosniff: true,
|
||||
BrowserXSSFilter: true,
|
||||
CustomBrowserXSSValue: "foobar",
|
||||
ContentSecurityPolicy: "foobar",
|
||||
ContentSecurityPolicyReportOnly: "foobar",
|
||||
PublicKey: "foobar",
|
||||
ReferrerPolicy: "foobar",
|
||||
FeaturePolicy: String("foobar"),
|
||||
PermissionsPolicy: "foobar",
|
||||
IsDevelopment: true,
|
||||
},
|
||||
},
|
||||
"Middleware9": {
|
||||
|
@ -1299,6 +1302,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware8.Headers.AllowedHosts": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.BrowserXSSFilter": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicy": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicyReportOnly": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentTypeNosniff": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomBrowserXSSValue": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomFrameOptionsValue": "foobar",
|
||||
|
|
|
@ -334,8 +334,8 @@ func TestOpenTelemetry(t *testing.T) {
|
|||
// TODO: the len of startUnixNano is no supposed to be 20, it should be 19
|
||||
expectedConfig := []string{
|
||||
`({"name":"traefik_config_reloads_total","description":"Config reloads","unit":"1","sum":{"dataPoints":\[{"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_config_last_reload_success","description":"Last config reload success","unit":"ms","gauge":{"dataPoints":\[{"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
`({"name":"traefik_open_connections","description":"How many open connections exist, by entryPoint and protocol","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test"}},{"key":"protocol","value":{"stringValue":"TCP"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
`({"name":"traefik_config_last_reload_success","description":"Last config reload success","unit":"ms","gauge":{"dataPoints":\[{"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
`({"name":"traefik_open_connections","description":"How many open connections exist, by entryPoint and protocol","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test"}},{"key":"protocol","value":{"stringValue":"TCP"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
}
|
||||
|
||||
registry.ConfigReloadsCounter().Add(1)
|
||||
|
@ -345,7 +345,7 @@ func TestOpenTelemetry(t *testing.T) {
|
|||
tryAssertMessage(t, c, expectedConfig)
|
||||
|
||||
expectedTLSCerts := []string{
|
||||
`({"name":"traefik_tls_certs_not_after","description":"Certificate expiration timestamp","unit":"ms","gauge":{"dataPoints":\[{"attributes":\[{"key":"key","value":{"stringValue":"value"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
`({"name":"traefik_tls_certs_not_after","description":"Certificate expiration timestamp","unit":"ms","gauge":{"dataPoints":\[{"attributes":\[{"key":"key","value":{"stringValue":"value"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
}
|
||||
|
||||
registry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
|
||||
|
@ -389,7 +389,7 @@ func TestOpenTelemetry(t *testing.T) {
|
|||
`({"name":"traefik_service_requests_total","description":"How many HTTP requests processed on a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_service_requests_tls_total","description":"How many HTTP requests with TLS processed on a service, partitioned by TLS version and TLS cipher.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_service_request_duration_seconds","description":"How long it took to process the request on a service, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
|
||||
`({"name":"traefik_service_server_up","description":"service server is up, described by gauge value of 0 or 1.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"url","value":{"stringValue":"http://127.0.0.1"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
`({"name":"traefik_service_server_up","description":"service server is up, described by gauge value of 0 or 1.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"url","value":{"stringValue":"http://127.0.0.1"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
|
||||
`({"name":"traefik_service_requests_bytes_total","description":"The total size of requests in bytes received by a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
`({"name":"traefik_service_responses_bytes_total","description":"The total size of responses in bytes returned by a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
|
||||
}
|
||||
|
@ -463,7 +463,7 @@ func verifyMessage(msg string, expected []string) []error {
|
|||
re := regexp.MustCompile(pattern)
|
||||
match := re.FindStringSubmatch(msg)
|
||||
if len(match) != 2 {
|
||||
errs = append(errs, fmt.Errorf("Got %q %v, want %q", msg, match, pattern))
|
||||
errs = append(errs, fmt.Errorf("got %q %v, want %q", msg, match, pattern))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
|
|
|
@ -601,6 +601,7 @@ func (s *mockSpan) SetAttributes(kv ...attribute.KeyValue) {
|
|||
func (s *mockSpan) End(...trace.SpanEndOption) {}
|
||||
func (s *mockSpan) RecordError(_ error, _ ...trace.EventOption) {}
|
||||
func (s *mockSpan) AddEvent(_ string, _ ...trace.EventOption) {}
|
||||
func (s *mockSpan) AddLink(_ trace.Link) {}
|
||||
|
||||
func (s *mockSpan) SetName(name string) { s.name = name }
|
||||
|
||||
|
|
138
pkg/middlewares/compress/acceptencoding.go
Normal file
138
pkg/middlewares/compress/acceptencoding.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
package compress
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const acceptEncodingHeader = "Accept-Encoding"
|
||||
|
||||
const (
|
||||
brotliName = "br"
|
||||
gzipName = "gzip"
|
||||
zstdName = "zstd"
|
||||
identityName = "identity"
|
||||
wildcardName = "*"
|
||||
notAcceptable = "not_acceptable"
|
||||
)
|
||||
|
||||
type Encoding struct {
|
||||
Type string
|
||||
Weight *float64
|
||||
}
|
||||
|
||||
func getCompressionType(acceptEncoding []string, defaultType string) string {
|
||||
if defaultType == "" {
|
||||
// Keeps the pre-existing default inside Traefik.
|
||||
defaultType = brotliName
|
||||
}
|
||||
|
||||
encodings, hasWeight := parseAcceptEncoding(acceptEncoding)
|
||||
|
||||
if hasWeight {
|
||||
if len(encodings) == 0 {
|
||||
return identityName
|
||||
}
|
||||
|
||||
encoding := encodings[0]
|
||||
|
||||
if encoding.Type == identityName && encoding.Weight != nil && *encoding.Weight == 0 {
|
||||
return notAcceptable
|
||||
}
|
||||
|
||||
if encoding.Type == wildcardName && encoding.Weight != nil && *encoding.Weight == 0 {
|
||||
return notAcceptable
|
||||
}
|
||||
|
||||
if encoding.Type == wildcardName {
|
||||
return defaultType
|
||||
}
|
||||
|
||||
return encoding.Type
|
||||
}
|
||||
|
||||
for _, dt := range []string{zstdName, brotliName, gzipName} {
|
||||
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == dt }) {
|
||||
return dt
|
||||
}
|
||||
}
|
||||
|
||||
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == wildcardName }) {
|
||||
return defaultType
|
||||
}
|
||||
|
||||
return identityName
|
||||
}
|
||||
|
||||
func parseAcceptEncoding(acceptEncoding []string) ([]Encoding, bool) {
|
||||
var encodings []Encoding
|
||||
var hasWeight bool
|
||||
|
||||
for _, line := range acceptEncoding {
|
||||
for _, item := range strings.Split(strings.ReplaceAll(line, " ", ""), ",") {
|
||||
parsed := strings.SplitN(item, ";", 2)
|
||||
if len(parsed) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch parsed[0] {
|
||||
case zstdName, brotliName, gzipName, identityName, wildcardName:
|
||||
// supported encoding
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
var weight *float64
|
||||
if len(parsed) > 1 && strings.HasPrefix(parsed[1], "q=") {
|
||||
w, _ := strconv.ParseFloat(strings.TrimPrefix(parsed[1], "q="), 64)
|
||||
|
||||
weight = &w
|
||||
hasWeight = true
|
||||
}
|
||||
|
||||
encodings = append(encodings, Encoding{
|
||||
Type: parsed[0],
|
||||
Weight: weight,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(encodings, compareEncoding)
|
||||
|
||||
return encodings, hasWeight
|
||||
}
|
||||
|
||||
func compareEncoding(a, b Encoding) int {
|
||||
lhs, rhs := a.Weight, b.Weight
|
||||
|
||||
if lhs == nil && rhs == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
if lhs == nil && *rhs == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if lhs == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if rhs == nil && *lhs == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
if rhs == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
if *lhs < *rhs {
|
||||
return 1
|
||||
}
|
||||
|
||||
if *lhs > *rhs {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
156
pkg/middlewares/compress/acceptencoding_test.go
Normal file
156
pkg/middlewares/compress/acceptencoding_test.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package compress
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_getCompressionType(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values []string
|
||||
defaultType string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "br > gzip (no weight)",
|
||||
values: []string{"gzip, br"},
|
||||
expected: brotliName,
|
||||
},
|
||||
{
|
||||
desc: "zstd > br > gzip (no weight)",
|
||||
values: []string{"zstd, gzip, br"},
|
||||
expected: zstdName,
|
||||
},
|
||||
{
|
||||
desc: "known compression type (no weight)",
|
||||
values: []string{"compress, gzip"},
|
||||
expected: gzipName,
|
||||
},
|
||||
{
|
||||
desc: "unknown compression type (no weight), no encoding",
|
||||
values: []string{"compress, rar"},
|
||||
expected: identityName,
|
||||
},
|
||||
{
|
||||
desc: "wildcard return the default compression type",
|
||||
values: []string{"*"},
|
||||
expected: brotliName,
|
||||
},
|
||||
{
|
||||
desc: "wildcard return the custom default compression type",
|
||||
values: []string{"*"},
|
||||
defaultType: "foo",
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
desc: "follows weight",
|
||||
values: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"},
|
||||
expected: gzipName,
|
||||
},
|
||||
{
|
||||
desc: "ignore unknown compression type",
|
||||
values: []string{"compress;q=1.0, gzip;q=0.5"},
|
||||
expected: gzipName,
|
||||
},
|
||||
{
|
||||
desc: "fallback on non-zero compression type",
|
||||
values: []string{"compress;q=1.0, gzip, identity;q=0"},
|
||||
expected: gzipName,
|
||||
},
|
||||
{
|
||||
desc: "not acceptable (identity)",
|
||||
values: []string{"compress;q=1.0, identity;q=0"},
|
||||
expected: notAcceptable,
|
||||
},
|
||||
{
|
||||
desc: "not acceptable (wildcard)",
|
||||
values: []string{"compress;q=1.0, *;q=0"},
|
||||
expected: notAcceptable,
|
||||
},
|
||||
{
|
||||
desc: "non-zero is higher than 0",
|
||||
values: []string{"gzip, *;q=0"},
|
||||
expected: gzipName,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
encodingType := getCompressionType(test.values, test.defaultType)
|
||||
|
||||
assert.Equal(t, test.expected, encodingType)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseAcceptEncoding(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
values []string
|
||||
expected []Encoding
|
||||
assertWeight assert.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
desc: "weight",
|
||||
values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"},
|
||||
expected: []Encoding{
|
||||
{Type: brotliName, Weight: ptr[float64](1)},
|
||||
{Type: zstdName, Weight: ptr(0.9)},
|
||||
{Type: gzipName, Weight: ptr(0.8)},
|
||||
{Type: wildcardName, Weight: ptr(0.1)},
|
||||
},
|
||||
assertWeight: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "mixed",
|
||||
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
|
||||
expected: []Encoding{
|
||||
{Type: brotliName, Weight: ptr[float64](1)},
|
||||
{Type: zstdName},
|
||||
{Type: gzipName},
|
||||
{Type: wildcardName, Weight: ptr[float64](0)},
|
||||
},
|
||||
assertWeight: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "no weight",
|
||||
values: []string{"zstd, gzip, br, *"},
|
||||
expected: []Encoding{
|
||||
{Type: zstdName},
|
||||
{Type: gzipName},
|
||||
{Type: brotliName},
|
||||
{Type: wildcardName},
|
||||
},
|
||||
assertWeight: assert.False,
|
||||
},
|
||||
{
|
||||
desc: "weight and identity",
|
||||
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
|
||||
expected: []Encoding{
|
||||
{Type: gzipName, Weight: ptr[float64](1)},
|
||||
{Type: identityName, Weight: ptr(0.5)},
|
||||
{Type: wildcardName, Weight: ptr[float64](0)},
|
||||
},
|
||||
assertWeight: assert.True,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aes, hasWeight := parseAcceptEncoding(test.values)
|
||||
|
||||
assert.Equal(t, test.expected, aes)
|
||||
test.assertWeight(t, hasWeight)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ptr[T any](t T) *T {
|
||||
return &t
|
||||
}
|
|
@ -7,12 +7,10 @@ import (
|
|||
"mime"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/compress/brotli"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
|
@ -24,14 +22,16 @@ const DefaultMinSize = 1024
|
|||
|
||||
// Compress is a middleware that allows to compress the response.
|
||||
type compress struct {
|
||||
next http.Handler
|
||||
name string
|
||||
excludes []string
|
||||
includes []string
|
||||
minSize int
|
||||
next http.Handler
|
||||
name string
|
||||
excludes []string
|
||||
includes []string
|
||||
minSize int
|
||||
defaultEncoding string
|
||||
|
||||
brotliHandler http.Handler
|
||||
gzipHandler http.Handler
|
||||
zstdHandler http.Handler
|
||||
}
|
||||
|
||||
// New creates a new compress middleware.
|
||||
|
@ -68,15 +68,22 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str
|
|||
}
|
||||
|
||||
c := &compress{
|
||||
next: next,
|
||||
name: name,
|
||||
excludes: excludes,
|
||||
includes: includes,
|
||||
minSize: minSize,
|
||||
next: next,
|
||||
name: name,
|
||||
excludes: excludes,
|
||||
includes: includes,
|
||||
minSize: minSize,
|
||||
defaultEncoding: conf.DefaultEncoding,
|
||||
}
|
||||
|
||||
var err error
|
||||
c.brotliHandler, err = c.newBrotliHandler()
|
||||
|
||||
c.zstdHandler, err = c.newCompressionHandler(zstdName, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.brotliHandler, err = c.newCompressionHandler(brotliName, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -109,25 +116,35 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Client doesn't specify a preferred encoding, for compatibility don't encode the request
|
||||
// See https://github.com/traefik/traefik/issues/9734
|
||||
acceptEncoding, ok := req.Header["Accept-Encoding"]
|
||||
acceptEncoding, ok := req.Header[acceptEncodingHeader]
|
||||
if !ok {
|
||||
if c.defaultEncoding != "" {
|
||||
// RFC says: "If no Accept-Encoding header field is in the request, any content coding is considered acceptable by the user agent."
|
||||
// https://www.rfc-editor.org/rfc/rfc9110#field.accept-encoding
|
||||
c.chooseHandler(c.defaultEncoding, rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
// Client doesn't specify a preferred encoding, for compatibility don't encode the request
|
||||
// See https://github.com/traefik/traefik/issues/9734
|
||||
c.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
if encodingAccepts(acceptEncoding, "br") {
|
||||
c.chooseHandler(getCompressionType(acceptEncoding, c.defaultEncoding), rw, req)
|
||||
}
|
||||
|
||||
func (c *compress) chooseHandler(typ string, rw http.ResponseWriter, req *http.Request) {
|
||||
switch typ {
|
||||
case zstdName:
|
||||
c.zstdHandler.ServeHTTP(rw, req)
|
||||
case brotliName:
|
||||
c.brotliHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
if encodingAccepts(acceptEncoding, "gzip") {
|
||||
case gzipName:
|
||||
c.gzipHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
default:
|
||||
c.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
c.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (c *compress) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
|
@ -157,34 +174,13 @@ func (c *compress) newGzipHandler() (http.Handler, error) {
|
|||
return wrapper(c.next), nil
|
||||
}
|
||||
|
||||
func (c *compress) newBrotliHandler() (http.Handler, error) {
|
||||
cfg := brotli.Config{MinSize: c.minSize}
|
||||
func (c *compress) newCompressionHandler(algo string, middlewareName string) (http.Handler, error) {
|
||||
cfg := Config{MinSize: c.minSize, Algorithm: algo, MiddlewareName: middlewareName}
|
||||
if len(c.includes) > 0 {
|
||||
cfg.IncludedContentTypes = c.includes
|
||||
} else {
|
||||
cfg.ExcludedContentTypes = c.excludes
|
||||
}
|
||||
|
||||
wrapper, err := brotli.NewWrapper(cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new brotli wrapper: %w", err)
|
||||
}
|
||||
|
||||
return wrapper(c.next), nil
|
||||
}
|
||||
|
||||
func encodingAccepts(acceptEncoding []string, typ string) bool {
|
||||
for _, ae := range acceptEncoding {
|
||||
for _, e := range strings.Split(ae, ",") {
|
||||
parsed := strings.Split(strings.TrimSpace(e), ";")
|
||||
if len(parsed) == 0 {
|
||||
continue
|
||||
}
|
||||
if parsed[0] == typ || parsed[0] == "*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return NewCompressionHandler(cfg, c.next)
|
||||
}
|
||||
|
|
|
@ -18,12 +18,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
acceptEncodingHeader = "Accept-Encoding"
|
||||
contentEncodingHeader = "Content-Encoding"
|
||||
contentTypeHeader = "Content-Type"
|
||||
varyHeader = "Vary"
|
||||
gzipValue = "gzip"
|
||||
brotliValue = "br"
|
||||
)
|
||||
|
||||
func TestNegotiation(t *testing.T) {
|
||||
|
@ -44,32 +41,52 @@ func TestNegotiation(t *testing.T) {
|
|||
{
|
||||
desc: "accept any header",
|
||||
acceptEncHeader: "*",
|
||||
expEncoding: "br",
|
||||
expEncoding: brotliName,
|
||||
},
|
||||
{
|
||||
desc: "gzip accept header",
|
||||
acceptEncHeader: "gzip",
|
||||
expEncoding: "gzip",
|
||||
expEncoding: gzipName,
|
||||
},
|
||||
{
|
||||
desc: "br accept header",
|
||||
acceptEncHeader: "br",
|
||||
expEncoding: "br",
|
||||
expEncoding: brotliName,
|
||||
},
|
||||
{
|
||||
desc: "multi accept header, prefer br",
|
||||
acceptEncHeader: "br;q=0.8, gzip;q=0.6",
|
||||
expEncoding: "br",
|
||||
expEncoding: brotliName,
|
||||
},
|
||||
{
|
||||
desc: "multi accept header, prefer br",
|
||||
desc: "multi accept header, prefer gzip",
|
||||
acceptEncHeader: "gzip;q=1.0, br;q=0.8",
|
||||
expEncoding: "br",
|
||||
expEncoding: gzipName,
|
||||
},
|
||||
{
|
||||
desc: "multi accept header list, prefer br",
|
||||
acceptEncHeader: "gzip, br",
|
||||
expEncoding: "br",
|
||||
expEncoding: brotliName,
|
||||
},
|
||||
{
|
||||
desc: "zstd accept header",
|
||||
acceptEncHeader: "zstd",
|
||||
expEncoding: zstdName,
|
||||
},
|
||||
{
|
||||
desc: "multi accept header, prefer zstd",
|
||||
acceptEncHeader: "zstd;q=0.9, br;q=0.8, gzip;q=0.6",
|
||||
expEncoding: zstdName,
|
||||
},
|
||||
{
|
||||
desc: "multi accept header, prefer gzip",
|
||||
acceptEncHeader: "gzip;q=1.0, br;q=0.8, zstd;q=0.7",
|
||||
expEncoding: gzipName,
|
||||
},
|
||||
{
|
||||
desc: "multi accept header list, prefer zstd",
|
||||
acceptEncHeader: "gzip, br, zstd",
|
||||
expEncoding: zstdName,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -98,7 +115,7 @@ func TestNegotiation(t *testing.T) {
|
|||
|
||||
func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
|
||||
baseBody := generateBytes(gzhttp.DefaultMinSize)
|
||||
|
||||
|
@ -112,7 +129,7 @@ func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) {
|
|||
rw := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, gzipValue, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, gzipName, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
|
||||
|
||||
gr, err := gzip.NewReader(rw.Body)
|
||||
|
@ -125,11 +142,11 @@ func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) {
|
|||
|
||||
func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
|
||||
fakeCompressedBody := generateBytes(gzhttp.DefaultMinSize)
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Add(contentEncodingHeader, gzipValue)
|
||||
rw.Header().Add(contentEncodingHeader, gzipName)
|
||||
rw.Header().Add(varyHeader, acceptEncodingHeader)
|
||||
_, err := rw.Write(fakeCompressedBody)
|
||||
if err != nil {
|
||||
|
@ -142,7 +159,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
|
|||
rw := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, gzipValue, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, gzipName, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
|
||||
|
||||
assert.EqualValues(t, rw.Body.Bytes(), fakeCompressedBody)
|
||||
|
@ -225,7 +242,7 @@ func TestShouldNotCompressWhenEmptyAcceptEncodingHeader(t *testing.T) {
|
|||
|
||||
func TestShouldNotCompressHeadRequest(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodHead, "http://localhost", nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
|
||||
fakeBody := generateBytes(gzhttp.DefaultMinSize)
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
|
@ -301,7 +318,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
if test.reqContentType != "" {
|
||||
req.Header.Add(contentTypeHeader, test.reqContentType)
|
||||
}
|
||||
|
@ -352,7 +369,7 @@ func TestShouldCompressWhenSpecificContentType(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set(contentTypeHeader, test.respContentType)
|
||||
|
@ -368,7 +385,7 @@ func TestShouldCompressWhenSpecificContentType(t *testing.T) {
|
|||
rw := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, gzipValue, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, gzipName, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
|
||||
assert.NotEqualValues(t, rw.Body.Bytes(), baseBody)
|
||||
})
|
||||
|
@ -386,7 +403,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
|
|||
{
|
||||
name: "when content already compressed",
|
||||
handler: http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Add(contentEncodingHeader, gzipValue)
|
||||
rw.Header().Add(contentEncodingHeader, gzipName)
|
||||
rw.Header().Add(varyHeader, acceptEncodingHeader)
|
||||
_, err := rw.Write(fakeCompressedBody)
|
||||
if err != nil {
|
||||
|
@ -398,7 +415,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
|
|||
{
|
||||
name: "when content already compressed and status code Created",
|
||||
handler: http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Add(contentEncodingHeader, gzipValue)
|
||||
rw.Header().Add(contentEncodingHeader, gzipName)
|
||||
rw.Header().Add(varyHeader, acceptEncodingHeader)
|
||||
rw.WriteHeader(http.StatusCreated)
|
||||
_, err := rw.Write(fakeCompressedBody)
|
||||
|
@ -419,14 +436,14 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
|
|||
defer ts.Close()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, resp.StatusCode)
|
||||
|
||||
assert.Equal(t, gzipValue, resp.Header.Get(contentEncodingHeader))
|
||||
assert.Equal(t, gzipName, resp.Header.Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, resp.Header.Get(varyHeader))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
@ -438,7 +455,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
|
|||
|
||||
func TestShouldWriteHeaderWhenFlush(t *testing.T) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Add(contentEncodingHeader, gzipValue)
|
||||
rw.Header().Add(contentEncodingHeader, gzipName)
|
||||
rw.Header().Add(varyHeader, acceptEncodingHeader)
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
rw.(http.Flusher).Flush()
|
||||
|
@ -454,14 +471,14 @@ func TestShouldWriteHeaderWhenFlush(t *testing.T) {
|
|||
defer ts.Close()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||||
|
||||
assert.Equal(t, gzipValue, resp.Header.Get(contentEncodingHeader))
|
||||
assert.Equal(t, gzipName, resp.Header.Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, resp.Header.Get(varyHeader))
|
||||
}
|
||||
|
||||
|
@ -505,14 +522,14 @@ func TestIntegrationShouldCompress(t *testing.T) {
|
|||
defer ts.Close()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, resp.StatusCode)
|
||||
|
||||
assert.Equal(t, gzipValue, resp.Header.Get(contentEncodingHeader))
|
||||
assert.Equal(t, gzipName, resp.Header.Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, resp.Header.Get(varyHeader))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
@ -547,7 +564,7 @@ func TestMinResponseBodyBytes(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if _, err := rw.Write(fakeBody); err != nil {
|
||||
|
@ -562,7 +579,7 @@ func TestMinResponseBodyBytes(t *testing.T) {
|
|||
handler.ServeHTTP(rw, req)
|
||||
|
||||
if test.expectedCompression {
|
||||
assert.Equal(t, gzipValue, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, gzipName, rw.Header().Get(contentEncodingHeader))
|
||||
assert.NotEqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||
return
|
||||
}
|
||||
|
@ -636,7 +653,7 @@ func Test1xxResponses(t *testing.T) {
|
|||
},
|
||||
}
|
||||
req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
req.Header.Add(acceptEncodingHeader, gzipName)
|
||||
|
||||
res, err := frontendClient.Do(req)
|
||||
assert.NoError(t, err)
|
||||
|
@ -648,7 +665,7 @@ func Test1xxResponses(t *testing.T) {
|
|||
}
|
||||
checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script", "</foo.js>; rel=preload; as=script"}, res.Header["Link"])
|
||||
|
||||
assert.Equal(t, gzipValue, res.Header.Get(contentEncodingHeader))
|
||||
assert.Equal(t, gzipName, res.Header.Get(contentEncodingHeader))
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
assert.NotEqualValues(t, body, fakeBody)
|
||||
}
|
||||
|
@ -730,7 +747,7 @@ func runBenchmark(b *testing.B, req *http.Request, handler http.Handler) {
|
|||
b.Fatalf("Expected 200 but got %d", code)
|
||||
}
|
||||
|
||||
assert.Equal(b, gzipValue, res.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(b, gzipName, res.Header().Get(contentEncodingHeader))
|
||||
}
|
||||
|
||||
func generateBytes(length int) []byte {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package brotli
|
||||
package compress
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
@ -10,6 +10,9 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -30,10 +33,26 @@ type Config struct {
|
|||
IncludedContentTypes []string
|
||||
// MinSize is the minimum size (in bytes) required to enable compression.
|
||||
MinSize int
|
||||
// Algorithm used for the compression (currently Brotli and Zstandard)
|
||||
Algorithm string
|
||||
// MiddlewareName use for logging purposes
|
||||
MiddlewareName string
|
||||
}
|
||||
|
||||
// NewWrapper returns a new Brotli compressing wrapper.
|
||||
func NewWrapper(cfg Config) (func(http.Handler) http.HandlerFunc, error) {
|
||||
// CompressionHandler handles Brolti and Zstd compression.
|
||||
type CompressionHandler struct {
|
||||
cfg Config
|
||||
excludedContentTypes []parsedContentType
|
||||
includedContentTypes []parsedContentType
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// NewCompressionHandler returns a new compressing handler.
|
||||
func NewCompressionHandler(cfg Config, next http.Handler) (http.Handler, error) {
|
||||
if cfg.Algorithm == "" {
|
||||
return nil, errors.New("compression algorithm undefined")
|
||||
}
|
||||
|
||||
if cfg.MinSize < 0 {
|
||||
return nil, errors.New("minimum size must be greater than or equal to zero")
|
||||
}
|
||||
|
@ -62,30 +81,89 @@ func NewWrapper(cfg Config) (func(http.Handler) http.HandlerFunc, error) {
|
|||
includedContentTypes = append(includedContentTypes, parsedContentType{mediaType, params})
|
||||
}
|
||||
|
||||
return func(h http.Handler) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Add(vary, acceptEncoding)
|
||||
|
||||
brw := &responseWriter{
|
||||
rw: rw,
|
||||
bw: brotli.NewWriter(rw),
|
||||
minSize: cfg.MinSize,
|
||||
statusCode: http.StatusOK,
|
||||
excludedContentTypes: excludedContentTypes,
|
||||
includedContentTypes: includedContentTypes,
|
||||
}
|
||||
defer brw.close()
|
||||
|
||||
h.ServeHTTP(brw, r)
|
||||
}
|
||||
return &CompressionHandler{
|
||||
cfg: cfg,
|
||||
excludedContentTypes: excludedContentTypes,
|
||||
includedContentTypes: includedContentTypes,
|
||||
next: next,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CompressionHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Add(vary, acceptEncoding)
|
||||
|
||||
compressionWriter, err := newCompressionWriter(c.cfg.Algorithm, rw)
|
||||
if err != nil {
|
||||
logger := middlewares.GetLogger(r.Context(), c.cfg.MiddlewareName, typeName)
|
||||
logMessage := fmt.Sprintf("create compression handler: %v", err)
|
||||
logger.Debug().Msg(logMessage)
|
||||
observability.SetStatusErrorf(r.Context(), logMessage)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
responseWriter := &responseWriter{
|
||||
rw: rw,
|
||||
compressionWriter: compressionWriter,
|
||||
minSize: c.cfg.MinSize,
|
||||
statusCode: http.StatusOK,
|
||||
excludedContentTypes: c.excludedContentTypes,
|
||||
includedContentTypes: c.includedContentTypes,
|
||||
}
|
||||
defer responseWriter.close()
|
||||
|
||||
c.next.ServeHTTP(responseWriter, r)
|
||||
}
|
||||
|
||||
type compression interface {
|
||||
// Write data to the encoder.
|
||||
// Input data will be buffered and as the buffer fills up
|
||||
// content will be compressed and written to the output.
|
||||
// When done writing, use Close to flush the remaining output
|
||||
// and write CRC if requested.
|
||||
Write(p []byte) (n int, err error)
|
||||
// Flush will send the currently written data to output
|
||||
// and block until everything has been written.
|
||||
// This should only be used on rare occasions where pushing the currently queued data is critical.
|
||||
Flush() error
|
||||
// Close closes the underlying writers if/when appropriate.
|
||||
// Note that the compressed writer should not be closed if we never used it,
|
||||
// as it would otherwise send some extra "end of compression" bytes.
|
||||
// Close also makes sure to flush whatever was left to write from the buffer.
|
||||
Close() error
|
||||
}
|
||||
|
||||
type compressionWriter struct {
|
||||
compression
|
||||
alg string
|
||||
}
|
||||
|
||||
func newCompressionWriter(algo string, in io.Writer) (*compressionWriter, error) {
|
||||
switch algo {
|
||||
case brotliName:
|
||||
return &compressionWriter{compression: brotli.NewWriter(in), alg: algo}, nil
|
||||
|
||||
case zstdName:
|
||||
writer, err := zstd.NewWriter(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating zstd writer: %w", err)
|
||||
}
|
||||
return &compressionWriter{compression: writer, alg: algo}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown compression algo: %s", algo)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compressionWriter) ContentEncoding() string {
|
||||
return c.alg
|
||||
}
|
||||
|
||||
// TODO: check whether we want to implement content-type sniffing (as gzip does)
|
||||
// TODO: check whether we should support Accept-Ranges (as gzip does, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges)
|
||||
type responseWriter struct {
|
||||
rw http.ResponseWriter
|
||||
bw *brotli.Writer
|
||||
rw http.ResponseWriter
|
||||
compressionWriter *compressionWriter
|
||||
|
||||
minSize int
|
||||
excludedContentTypes []parsedContentType
|
||||
|
@ -133,7 +211,7 @@ func (r *responseWriter) Write(p []byte) (int, error) {
|
|||
// We are now in compression cruise mode until the end of times.
|
||||
if r.compressionStarted {
|
||||
// If compressionStarted we assume we have sent headers already
|
||||
return r.bw.Write(p)
|
||||
return r.compressionWriter.Write(p)
|
||||
}
|
||||
|
||||
// If we detect a contentEncoding, we know we are never going to compress.
|
||||
|
@ -160,6 +238,7 @@ func (r *responseWriter) Write(p []byte) (int, error) {
|
|||
}
|
||||
if !found {
|
||||
r.compressionDisabled = true
|
||||
r.rw.WriteHeader(r.statusCode)
|
||||
return r.rw.Write(p)
|
||||
}
|
||||
}
|
||||
|
@ -167,6 +246,7 @@ func (r *responseWriter) Write(p []byte) (int, error) {
|
|||
for _, excludedContentType := range r.excludedContentTypes {
|
||||
if excludedContentType.equals(mediaType, params) {
|
||||
r.compressionDisabled = true
|
||||
r.rw.WriteHeader(r.statusCode)
|
||||
return r.rw.Write(p)
|
||||
}
|
||||
}
|
||||
|
@ -185,13 +265,13 @@ func (r *responseWriter) Write(p []byte) (int, error) {
|
|||
// Since we know we are going to compress we will never be able to know the actual length.
|
||||
r.rw.Header().Del(contentLength)
|
||||
|
||||
r.rw.Header().Set(contentEncoding, "br")
|
||||
r.rw.Header().Set(contentEncoding, r.compressionWriter.ContentEncoding())
|
||||
r.rw.WriteHeader(r.statusCode)
|
||||
r.headersSent = true
|
||||
|
||||
// Start with sending what we have previously buffered, before actually writing
|
||||
// the bytes in argument.
|
||||
n, err := r.bw.Write(r.buf)
|
||||
n, err := r.compressionWriter.Write(r.buf)
|
||||
if err != nil {
|
||||
r.buf = r.buf[n:]
|
||||
// Return zero because we haven't taken care of the bytes in argument yet.
|
||||
|
@ -210,7 +290,7 @@ func (r *responseWriter) Write(p []byte) (int, error) {
|
|||
r.buf = r.buf[:0]
|
||||
|
||||
// Now that we emptied the buffer, we can actually write the given bytes.
|
||||
return r.bw.Write(p)
|
||||
return r.compressionWriter.Write(p)
|
||||
}
|
||||
|
||||
// Flush flushes data to the appropriate underlying writer(s), although it does
|
||||
|
@ -248,7 +328,7 @@ func (r *responseWriter) Flush() {
|
|||
// we have to do it ourselves.
|
||||
defer func() {
|
||||
// because we also ignore the error returned by Write anyway
|
||||
_ = r.bw.Flush()
|
||||
_ = r.compressionWriter.Flush()
|
||||
|
||||
if rw, ok := r.rw.(http.Flusher); ok {
|
||||
rw.Flush()
|
||||
|
@ -256,7 +336,7 @@ func (r *responseWriter) Flush() {
|
|||
}()
|
||||
|
||||
// We empty whatever is left of the buffer that Write never took care of.
|
||||
n, err := r.bw.Write(r.buf)
|
||||
n, err := r.compressionWriter.Write(r.buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -311,7 +391,7 @@ func (r *responseWriter) close() error {
|
|||
|
||||
if len(r.buf) == 0 {
|
||||
// If we got here we know compression has started, so we can safely flush on bw.
|
||||
return r.bw.Close()
|
||||
return r.compressionWriter.Close()
|
||||
}
|
||||
|
||||
// There is still data in the buffer, because we never reached minSize (to
|
||||
|
@ -329,16 +409,16 @@ func (r *responseWriter) close() error {
|
|||
|
||||
// There is still data in the buffer, simply because Write did not take care of it all.
|
||||
// We flush it to the compressed writer.
|
||||
n, err := r.bw.Write(r.buf)
|
||||
n, err := r.compressionWriter.Write(r.buf)
|
||||
if err != nil {
|
||||
r.bw.Close()
|
||||
r.compressionWriter.Close()
|
||||
return err
|
||||
}
|
||||
if n < len(r.buf) {
|
||||
r.bw.Close()
|
||||
r.compressionWriter.Close()
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return r.bw.Close()
|
||||
return r.compressionWriter.Close()
|
||||
}
|
||||
|
||||
// parsedContentType is the parsed representation of one of the inputs to ContentTypes.
|
|
@ -1,4 +1,4 @@
|
|||
package brotli
|
||||
package compress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -9,6 +9,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -19,44 +20,107 @@ var (
|
|||
)
|
||||
|
||||
func Test_Vary(t *testing.T) {
|
||||
h := newTestHandler(t, smallTestBody)
|
||||
testCases := []struct {
|
||||
desc string
|
||||
h http.Handler
|
||||
acceptEncoding string
|
||||
}{
|
||||
{
|
||||
desc: "brotli",
|
||||
h: newTestBrotliHandler(t, smallTestBody),
|
||||
acceptEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "zstd",
|
||||
h: newTestZstandardHandler(t, smallTestBody),
|
||||
acceptEncoding: "zstd",
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, test.acceptEncoding)
|
||||
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
assert.Equal(t, acceptEncoding, rw.Header().Get(vary))
|
||||
rw := httptest.NewRecorder()
|
||||
test.h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
assert.Equal(t, acceptEncoding, rw.Header().Get(vary))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SmallBodyNoCompression(t *testing.T) {
|
||||
h := newTestHandler(t, smallTestBody)
|
||||
testCases := []struct {
|
||||
desc string
|
||||
h http.Handler
|
||||
acceptEncoding string
|
||||
}{
|
||||
{
|
||||
desc: "brotli",
|
||||
h: newTestBrotliHandler(t, smallTestBody),
|
||||
acceptEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "zstd",
|
||||
h: newTestZstandardHandler(t, smallTestBody),
|
||||
acceptEncoding: "zstd",
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, test.acceptEncoding)
|
||||
|
||||
// With less than 1024 bytes the response should not be compressed.
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
assert.Empty(t, rw.Header().Get(contentEncoding))
|
||||
assert.Equal(t, smallTestBody, rw.Body.Bytes())
|
||||
rw := httptest.NewRecorder()
|
||||
test.h.ServeHTTP(rw, req)
|
||||
|
||||
// With less than 1024 bytes the response should not be compressed.
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
assert.Empty(t, rw.Header().Get(contentEncoding))
|
||||
assert.Equal(t, smallTestBody, rw.Body.Bytes())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AlreadyCompressed(t *testing.T) {
|
||||
h := newTestHandler(t, bigTestBody)
|
||||
testCases := []struct {
|
||||
desc string
|
||||
h http.Handler
|
||||
acceptEncoding string
|
||||
}{
|
||||
{
|
||||
desc: "brotli",
|
||||
h: newTestBrotliHandler(t, bigTestBody),
|
||||
acceptEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "zstd",
|
||||
h: newTestZstandardHandler(t, bigTestBody),
|
||||
acceptEncoding: "zstd",
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/compressed", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/compressed", nil)
|
||||
req.Header.Set(acceptEncoding, test.acceptEncoding)
|
||||
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
assert.Equal(t, bigTestBody, rw.Body.Bytes())
|
||||
rw := httptest.NewRecorder()
|
||||
test.h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
assert.Equal(t, bigTestBody, rw.Body.Bytes())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NoBody(t *testing.T) {
|
||||
|
@ -91,15 +155,17 @@ func Test_NoBody(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(test.statusCode)
|
||||
|
||||
_, err := rw.Write(test.body)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
})
|
||||
|
||||
h := mustNewCompressionHandler(t, Config{MinSize: 1024, Algorithm: zstdName}, next)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
req.Header.Set(acceptEncoding, "zstd")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
@ -115,24 +181,26 @@ func Test_NoBody(t *testing.T) {
|
|||
|
||||
func Test_MinSize(t *testing.T) {
|
||||
cfg := Config{
|
||||
MinSize: 128,
|
||||
MinSize: 128,
|
||||
Algorithm: zstdName,
|
||||
}
|
||||
|
||||
var bodySize int
|
||||
h := mustNewWrapper(t, cfg)(http.HandlerFunc(
|
||||
func(rw http.ResponseWriter, req *http.Request) {
|
||||
for i := 0; i < bodySize; i++ {
|
||||
// We make sure to Write at least once less than minSize so that both
|
||||
// cases below go through the same algo: i.e. they start buffering
|
||||
// because they haven't reached minSize.
|
||||
_, err := rw.Write([]byte{'x'})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
},
|
||||
))
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
for range bodySize {
|
||||
// We make sure to Write at least once less than minSize so that both
|
||||
// cases below go through the same algo: i.e. they start buffering
|
||||
// because they haven't reached minSize.
|
||||
_, err := rw.Write([]byte{'x'})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
|
||||
h := mustNewCompressionHandler(t, cfg, next)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", &bytes.Buffer{})
|
||||
req.Header.Add(acceptEncoding, "br")
|
||||
req.Header.Add(acceptEncoding, "zstd")
|
||||
|
||||
// Short response is not compressed
|
||||
bodySize = cfg.MinSize - 1
|
||||
|
@ -146,18 +214,20 @@ func Test_MinSize(t *testing.T) {
|
|||
rw = httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, "br", rw.Result().Header.Get(contentEncoding))
|
||||
assert.Equal(t, "zstd", rw.Result().Header.Get(contentEncoding))
|
||||
}
|
||||
|
||||
func Test_MultipleWriteHeader(t *testing.T) {
|
||||
h := mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
// We ensure that the subsequent call to WriteHeader is a noop.
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
})
|
||||
|
||||
h := mustNewCompressionHandler(t, Config{MinSize: 1024, Algorithm: zstdName}, next)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
req.Header.Set(acceptEncoding, "zstd")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
@ -166,121 +236,255 @@ func Test_MultipleWriteHeader(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_FlushBeforeWrite(t *testing.T) {
|
||||
srv := httptest.NewServer(mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.(http.Flusher).Flush()
|
||||
testCases := []struct {
|
||||
desc string
|
||||
cfg Config
|
||||
readerBuilder func(io.Reader) (io.Reader, error)
|
||||
acceptEncoding string
|
||||
}{
|
||||
{
|
||||
desc: "brotli",
|
||||
cfg: Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Test"},
|
||||
readerBuilder: func(reader io.Reader) (io.Reader, error) {
|
||||
return brotli.NewReader(reader), nil
|
||||
},
|
||||
acceptEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "zstd",
|
||||
cfg: Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Test"},
|
||||
readerBuilder: func(reader io.Reader) (io.Reader, error) {
|
||||
return zstd.NewReader(reader)
|
||||
},
|
||||
acceptEncoding: "zstd",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := rw.Write(bigTestBody)
|
||||
require.NoError(t, err)
|
||||
})))
|
||||
defer srv.Close()
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.(http.Flusher).Flush()
|
||||
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
_, err := rw.Write(bigTestBody)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, next))
|
||||
defer srv.Close()
|
||||
|
||||
defer res.Body.Close()
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Equal(t, "br", res.Header.Get(contentEncoding))
|
||||
req.Header.Set(acceptEncoding, test.acceptEncoding)
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(res.Body))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Equal(t, test.acceptEncoding, res.Header.Get(contentEncoding))
|
||||
|
||||
reader, err := test.readerBuilder(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(reader)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FlushAfterWrite(t *testing.T) {
|
||||
srv := httptest.NewServer(mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
testCases := []struct {
|
||||
desc string
|
||||
cfg Config
|
||||
readerBuilder func(io.Reader) (io.Reader, error)
|
||||
acceptEncoding string
|
||||
}{
|
||||
{
|
||||
desc: "brotli",
|
||||
cfg: Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Test"},
|
||||
readerBuilder: func(reader io.Reader) (io.Reader, error) {
|
||||
return brotli.NewReader(reader), nil
|
||||
},
|
||||
acceptEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "zstd",
|
||||
cfg: Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Test"},
|
||||
readerBuilder: func(reader io.Reader) (io.Reader, error) {
|
||||
return zstd.NewReader(reader)
|
||||
},
|
||||
acceptEncoding: "zstd",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := rw.Write(bigTestBody[0:1])
|
||||
require.NoError(t, err)
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
rw.(http.Flusher).Flush()
|
||||
for _, b := range bigTestBody[1:] {
|
||||
_, err := rw.Write([]byte{b})
|
||||
_, err := rw.Write(bigTestBody[0:1])
|
||||
require.NoError(t, err)
|
||||
|
||||
rw.(http.Flusher).Flush()
|
||||
for _, b := range bigTestBody[1:] {
|
||||
_, err := rw.Write([]byte{b})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
|
||||
srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, next))
|
||||
defer srv.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})))
|
||||
defer srv.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set(acceptEncoding, test.acceptEncoding)
|
||||
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
|
||||
defer res.Body.Close()
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Equal(t, test.acceptEncoding, res.Header.Get(contentEncoding))
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Equal(t, "br", res.Header.Get(contentEncoding))
|
||||
reader, err := test.readerBuilder(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(res.Body))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
got, err := io.ReadAll(reader)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FlushAfterWriteNil(t *testing.T) {
|
||||
srv := httptest.NewServer(mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
testCases := []struct {
|
||||
desc string
|
||||
cfg Config
|
||||
readerBuilder func(io.Reader) (io.Reader, error)
|
||||
acceptEncoding string
|
||||
}{
|
||||
{
|
||||
desc: "brotli",
|
||||
cfg: Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Test"},
|
||||
readerBuilder: func(reader io.Reader) (io.Reader, error) {
|
||||
return brotli.NewReader(reader), nil
|
||||
},
|
||||
acceptEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "zstd",
|
||||
cfg: Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Test"},
|
||||
readerBuilder: func(reader io.Reader) (io.Reader, error) {
|
||||
return zstd.NewReader(reader)
|
||||
},
|
||||
acceptEncoding: "zstd",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := rw.Write(nil)
|
||||
require.NoError(t, err)
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
rw.(http.Flusher).Flush()
|
||||
})))
|
||||
defer srv.Close()
|
||||
_, err := rw.Write(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
rw.(http.Flusher).Flush()
|
||||
})
|
||||
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, next))
|
||||
defer srv.Close()
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer res.Body.Close()
|
||||
req.Header.Set(acceptEncoding, test.acceptEncoding)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Empty(t, res.Header.Get(contentEncoding))
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(res.Body))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, got)
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Empty(t, res.Header.Get(contentEncoding))
|
||||
|
||||
reader, err := test.readerBuilder(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(reader)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FlushAfterAllWrites(t *testing.T) {
|
||||
srv := httptest.NewServer(mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
for i := range bigTestBody {
|
||||
_, err := rw.Write(bigTestBody[i : i+1])
|
||||
testCases := []struct {
|
||||
desc string
|
||||
cfg Config
|
||||
readerBuilder func(io.Reader) (io.Reader, error)
|
||||
acceptEncoding string
|
||||
}{
|
||||
{
|
||||
desc: "brotli",
|
||||
cfg: Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Test"},
|
||||
readerBuilder: func(reader io.Reader) (io.Reader, error) {
|
||||
return brotli.NewReader(reader), nil
|
||||
},
|
||||
acceptEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "zstd",
|
||||
cfg: Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Test"},
|
||||
readerBuilder: func(reader io.Reader) (io.Reader, error) {
|
||||
return zstd.NewReader(reader)
|
||||
},
|
||||
acceptEncoding: "zstd",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
for i := range bigTestBody {
|
||||
_, err := rw.Write(bigTestBody[i : i+1])
|
||||
require.NoError(t, err)
|
||||
}
|
||||
rw.(http.Flusher).Flush()
|
||||
})
|
||||
|
||||
srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, next))
|
||||
defer srv.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
rw.(http.Flusher).Flush()
|
||||
})))
|
||||
defer srv.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set(acceptEncoding, test.acceptEncoding)
|
||||
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
|
||||
defer res.Body.Close()
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Equal(t, test.acceptEncoding, res.Header.Get(contentEncoding))
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Equal(t, "br", res.Header.Get(contentEncoding))
|
||||
reader, err := test.readerBuilder(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(res.Body))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
got, err := io.ReadAll(reader)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ExcludedContentTypes(t *testing.T) {
|
||||
|
@ -352,32 +556,39 @@ func Test_ExcludedContentTypes(t *testing.T) {
|
|||
cfg := Config{
|
||||
MinSize: 1024,
|
||||
ExcludedContentTypes: test.excludedContentTypes,
|
||||
Algorithm: zstdName,
|
||||
}
|
||||
h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set(contentType, test.contentType)
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
|
||||
_, err := rw.Write(bigTestBody)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
})
|
||||
|
||||
h := mustNewCompressionHandler(t, cfg, next)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
req.Header.Set(acceptEncoding, zstdName)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
|
||||
if test.expCompression {
|
||||
assert.Equal(t, "br", rw.Header().Get(contentEncoding))
|
||||
assert.Equal(t, zstdName, rw.Header().Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(rw.Body))
|
||||
reader, err := zstd.NewReader(rw.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(reader)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
} else {
|
||||
assert.NotEqual(t, "br", rw.Header().Get("Content-Encoding"))
|
||||
assert.NotEqual(t, zstdName, rw.Header().Get("Content-Encoding"))
|
||||
|
||||
got, err := io.ReadAll(rw.Body)
|
||||
assert.NoError(t, err)
|
||||
|
@ -456,32 +667,39 @@ func Test_IncludedContentTypes(t *testing.T) {
|
|||
cfg := Config{
|
||||
MinSize: 1024,
|
||||
IncludedContentTypes: test.includedContentTypes,
|
||||
Algorithm: zstdName,
|
||||
}
|
||||
h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set(contentType, test.contentType)
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
|
||||
_, err := rw.Write(bigTestBody)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
})
|
||||
|
||||
h := mustNewCompressionHandler(t, cfg, next)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
req.Header.Set(acceptEncoding, zstdName)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
assert.Equal(t, http.StatusAccepted, rw.Code)
|
||||
|
||||
if test.expCompression {
|
||||
assert.Equal(t, "br", rw.Header().Get(contentEncoding))
|
||||
assert.Equal(t, zstdName, rw.Header().Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(rw.Body))
|
||||
reader, err := zstd.NewReader(rw.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(reader)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
} else {
|
||||
assert.NotEqual(t, "br", rw.Header().Get("Content-Encoding"))
|
||||
assert.NotEqual(t, zstdName, rw.Header().Get("Content-Encoding"))
|
||||
|
||||
got, err := io.ReadAll(rw.Body)
|
||||
assert.NoError(t, err)
|
||||
|
@ -560,8 +778,10 @@ func Test_FlushExcludedContentTypes(t *testing.T) {
|
|||
cfg := Config{
|
||||
MinSize: 1024,
|
||||
ExcludedContentTypes: test.excludedContentTypes,
|
||||
Algorithm: zstdName,
|
||||
}
|
||||
h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set(contentType, test.contentType)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
|
@ -581,10 +801,12 @@ func Test_FlushExcludedContentTypes(t *testing.T) {
|
|||
rw.(http.Flusher).Flush()
|
||||
tb = tb[toWrite:]
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
h := mustNewCompressionHandler(t, cfg, next)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
req.Header.Set(acceptEncoding, zstdName)
|
||||
|
||||
// This doesn't allow checking flushes, but we validate if content is correct.
|
||||
rw := httptest.NewRecorder()
|
||||
|
@ -593,13 +815,16 @@ func Test_FlushExcludedContentTypes(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
|
||||
if test.expCompression {
|
||||
assert.Equal(t, "br", rw.Header().Get(contentEncoding))
|
||||
assert.Equal(t, zstdName, rw.Header().Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(rw.Body))
|
||||
reader, err := zstd.NewReader(rw.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(reader)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
} else {
|
||||
assert.NotEqual(t, "br", rw.Header().Get(contentEncoding))
|
||||
assert.NotEqual(t, zstdName, rw.Header().Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(rw.Body)
|
||||
assert.NoError(t, err)
|
||||
|
@ -678,8 +903,10 @@ func Test_FlushIncludedContentTypes(t *testing.T) {
|
|||
cfg := Config{
|
||||
MinSize: 1024,
|
||||
IncludedContentTypes: test.includedContentTypes,
|
||||
Algorithm: zstdName,
|
||||
}
|
||||
h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set(contentType, test.contentType)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
|
@ -699,10 +926,12 @@ func Test_FlushIncludedContentTypes(t *testing.T) {
|
|||
rw.(http.Flusher).Flush()
|
||||
tb = tb[toWrite:]
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
h := mustNewCompressionHandler(t, cfg, next)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
req.Header.Set(acceptEncoding, zstdName)
|
||||
|
||||
// This doesn't allow checking flushes, but we validate if content is correct.
|
||||
rw := httptest.NewRecorder()
|
||||
|
@ -711,13 +940,16 @@ func Test_FlushIncludedContentTypes(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
|
||||
if test.expCompression {
|
||||
assert.Equal(t, "br", rw.Header().Get(contentEncoding))
|
||||
assert.Equal(t, zstdName, rw.Header().Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(rw.Body))
|
||||
reader, err := zstd.NewReader(rw.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(reader)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
} else {
|
||||
assert.NotEqual(t, "br", rw.Header().Get(contentEncoding))
|
||||
assert.NotEqual(t, zstdName, rw.Header().Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(rw.Body)
|
||||
assert.NoError(t, err)
|
||||
|
@ -727,32 +959,48 @@ func Test_FlushIncludedContentTypes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func mustNewWrapper(t *testing.T, cfg Config) func(http.Handler) http.HandlerFunc {
|
||||
func mustNewCompressionHandler(t *testing.T, cfg Config, next http.Handler) http.Handler {
|
||||
t.Helper()
|
||||
|
||||
w, err := NewWrapper(cfg)
|
||||
w, err := NewCompressionHandler(cfg, next)
|
||||
require.NoError(t, err)
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func newTestHandler(t *testing.T, body []byte) http.Handler {
|
||||
func newTestBrotliHandler(t *testing.T, body []byte) http.Handler {
|
||||
t.Helper()
|
||||
|
||||
return mustNewWrapper(t, Config{MinSize: 1024})(
|
||||
http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path == "/compressed" {
|
||||
rw.Header().Set("Content-Encoding", "br")
|
||||
}
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path == "/compressed" {
|
||||
rw.Header().Set("Content-Encoding", brotliName)
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
_, err := rw.Write(body)
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
)
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
_, err := rw.Write(body)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
return mustNewCompressionHandler(t, Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Compress"}, next)
|
||||
}
|
||||
|
||||
func TestParseContentType_equals(t *testing.T) {
|
||||
func newTestZstandardHandler(t *testing.T, body []byte) http.Handler {
|
||||
t.Helper()
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path == "/compressed" {
|
||||
rw.Header().Set("Content-Encoding", zstdName)
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
_, err := rw.Write(body)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
return mustNewCompressionHandler(t, Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Compress"}, next)
|
||||
}
|
||||
|
||||
func Test_ParseContentType_equals(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
pct parsedContentType
|
|
@ -22,7 +22,7 @@ type requestHeaderModifier struct {
|
|||
}
|
||||
|
||||
// NewRequestHeaderModifier creates a new request header modifier middleware.
|
||||
func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dynamic.RequestHeaderModifier, name string) (http.Handler, error) {
|
||||
func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dynamic.RequestHeaderModifier, name string) http.Handler {
|
||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||
logger.Debug().Msg("Creating middleware")
|
||||
|
||||
|
@ -32,7 +32,7 @@ func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dyn
|
|||
set: config.Set,
|
||||
add: config.Add,
|
||||
remove: config.Remove,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *requestHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) {
|
|
@ -7,7 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
||||
)
|
||||
|
@ -104,8 +103,7 @@ func TestRequestHeaderModifier(t *testing.T) {
|
|||
gotHeaders = r.Header
|
||||
})
|
||||
|
||||
handler, err := NewRequestHeaderModifier(context.Background(), next, test.config, "foo-request-header-modifier")
|
||||
require.NoError(t, err)
|
||||
handler := NewRequestHeaderModifier(context.Background(), next, test.config, "foo-request-header-modifier")
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
if test.requestHeaders != nil {
|
103
pkg/middlewares/gatewayapi/redirect/request_redirect.go
Normal file
103
pkg/middlewares/gatewayapi/redirect/request_redirect.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package redirect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
typeName = "RequestRedirect"
|
||||
)
|
||||
|
||||
type redirect struct {
|
||||
name string
|
||||
next http.Handler
|
||||
|
||||
scheme *string
|
||||
hostname *string
|
||||
port *string
|
||||
path *string
|
||||
pathPrefix *string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
// NewRequestRedirect creates a redirect middleware.
|
||||
func NewRequestRedirect(ctx context.Context, next http.Handler, conf dynamic.RequestRedirect, name string) (http.Handler, error) {
|
||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||
logger.Debug().Msg("Creating middleware")
|
||||
|
||||
statusCode := conf.StatusCode
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusFound
|
||||
}
|
||||
if statusCode != http.StatusMovedPermanently && statusCode != http.StatusFound {
|
||||
return nil, fmt.Errorf("unsupported status code: %d", statusCode)
|
||||
}
|
||||
|
||||
return redirect{
|
||||
name: name,
|
||||
next: next,
|
||||
scheme: conf.Scheme,
|
||||
hostname: conf.Hostname,
|
||||
port: conf.Port,
|
||||
path: conf.Path,
|
||||
pathPrefix: conf.PathPrefix,
|
||||
statusCode: statusCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r redirect) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return r.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (r redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
redirectURL := *req.URL
|
||||
redirectURL.Host = req.Host
|
||||
|
||||
if r.scheme != nil {
|
||||
redirectURL.Scheme = *r.scheme
|
||||
}
|
||||
|
||||
host := redirectURL.Hostname()
|
||||
if r.hostname != nil {
|
||||
host = *r.hostname
|
||||
}
|
||||
|
||||
port := redirectURL.Port()
|
||||
if r.port != nil {
|
||||
port = *r.port
|
||||
}
|
||||
|
||||
if port != "" {
|
||||
host = net.JoinHostPort(host, port)
|
||||
}
|
||||
redirectURL.Host = host
|
||||
|
||||
if r.path != nil && r.pathPrefix == nil {
|
||||
redirectURL.Path = *r.path
|
||||
}
|
||||
|
||||
if r.path != nil && r.pathPrefix != nil {
|
||||
redirectURL.Path = path.Join(*r.path, strings.TrimPrefix(req.URL.Path, *r.pathPrefix))
|
||||
|
||||
// add the trailing slash if needed, as path.Join removes trailing slashes.
|
||||
if strings.HasSuffix(req.URL.Path, "/") && !strings.HasSuffix(redirectURL.Path, "/") {
|
||||
redirectURL.Path += "/"
|
||||
}
|
||||
}
|
||||
|
||||
rw.Header().Set("Location", redirectURL.String())
|
||||
|
||||
rw.WriteHeader(r.statusCode)
|
||||
if _, err := rw.Write([]byte(http.StatusText(r.statusCode))); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
216
pkg/middlewares/gatewayapi/redirect/request_redirect_test.go
Normal file
216
pkg/middlewares/gatewayapi/redirect/request_redirect_test.go
Normal file
|
@ -0,0 +1,216 @@
|
|||
package redirect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestRequestRedirectHandler(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
config dynamic.RequestRedirect
|
||||
url string
|
||||
wantURL string
|
||||
wantStatus int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "wrong status code",
|
||||
config: dynamic.RequestRedirect{
|
||||
Path: ptr.To("/baz"),
|
||||
StatusCode: http.StatusOK,
|
||||
},
|
||||
url: "http://foo.com:80/foo/bar",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "replace path",
|
||||
config: dynamic.RequestRedirect{
|
||||
Path: ptr.To("/baz"),
|
||||
},
|
||||
url: "http://foo.com:80/foo/bar",
|
||||
wantURL: "http://foo.com:80/baz",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "replace path without trailing slash",
|
||||
config: dynamic.RequestRedirect{
|
||||
Path: ptr.To("/baz"),
|
||||
},
|
||||
url: "http://foo.com:80/foo/bar/",
|
||||
wantURL: "http://foo.com:80/baz",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "replace path with trailing slash",
|
||||
config: dynamic.RequestRedirect{
|
||||
Path: ptr.To("/baz/"),
|
||||
},
|
||||
url: "http://foo.com:80/foo/bar",
|
||||
wantURL: "http://foo.com:80/baz/",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "only hostname",
|
||||
config: dynamic.RequestRedirect{
|
||||
Hostname: ptr.To("bar.com"),
|
||||
},
|
||||
url: "http://foo.com:8080/foo/",
|
||||
wantURL: "http://bar.com:8080/foo/",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "replace prefix path",
|
||||
config: dynamic.RequestRedirect{
|
||||
Path: ptr.To("/baz"),
|
||||
PathPrefix: ptr.To("/foo"),
|
||||
},
|
||||
url: "http://foo.com:80/foo/bar",
|
||||
wantURL: "http://foo.com:80/baz/bar",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "replace prefix path with trailing slash",
|
||||
config: dynamic.RequestRedirect{
|
||||
Path: ptr.To("/baz"),
|
||||
PathPrefix: ptr.To("/foo"),
|
||||
},
|
||||
url: "http://foo.com:80/foo/bar/",
|
||||
wantURL: "http://foo.com:80/baz/bar/",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "replace prefix path without slash prefix",
|
||||
config: dynamic.RequestRedirect{
|
||||
Path: ptr.To("baz"),
|
||||
PathPrefix: ptr.To("/foo"),
|
||||
},
|
||||
url: "http://foo.com:80/foo/bar",
|
||||
wantURL: "http://foo.com:80/baz/bar",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "replace prefix path without slash prefix",
|
||||
config: dynamic.RequestRedirect{
|
||||
Path: ptr.To("/baz"),
|
||||
PathPrefix: ptr.To("/foo/"),
|
||||
},
|
||||
url: "http://foo.com:80/foo/bar",
|
||||
wantURL: "http://foo.com:80/baz/bar",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "simple redirection",
|
||||
config: dynamic.RequestRedirect{
|
||||
Scheme: ptr.To("https"),
|
||||
Hostname: ptr.To("foobar.com"),
|
||||
Port: ptr.To("443"),
|
||||
},
|
||||
url: "http://foo.com:80",
|
||||
wantURL: "https://foobar.com:443",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "HTTP to HTTPS permanent",
|
||||
config: dynamic.RequestRedirect{
|
||||
Scheme: ptr.To("https"),
|
||||
StatusCode: http.StatusMovedPermanently,
|
||||
},
|
||||
url: "http://foo",
|
||||
wantURL: "https://foo",
|
||||
wantStatus: http.StatusMovedPermanently,
|
||||
},
|
||||
{
|
||||
desc: "HTTPS to HTTP permanent",
|
||||
config: dynamic.RequestRedirect{
|
||||
Scheme: ptr.To("http"),
|
||||
StatusCode: http.StatusMovedPermanently,
|
||||
},
|
||||
url: "https://foo",
|
||||
wantURL: "http://foo",
|
||||
wantStatus: http.StatusMovedPermanently,
|
||||
},
|
||||
{
|
||||
desc: "HTTP to HTTPS",
|
||||
config: dynamic.RequestRedirect{
|
||||
Scheme: ptr.To("https"),
|
||||
Port: ptr.To("443"),
|
||||
},
|
||||
url: "http://foo:80",
|
||||
wantURL: "https://foo:443",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "HTTP to HTTPS, with X-Forwarded-Proto",
|
||||
config: dynamic.RequestRedirect{
|
||||
Scheme: ptr.To("https"),
|
||||
Port: ptr.To("443"),
|
||||
},
|
||||
url: "http://foo:80",
|
||||
wantURL: "https://foo:443",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "HTTPS to HTTP",
|
||||
config: dynamic.RequestRedirect{
|
||||
Scheme: ptr.To("http"),
|
||||
Port: ptr.To("80"),
|
||||
},
|
||||
url: "https://foo:443",
|
||||
wantURL: "http://foo:80",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "HTTP to HTTP",
|
||||
config: dynamic.RequestRedirect{
|
||||
Scheme: ptr.To("http"),
|
||||
Port: ptr.To("88"),
|
||||
},
|
||||
url: "http://foo:80",
|
||||
wantURL: "http://foo:88",
|
||||
wantStatus: http.StatusFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
handler, err := NewRequestRedirect(context.Background(), next, test.config, "traefikTest")
|
||||
if test.wantErr {
|
||||
require.Error(t, err)
|
||||
require.Nil(t, handler)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, handler)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, test.url, nil)
|
||||
|
||||
handler.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, test.wantStatus, recorder.Code)
|
||||
switch test.wantStatus {
|
||||
case http.StatusMovedPermanently, http.StatusFound:
|
||||
location, err := recorder.Result().Location()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.wantURL, location.String())
|
||||
default:
|
||||
location, err := recorder.Result().Location()
|
||||
require.Errorf(t, err, "Location %v", location)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
68
pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go
Normal file
68
pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package urlrewrite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
typeName = "URLRewrite"
|
||||
)
|
||||
|
||||
type urlRewrite struct {
|
||||
name string
|
||||
next http.Handler
|
||||
|
||||
hostname *string
|
||||
path *string
|
||||
pathPrefix *string
|
||||
}
|
||||
|
||||
// NewURLRewrite creates a URL rewrite middleware.
|
||||
func NewURLRewrite(ctx context.Context, next http.Handler, conf dynamic.URLRewrite, name string) http.Handler {
|
||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||
logger.Debug().Msg("Creating middleware")
|
||||
|
||||
return urlRewrite{
|
||||
name: name,
|
||||
next: next,
|
||||
hostname: conf.Hostname,
|
||||
path: conf.Path,
|
||||
pathPrefix: conf.PathPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
func (u urlRewrite) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return u.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (u urlRewrite) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
newPath := req.URL.Path
|
||||
if u.path != nil && u.pathPrefix == nil {
|
||||
newPath = *u.path
|
||||
}
|
||||
if u.path != nil && u.pathPrefix != nil {
|
||||
newPath = path.Join(*u.path, strings.TrimPrefix(req.URL.Path, *u.pathPrefix))
|
||||
|
||||
// add the trailing slash if needed, as path.Join removes trailing slashes.
|
||||
if strings.HasSuffix(req.URL.Path, "/") && !strings.HasSuffix(newPath, "/") {
|
||||
newPath += "/"
|
||||
}
|
||||
}
|
||||
|
||||
req.URL.Path = newPath
|
||||
req.URL.RawPath = req.URL.EscapedPath()
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
|
||||
if u.hostname != nil {
|
||||
req.Host = *u.hostname
|
||||
}
|
||||
|
||||
u.next.ServeHTTP(rw, req)
|
||||
}
|
126
pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go
Normal file
126
pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
package urlrewrite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestURLRewriteHandler(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
config dynamic.URLRewrite
|
||||
url string
|
||||
wantURL string
|
||||
wantHost string
|
||||
}{
|
||||
{
|
||||
desc: "replace path",
|
||||
config: dynamic.URLRewrite{
|
||||
Path: ptr.To("/baz"),
|
||||
},
|
||||
url: "http://foo.com/foo/bar",
|
||||
wantURL: "http://foo.com/baz",
|
||||
wantHost: "foo.com",
|
||||
},
|
||||
{
|
||||
desc: "replace path without trailing slash",
|
||||
config: dynamic.URLRewrite{
|
||||
Path: ptr.To("/baz"),
|
||||
},
|
||||
url: "http://foo.com/foo/bar/",
|
||||
wantURL: "http://foo.com/baz",
|
||||
wantHost: "foo.com",
|
||||
},
|
||||
{
|
||||
desc: "replace path with trailing slash",
|
||||
config: dynamic.URLRewrite{
|
||||
Path: ptr.To("/baz/"),
|
||||
},
|
||||
url: "http://foo.com/foo/bar",
|
||||
wantURL: "http://foo.com/baz/",
|
||||
wantHost: "foo.com",
|
||||
},
|
||||
{
|
||||
desc: "only host",
|
||||
config: dynamic.URLRewrite{
|
||||
Hostname: ptr.To("bar.com"),
|
||||
},
|
||||
url: "http://foo.com/foo/",
|
||||
wantURL: "http://foo.com/foo/",
|
||||
wantHost: "bar.com",
|
||||
},
|
||||
{
|
||||
desc: "host and path",
|
||||
config: dynamic.URLRewrite{
|
||||
Hostname: ptr.To("bar.com"),
|
||||
Path: ptr.To("/baz/"),
|
||||
},
|
||||
url: "http://foo.com/foo/",
|
||||
wantURL: "http://foo.com/baz/",
|
||||
wantHost: "bar.com",
|
||||
},
|
||||
{
|
||||
desc: "replace prefix path",
|
||||
config: dynamic.URLRewrite{
|
||||
Path: ptr.To("/baz"),
|
||||
PathPrefix: ptr.To("/foo"),
|
||||
},
|
||||
url: "http://foo.com/foo/bar",
|
||||
wantURL: "http://foo.com/baz/bar",
|
||||
wantHost: "foo.com",
|
||||
},
|
||||
{
|
||||
desc: "replace prefix path with trailing slash",
|
||||
config: dynamic.URLRewrite{
|
||||
Path: ptr.To("/baz"),
|
||||
PathPrefix: ptr.To("/foo"),
|
||||
},
|
||||
url: "http://foo.com/foo/bar/",
|
||||
wantURL: "http://foo.com/baz/bar/",
|
||||
wantHost: "foo.com",
|
||||
},
|
||||
{
|
||||
desc: "replace prefix path without slash prefix",
|
||||
config: dynamic.URLRewrite{
|
||||
Path: ptr.To("baz"),
|
||||
PathPrefix: ptr.To("/foo"),
|
||||
},
|
||||
url: "http://foo.com/foo/bar",
|
||||
wantURL: "http://foo.com/baz/bar",
|
||||
wantHost: "foo.com",
|
||||
},
|
||||
{
|
||||
desc: "replace prefix path without slash prefix",
|
||||
config: dynamic.URLRewrite{
|
||||
Path: ptr.To("/baz"),
|
||||
PathPrefix: ptr.To("/foo/"),
|
||||
},
|
||||
url: "http://foo.com/foo/bar",
|
||||
wantURL: "http://foo.com/baz/bar",
|
||||
wantHost: "foo.com",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
handler := NewURLRewrite(context.Background(), next, test.config, "traefikTest")
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, test.url, nil)
|
||||
handler.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, test.wantURL, req.URL.String())
|
||||
assert.Equal(t, test.wantHost, req.Host)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -17,24 +17,25 @@ type secureHeader struct {
|
|||
// newSecure constructs a new secure instance with supplied options.
|
||||
func newSecure(next http.Handler, cfg dynamic.Headers, contextKey string) *secureHeader {
|
||||
opt := secure.Options{
|
||||
BrowserXssFilter: cfg.BrowserXSSFilter,
|
||||
ContentTypeNosniff: cfg.ContentTypeNosniff,
|
||||
ForceSTSHeader: cfg.ForceSTSHeader,
|
||||
FrameDeny: cfg.FrameDeny,
|
||||
IsDevelopment: cfg.IsDevelopment,
|
||||
STSIncludeSubdomains: cfg.STSIncludeSubdomains,
|
||||
STSPreload: cfg.STSPreload,
|
||||
ContentSecurityPolicy: cfg.ContentSecurityPolicy,
|
||||
CustomBrowserXssValue: cfg.CustomBrowserXSSValue,
|
||||
CustomFrameOptionsValue: cfg.CustomFrameOptionsValue,
|
||||
PublicKey: cfg.PublicKey,
|
||||
ReferrerPolicy: cfg.ReferrerPolicy,
|
||||
AllowedHosts: cfg.AllowedHosts,
|
||||
HostsProxyHeaders: cfg.HostsProxyHeaders,
|
||||
SSLProxyHeaders: cfg.SSLProxyHeaders,
|
||||
STSSeconds: cfg.STSSeconds,
|
||||
PermissionsPolicy: cfg.PermissionsPolicy,
|
||||
SecureContextKey: contextKey,
|
||||
BrowserXssFilter: cfg.BrowserXSSFilter,
|
||||
ContentTypeNosniff: cfg.ContentTypeNosniff,
|
||||
ForceSTSHeader: cfg.ForceSTSHeader,
|
||||
FrameDeny: cfg.FrameDeny,
|
||||
IsDevelopment: cfg.IsDevelopment,
|
||||
STSIncludeSubdomains: cfg.STSIncludeSubdomains,
|
||||
STSPreload: cfg.STSPreload,
|
||||
ContentSecurityPolicy: cfg.ContentSecurityPolicy,
|
||||
ContentSecurityPolicyReportOnly: cfg.ContentSecurityPolicyReportOnly,
|
||||
CustomBrowserXssValue: cfg.CustomBrowserXSSValue,
|
||||
CustomFrameOptionsValue: cfg.CustomFrameOptionsValue,
|
||||
PublicKey: cfg.PublicKey,
|
||||
ReferrerPolicy: cfg.ReferrerPolicy,
|
||||
AllowedHosts: cfg.AllowedHosts,
|
||||
HostsProxyHeaders: cfg.HostsProxyHeaders,
|
||||
SSLProxyHeaders: cfg.SSLProxyHeaders,
|
||||
STSSeconds: cfg.STSSeconds,
|
||||
PermissionsPolicy: cfg.PermissionsPolicy,
|
||||
SecureContextKey: contextKey,
|
||||
}
|
||||
|
||||
return &secureHeader{
|
||||
|
|
|
@ -58,6 +58,7 @@ func (s *mockSpan) SetAttributes(kv ...attribute.KeyValue) {
|
|||
func (s *mockSpan) End(...trace.SpanEndOption) {}
|
||||
func (s *mockSpan) RecordError(_ error, _ ...trace.EventOption) {}
|
||||
func (s *mockSpan) AddEvent(_ string, _ ...trace.EventOption) {}
|
||||
func (s *mockSpan) AddLink(_ trace.Link) {}
|
||||
|
||||
func (s *mockSpan) SetName(name string) { s.name = name }
|
||||
|
||||
|
|
|
@ -264,7 +264,7 @@ func queryRegexp(tree *matchersTree, queries ...string) error {
|
|||
|
||||
// IsASCII checks if the given string contains only ASCII characters.
|
||||
func IsASCII(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
for i := range len(s) {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ func hostSNIRegexp(tree *matchersTree, templates ...string) error {
|
|||
|
||||
// isASCII checks if the given string contains only ASCII characters.
|
||||
func isASCII(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
for i := range len(s) {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -219,7 +219,7 @@ func varGroupName(idx int) string {
|
|||
func braceIndices(s string) ([]int, error) {
|
||||
var level, idx int
|
||||
var idxs []int
|
||||
for i := 0; i < len(s); i++ {
|
||||
for i := range len(s) {
|
||||
switch s[i] {
|
||||
case '{':
|
||||
if level++; level == 1 {
|
||||
|
|
|
@ -3878,7 +3878,7 @@ func TestFilterHealthStatuses(t *testing.T) {
|
|||
err := p.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; i < len(test.items); i++ {
|
||||
for i := range len(test.items) {
|
||||
var err error
|
||||
test.items[i].ExtraConf, err = p.getExtraConf(test.items[i].Labels)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/foo`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
|
||||
services:
|
||||
- name: external-svc
|
||||
port: 443
|
||||
healthCheck:
|
||||
path: /health
|
||||
interval: 15s
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/foo`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
|
||||
services:
|
||||
- name: external-svc
|
||||
port: 443
|
||||
healthCheck:
|
||||
path: /health1
|
||||
interval: 15s
|
||||
- name: whoami2
|
||||
port: 443
|
||||
healthCheck:
|
||||
path: /health3
|
||||
interval: 30s
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/foo`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
|
||||
services:
|
||||
- name: external-svc
|
||||
port: 443
|
||||
healthCheck:
|
||||
path: /health1
|
||||
interval: 15s
|
||||
- name: external-svc-with-https
|
||||
port: 443
|
||||
healthCheck:
|
||||
path: /health2
|
||||
interval: 20s
|
|
@ -306,6 +306,36 @@ func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.Load
|
|||
lb.SetDefaults()
|
||||
lb.Servers = servers
|
||||
|
||||
if svc.HealthCheck != nil {
|
||||
lb.HealthCheck = &dynamic.ServerHealthCheck{
|
||||
Scheme: svc.HealthCheck.Scheme,
|
||||
Path: svc.HealthCheck.Path,
|
||||
Method: svc.HealthCheck.Method,
|
||||
Status: svc.HealthCheck.Status,
|
||||
Port: svc.HealthCheck.Port,
|
||||
Hostname: svc.HealthCheck.Hostname,
|
||||
Headers: svc.HealthCheck.Headers,
|
||||
}
|
||||
lb.HealthCheck.SetDefaults()
|
||||
|
||||
if svc.HealthCheck.FollowRedirects != nil {
|
||||
lb.HealthCheck.FollowRedirects = svc.HealthCheck.FollowRedirects
|
||||
}
|
||||
if svc.HealthCheck.Mode != "http" {
|
||||
lb.HealthCheck.Mode = svc.HealthCheck.Mode
|
||||
}
|
||||
if svc.HealthCheck.Interval != nil {
|
||||
if err := lb.HealthCheck.Interval.Set(svc.HealthCheck.Interval.String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if svc.HealthCheck.Timeout != nil {
|
||||
if err := lb.HealthCheck.Timeout.Set(svc.HealthCheck.Timeout.String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conf := svc
|
||||
lb.PassHostHeader = conf.PassHostHeader
|
||||
if lb.PassHostHeader == nil {
|
||||
|
@ -380,6 +410,10 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
|
|||
}
|
||||
|
||||
var servers []dynamic.Server
|
||||
if service.Spec.Type != corev1.ServiceTypeExternalName && svc.HealthCheck != nil {
|
||||
return nil, fmt.Errorf("HealthCheck allowed only for ExternalName services: %s/%s", namespace, sanitizedName)
|
||||
}
|
||||
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
if !c.allowExternalNameServices {
|
||||
return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, sanitizedName)
|
||||
|
|
|
@ -2432,6 +2432,182 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with one external service and health check",
|
||||
paths: []string{"services.yml", "with_one_external_service_and_health_check.yml"},
|
||||
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-test-route-77c62dfe9517144aeeaa": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-route-77c62dfe9517144aeeaa",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/foo`)",
|
||||
Priority: 12,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-route-77c62dfe9517144aeeaa": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "https://external.domain:443",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
HealthCheck: &dynamic.ServerHealthCheck{
|
||||
Path: "/health",
|
||||
Timeout: 5000000000,
|
||||
Interval: 15000000000,
|
||||
FollowRedirects: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with two external services and health check",
|
||||
paths: []string{"services.yml", "with_two_external_services_and_health_check.yml"},
|
||||
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-test-route-77c62dfe9517144aeeaa": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-route-77c62dfe9517144aeeaa",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/foo`)",
|
||||
Priority: 12,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-route-77c62dfe9517144aeeaa": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "default-external-svc-443",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-external-svc-with-https-443",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-external-svc-443": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "https://external.domain:443",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
HealthCheck: &dynamic.ServerHealthCheck{
|
||||
Path: "/health1",
|
||||
Timeout: 5000000000,
|
||||
Interval: 15000000000,
|
||||
FollowRedirects: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-external-svc-with-https-443": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "https://external.domain:443",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
HealthCheck: &dynamic.ServerHealthCheck{
|
||||
Path: "/health2",
|
||||
Timeout: 5000000000,
|
||||
Interval: 20000000000,
|
||||
FollowRedirects: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with one external service and one regular service and health check",
|
||||
paths: []string{"services.yml", "with_one_external_svc_and_regular_svc_health_check.yml"},
|
||||
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{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-external-svc-443": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "https://external.domain:443",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
HealthCheck: &dynamic.ServerHealthCheck{
|
||||
Path: "/health1",
|
||||
Timeout: 5000000000,
|
||||
Interval: 15000000000,
|
||||
FollowRedirects: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "services lb, servers lb, and mirror service, all in a wrr with different namespaces",
|
||||
allowCrossNamespace: true,
|
||||
|
|
|
@ -131,6 +131,8 @@ type LoadBalancerSpec struct {
|
|||
// It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
|
||||
// By default, NodePortLB is false.
|
||||
NodePortLB bool `json:"nodePortLB,omitempty"`
|
||||
// Healthcheck defines health checks for ExternalName services.
|
||||
HealthCheck *ServerHealthCheck `json:"healthCheck,omitempty"`
|
||||
}
|
||||
|
||||
type ResponseForwarding struct {
|
||||
|
@ -142,6 +144,36 @@ type ResponseForwarding struct {
|
|||
FlushInterval string `json:"flushInterval,omitempty"`
|
||||
}
|
||||
|
||||
type ServerHealthCheck struct {
|
||||
// Scheme replaces the server URL scheme for the health check endpoint.
|
||||
Scheme string `json:"scheme,omitempty"`
|
||||
// Mode defines the health check mode.
|
||||
// If defined to grpc, will use the gRPC health check protocol to probe the server.
|
||||
// Default: http
|
||||
Mode string `json:"mode,omitempty"`
|
||||
// Path defines the server URL path for the health check endpoint.
|
||||
Path string `json:"path,omitempty"`
|
||||
// Method defines the healthcheck method.
|
||||
Method string `json:"method,omitempty"`
|
||||
// Status defines the expected HTTP status code of the response to the health check request.
|
||||
Status int `json:"status,omitempty"`
|
||||
// Port defines the server URL port for the health check endpoint.
|
||||
Port int `json:"port,omitempty"`
|
||||
// Interval defines the frequency of the health check calls.
|
||||
// Default: 30s
|
||||
Interval *intstr.IntOrString `json:"interval,omitempty"`
|
||||
// Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy.
|
||||
// Default: 5s
|
||||
Timeout *intstr.IntOrString `json:"timeout,omitempty"`
|
||||
// Hostname defines the value of hostname in the Host header of the health check request.
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
// FollowRedirects defines whether redirects should be followed during the health check calls.
|
||||
// Default: true
|
||||
FollowRedirects *bool `json:"followRedirects,omitempty"`
|
||||
// Headers defines custom headers to be sent to the health check endpoint.
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
}
|
||||
|
||||
// Service defines an upstream HTTP service to proxy traffic to.
|
||||
type Service struct {
|
||||
LoadBalancerSpec `json:",inline"`
|
||||
|
|
|
@ -582,6 +582,11 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
|
|||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.HealthCheck != nil {
|
||||
in, out := &in.HealthCheck, &out.HealthCheck
|
||||
*out = new(ServerHealthCheck)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1110,6 +1115,44 @@ func (in *RouteUDP) DeepCopy() *RouteUDP {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServerHealthCheck) DeepCopyInto(out *ServerHealthCheck) {
|
||||
*out = *in
|
||||
if in.Interval != nil {
|
||||
in, out := &in.Interval, &out.Interval
|
||||
*out = new(intstr.IntOrString)
|
||||
**out = **in
|
||||
}
|
||||
if in.Timeout != nil {
|
||||
in, out := &in.Timeout, &out.Timeout
|
||||
*out = new(intstr.IntOrString)
|
||||
**out = **in
|
||||
}
|
||||
if in.FollowRedirects != nil {
|
||||
in, out := &in.FollowRedirects, &out.FollowRedirects
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Headers != nil {
|
||||
in, out := &in.Headers, &out.Headers
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerHealthCheck.
|
||||
func (in *ServerHealthCheck) DeepCopy() *ServerHealthCheck {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServerHealthCheck)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServersTransport) DeepCopyInto(out *ServersTransport) {
|
||||
*out = *in
|
||||
|
|
|
@ -39,12 +39,10 @@ spec:
|
|||
hostnames:
|
||||
- "example.org"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
filters:
|
||||
- type: RequestRedirect
|
||||
requestRedirect:
|
||||
|
|
|
@ -39,12 +39,10 @@ spec:
|
|||
hostnames:
|
||||
- "example.org"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
filters:
|
||||
- type: RequestRedirect
|
||||
requestRedirect:
|
||||
|
|
|
@ -39,7 +39,11 @@ spec:
|
|||
hostnames:
|
||||
- "example.org"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
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.com"
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /foo
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
filters:
|
||||
- type: URLRewrite
|
||||
urlRewrite:
|
||||
hostname: www.foo.bar
|
||||
path:
|
||||
type: ReplacePrefixMatch
|
||||
replacePrefixMatch: /xyz
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
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.com"
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /foo
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
filters:
|
||||
- type: URLRewrite
|
||||
urlRewrite:
|
||||
path:
|
||||
type: ReplaceFullPath
|
||||
replaceFullPath: /bar
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
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.com"
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /foo
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
filters:
|
||||
- type: URLRewrite
|
||||
urlRewrite:
|
||||
hostname: www.foo.bar
|
|
@ -37,7 +37,11 @@ spec:
|
|||
- "foo.com"
|
||||
- "bar.com"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -107,7 +107,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -131,7 +135,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
|
@ -151,7 +159,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
|
|
|
@ -33,7 +33,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -33,7 +33,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -89,7 +89,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -33,7 +33,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -119,7 +119,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -134,7 +138,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -107,7 +107,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -177,7 +181,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -107,7 +107,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -177,7 +181,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -130,7 +130,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -200,7 +204,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: backend-from-bar
|
||||
namespace: bar
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: default
|
||||
to:
|
||||
- group: ""
|
||||
kind: Service
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: GatewayClass
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: http
|
||||
protocol: HTTP
|
||||
port: 80
|
||||
hostname: foo.example.com
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: HTTPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: http-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: GatewayClass
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: http
|
||||
protocol: HTTP
|
||||
port: 80
|
||||
hostname: foo.example.com
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: HTTPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: http-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: backend-from-bar
|
||||
namespace: bar
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: anothernamespce
|
||||
to:
|
||||
- group: ""
|
||||
kind: Service
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: GatewayClass
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: http
|
||||
protocol: HTTP
|
||||
port: 80
|
||||
hostname: foo.example.com
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: HTTPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: http-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: ReferenceGrant
|
||||
metadata:
|
||||
name: backend-from-bar
|
||||
namespace: bar
|
||||
spec:
|
||||
from:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: HTTPRoute
|
||||
namespace: default
|
||||
to:
|
||||
- group: ""
|
||||
kind: Service
|
||||
name: differentservice
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: GatewayClass
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners: # Use GatewayClass defaults for listener definition.
|
||||
- name: http
|
||||
protocol: HTTP
|
||||
port: 80
|
||||
hostname: foo.example.com
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: HTTPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: http-app-1
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
|
@ -37,7 +37,11 @@ spec:
|
|||
- "foo.com"
|
||||
- "*.bar.com"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -37,7 +37,11 @@ spec:
|
|||
- "foo.com"
|
||||
- "*.foo.com"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -20,8 +20,8 @@ import (
|
|||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||
routes, err := client.ListHTTPRoutes()
|
||||
func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||
routes, err := p.client.ListHTTPRoutes()
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to list HTTPRoutes")
|
||||
return
|
||||
|
@ -41,64 +41,45 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayLis
|
|||
Conditions: []metav1.Condition{
|
||||
{
|
||||
Type: string(gatev1.RouteConditionAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.RouteReasonAccepted),
|
||||
Reason: string(gatev1.RouteReasonNoMatchingParent),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var attachedListeners bool
|
||||
notAcceptedReason := gatev1.RouteReasonNoMatchingParent
|
||||
for _, listener := range gatewayListeners {
|
||||
if !matchListener(listener, route.Namespace, parentRef) {
|
||||
continue
|
||||
}
|
||||
|
||||
accepted := true
|
||||
if !allowRoute(listener, route.Namespace, kindHTTPRoute) {
|
||||
notAcceptedReason = gatev1.RouteReasonNotAllowedByListeners
|
||||
continue
|
||||
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
|
||||
accepted = false
|
||||
}
|
||||
|
||||
hostnames, ok := findMatchingHostnames(listener.Hostname, route.Spec.Hostnames)
|
||||
if !ok {
|
||||
notAcceptedReason = gatev1.RouteReasonNoMatchingListenerHostname
|
||||
continue
|
||||
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNoMatchingListenerHostname))
|
||||
accepted = false
|
||||
}
|
||||
|
||||
listener.Status.AttachedRoutes++
|
||||
|
||||
// TODO should we build the conf if the listener is not attached
|
||||
// only consider the route attached if the listener is in an "attached" state.
|
||||
if listener.Attached {
|
||||
attachedListeners = true
|
||||
if accepted {
|
||||
// Gateway listener should have AttachedRoutes set even when Gateway has unresolved refs.
|
||||
listener.Status.AttachedRoutes++
|
||||
// Only consider the route attached if the listener is in an "attached" state.
|
||||
if listener.Attached {
|
||||
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonAccepted))
|
||||
}
|
||||
}
|
||||
resolveConditions := p.loadHTTPRoute(logger.WithContext(ctx), client, listener, route, hostnames, conf)
|
||||
|
||||
// TODO: handle more accurately route conditions (in case of multiple listener matching).
|
||||
for _, condition := range resolveConditions {
|
||||
parentStatus.Conditions = appendCondition(parentStatus.Conditions, condition)
|
||||
routeConf, resolveRefCondition := p.loadHTTPRoute(logger.WithContext(ctx), listener, route, hostnames)
|
||||
if accepted && listener.Attached {
|
||||
mergeHTTPConfiguration(routeConf, conf)
|
||||
}
|
||||
}
|
||||
|
||||
if !attachedListeners {
|
||||
parentStatus.Conditions = []metav1.Condition{
|
||||
{
|
||||
Type: string(gatev1.RouteConditionAccepted),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(notAcceptedReason),
|
||||
},
|
||||
{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.RouteReasonRefNotPermitted),
|
||||
},
|
||||
}
|
||||
parentStatus.Conditions = upsertRouteConditionResolvedRefs(parentStatus.Conditions, resolveRefCondition)
|
||||
}
|
||||
|
||||
parentStatuses = append(parentStatuses, *parentStatus)
|
||||
|
@ -109,7 +90,7 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayLis
|
|||
Parents: parentStatuses,
|
||||
},
|
||||
}
|
||||
if err := client.UpdateHTTPRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, status); err != nil {
|
||||
if err := p.client.UpdateHTTPRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, status); err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Msg("Unable to update HTTPRoute status")
|
||||
|
@ -117,130 +98,146 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayLis
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Provider) loadHTTPRoute(ctx context.Context, client Client, listener gatewayListener, route *gatev1.HTTPRoute, hostnames []gatev1.Hostname, conf *dynamic.Configuration) []metav1.Condition {
|
||||
routeConditions := []metav1.Condition{
|
||||
{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: route.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.RouteConditionResolvedRefs),
|
||||
func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener, route *gatev1.HTTPRoute, hostnames []gatev1.Hostname) (*dynamic.Configuration, metav1.Condition) {
|
||||
conf := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
ServersTransports: make(map[string]*dynamic.ServersTransport),
|
||||
},
|
||||
}
|
||||
|
||||
hostRule := hostRule(hostnames)
|
||||
condition := metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: route.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.RouteConditionResolvedRefs),
|
||||
}
|
||||
|
||||
for _, routeRule := range route.Spec.Rules {
|
||||
router := dynamic.Router{
|
||||
RuleSyntax: "v3",
|
||||
Rule: routerRule(routeRule, hostRule),
|
||||
EntryPoints: []string{listener.EPName},
|
||||
}
|
||||
if listener.Protocol == gatev1.HTTPSProtocolType {
|
||||
router.TLS = &dynamic.RouterTLSConfig{}
|
||||
}
|
||||
|
||||
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||
routerName := route.Name + "-" + listener.GWName + "-" + listener.EPName
|
||||
routerKey := makeRouterKey(router.Rule, makeID(route.Namespace, routerName))
|
||||
|
||||
var wrr dynamic.WeightedRoundRobin
|
||||
wrrName := provider.Normalize(routerKey + "-wrr")
|
||||
|
||||
middlewares, err := p.loadMiddlewares(listener.Protocol, route.Namespace, routerKey, routeRule.Filters)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().
|
||||
Err(err).
|
||||
Msg("Unable to load HTTPRoute filters")
|
||||
|
||||
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||
errWrr := dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "invalid-httproute-filter",
|
||||
Status: ptr.To(500),
|
||||
Weight: ptr.To(1),
|
||||
})
|
||||
|
||||
conf.HTTP.Services[wrrName] = &dynamic.Service{Weighted: &wrr}
|
||||
router.Service = wrrName
|
||||
} else {
|
||||
for name, middleware := range middlewares {
|
||||
// If the middleware config is nil in the return of the loadMiddlewares function,
|
||||
// it means that we just need a reference to that middleware.
|
||||
if middleware != nil {
|
||||
conf.HTTP.Middlewares[name] = middleware
|
||||
}
|
||||
|
||||
router.Middlewares = append(router.Middlewares, name)
|
||||
}
|
||||
|
||||
// Traefik internal service can be used only if there is only one BackendRef service reference.
|
||||
if len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0].BackendRef) {
|
||||
router.Service = string(routeRule.BackendRefs[0].Name)
|
||||
} else {
|
||||
for _, backendRef := range routeRule.BackendRefs {
|
||||
name, svc, errCondition := p.loadHTTPService(client, route, backendRef)
|
||||
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
||||
if errCondition != nil {
|
||||
routeConditions = appendCondition(routeConditions, *errCondition)
|
||||
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||
Name: name,
|
||||
Status: ptr.To(500),
|
||||
Weight: weight,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if svc != nil {
|
||||
conf.HTTP.Services[name] = svc
|
||||
}
|
||||
|
||||
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||
Name: name,
|
||||
Weight: weight,
|
||||
})
|
||||
}
|
||||
|
||||
conf.HTTP.Services[wrrName] = &dynamic.Service{Weighted: &wrr}
|
||||
router.Service = wrrName
|
||||
}
|
||||
}
|
||||
|
||||
rt := &router
|
||||
p.applyRouterTransform(ctx, rt, route)
|
||||
|
||||
routerKey = provider.Normalize(routerKey)
|
||||
conf.HTTP.Routers[routerKey] = rt
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return routeConditions
|
||||
for ri, routeRule := range route.Spec.Rules {
|
||||
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||
routeKey := provider.Normalize(fmt.Sprintf("%s-%s-%s-%s-%d", route.Namespace, route.Name, listener.GWName, listener.EPName, ri))
|
||||
|
||||
for _, match := range routeRule.Matches {
|
||||
rule, priority := buildMatchRule(hostnames, match)
|
||||
router := dynamic.Router{
|
||||
RuleSyntax: "v3",
|
||||
Rule: rule,
|
||||
Priority: priority + len(route.Spec.Rules) - ri,
|
||||
EntryPoints: []string{listener.EPName},
|
||||
}
|
||||
if listener.Protocol == gatev1.HTTPSProtocolType {
|
||||
router.TLS = &dynamic.RouterTLSConfig{}
|
||||
}
|
||||
|
||||
var err error
|
||||
routerName := makeRouterName(rule, routeKey)
|
||||
router.Middlewares, err = p.loadMiddlewares(conf, route.Namespace, routerName, routeRule.Filters, match.Path)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to load HTTPRoute filters")
|
||||
|
||||
errWrrName := routerName + "-err-wrr"
|
||||
conf.HTTP.Services[errWrrName] = &dynamic.Service{Weighted: &errWrr}
|
||||
router.Service = errWrrName
|
||||
|
||||
case len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0].BackendRef):
|
||||
router.Service = string(routeRule.BackendRefs[0].Name)
|
||||
|
||||
default:
|
||||
var serviceCondition *metav1.Condition
|
||||
router.Service, serviceCondition = p.loadService(conf, routeKey, routeRule, route)
|
||||
if serviceCondition != nil {
|
||||
condition = *serviceCondition
|
||||
}
|
||||
}
|
||||
|
||||
p.applyRouterTransform(ctx, &router, route)
|
||||
|
||||
conf.HTTP.Routers[routerName] = &router
|
||||
}
|
||||
}
|
||||
|
||||
return conf, condition
|
||||
}
|
||||
|
||||
func (p *Provider) loadService(conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute) (string, *metav1.Condition) {
|
||||
name := routeKey + "-wrr"
|
||||
if _, ok := conf.HTTP.Services[name]; ok {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
var wrr dynamic.WeightedRoundRobin
|
||||
var condition *metav1.Condition
|
||||
for _, backendRef := range routeRule.BackendRefs {
|
||||
svcName, svc, errCondition := p.loadHTTPService(route, backendRef)
|
||||
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
||||
if errCondition != nil {
|
||||
condition = errCondition
|
||||
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||
Name: svcName,
|
||||
Status: ptr.To(500),
|
||||
Weight: weight,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if svc != nil {
|
||||
conf.HTTP.Services[svcName] = svc
|
||||
}
|
||||
|
||||
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||
Name: svcName,
|
||||
Weight: weight,
|
||||
})
|
||||
}
|
||||
|
||||
conf.HTTP.Services[name] = &dynamic.Service{Weighted: &wrr}
|
||||
return name, condition
|
||||
}
|
||||
|
||||
// loadHTTPService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef.
|
||||
// Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc).
|
||||
func (p *Provider) loadHTTPService(client Client, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, *metav1.Condition) {
|
||||
func (p *Provider) loadHTTPService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, *metav1.Condition) {
|
||||
kind := ptr.Deref(backendRef.Kind, "Service")
|
||||
|
||||
group := groupCore
|
||||
if backendRef.Group != nil && *backendRef.Group != "" {
|
||||
group = string(*backendRef.Group)
|
||||
}
|
||||
|
||||
kind := ptr.Deref(backendRef.Kind, "Service")
|
||||
namespace := ptr.Deref(backendRef.Namespace, gatev1.Namespace(route.Namespace))
|
||||
namespaceStr := string(namespace)
|
||||
serviceName := provider.Normalize(makeID(namespaceStr, string(backendRef.Name)))
|
||||
namespace := route.Namespace
|
||||
if backendRef.Namespace != nil && *backendRef.Namespace != "" {
|
||||
namespace = string(*backendRef.Namespace)
|
||||
}
|
||||
|
||||
// TODO support cross namespace through ReferenceGrant.
|
||||
if namespaceStr != route.Namespace {
|
||||
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
||||
|
||||
if err := p.isReferenceGranted(groupGateway, kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
||||
return serviceName, nil, &metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.RouteReasonRefNotPermitted),
|
||||
Message: fmt.Sprintf("Cannot load HTTPBackendRef %s/%s/%s/%s namespace not allowed", group, kind, namespace, backendRef.Name),
|
||||
Message: fmt.Sprintf("Cannot load HTTPBackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
|
||||
}
|
||||
}
|
||||
|
||||
if group != groupCore || kind != "Service" {
|
||||
name, service, err := p.loadHTTPBackendRef(namespaceStr, backendRef)
|
||||
name, service, err := p.loadHTTPBackendRef(namespace, backendRef)
|
||||
if err != nil {
|
||||
return serviceName, nil, &metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
|
@ -270,7 +267,7 @@ func (p *Provider) loadHTTPService(client Client, route *gatev1.HTTPRoute, backe
|
|||
portStr := strconv.FormatInt(int64(port), 10)
|
||||
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
||||
|
||||
lb, err := loadHTTPServers(client, namespaceStr, backendRef)
|
||||
lb, err := p.loadHTTPServers(namespace, backendRef)
|
||||
if err != nil {
|
||||
return serviceName, nil, &metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
|
@ -303,18 +300,21 @@ func (p *Provider) loadHTTPBackendRef(namespace string, backendRef gatev1.HTTPBa
|
|||
return backendFunc(string(backendRef.Name), namespace)
|
||||
}
|
||||
|
||||
func (p *Provider) loadMiddlewares(listenerProtocol gatev1.ProtocolType, namespace, prefix string, filters []gatev1.HTTPRouteFilter) (map[string]*dynamic.Middleware, error) {
|
||||
middlewares := make(map[string]*dynamic.Middleware)
|
||||
func (p *Provider) loadMiddlewares(conf *dynamic.Configuration, namespace, routerName string, filters []gatev1.HTTPRouteFilter, pathMatch *gatev1.HTTPPathMatch) ([]string, error) {
|
||||
pm := ptr.Deref(pathMatch, gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
})
|
||||
|
||||
middlewares := make(map[string]*dynamic.Middleware)
|
||||
for i, filter := range filters {
|
||||
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
|
||||
switch filter.Type {
|
||||
case gatev1.HTTPRouteFilterRequestRedirect:
|
||||
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
||||
middlewares[middlewareName] = createRedirectRegexMiddleware(listenerProtocol, filter.RequestRedirect)
|
||||
middlewares[name] = createRequestRedirect(filter.RequestRedirect, pm)
|
||||
|
||||
case gatev1.HTTPRouteFilterRequestHeaderModifier:
|
||||
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
||||
middlewares[middlewareName] = createRequestHeaderModifier(filter.RequestHeaderModifier)
|
||||
middlewares[name] = createRequestHeaderModifier(filter.RequestHeaderModifier)
|
||||
|
||||
case gatev1.HTTPRouteFilterExtensionRef:
|
||||
name, middleware, err := p.loadHTTPRouteFilterExtensionRef(namespace, filter.ExtensionRef)
|
||||
|
@ -324,6 +324,14 @@ func (p *Provider) loadMiddlewares(listenerProtocol gatev1.ProtocolType, namespa
|
|||
|
||||
middlewares[name] = middleware
|
||||
|
||||
case gatev1.HTTPRouteFilterURLRewrite:
|
||||
var err error
|
||||
middleware, err := createURLRewrite(filter.URLRewrite, pm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter %s: %w", filter.Type, err)
|
||||
}
|
||||
middlewares[name] = middleware
|
||||
|
||||
default:
|
||||
// As per the spec: https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
|
||||
// In all cases where incompatible or unsupported filters are
|
||||
|
@ -333,7 +341,16 @@ func (p *Provider) loadMiddlewares(listenerProtocol gatev1.ProtocolType, namespa
|
|||
}
|
||||
}
|
||||
|
||||
return middlewares, nil
|
||||
var middlewareNames []string
|
||||
for name, middleware := range middlewares {
|
||||
if middleware != nil {
|
||||
conf.HTTP.Middlewares[name] = middleware
|
||||
}
|
||||
|
||||
middlewareNames = append(middlewareNames, name)
|
||||
}
|
||||
|
||||
return middlewareNames, nil
|
||||
}
|
||||
|
||||
func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRef *gatev1.LocalObjectReference) (string, *dynamic.Middleware, error) {
|
||||
|
@ -352,9 +369,8 @@ func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRe
|
|||
return filterFunc(string(extensionRef.Name), namespace)
|
||||
}
|
||||
|
||||
// TODO support cross namespace through ReferencePolicy.
|
||||
func loadHTTPServers(client Client, namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) {
|
||||
service, exists, err := client.GetService(namespace, string(backendRef.Name))
|
||||
func (p *Provider) loadHTTPServers(namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) {
|
||||
service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting service: %w", err)
|
||||
}
|
||||
|
@ -376,7 +392,7 @@ func loadHTTPServers(client Client, namespace string, backendRef gatev1.HTTPBack
|
|||
return nil, errors.New("service port not found")
|
||||
}
|
||||
|
||||
endpoints, endpointsExists, err := client.GetEndpoints(namespace, string(backendRef.Name))
|
||||
endpoints, endpointsExists, err := p.client.GetEndpoints(namespace, string(backendRef.Name))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting endpoints: %w", err)
|
||||
}
|
||||
|
@ -418,12 +434,17 @@ func loadHTTPServers(client Client, namespace string, backendRef gatev1.HTTPBack
|
|||
return lb, nil
|
||||
}
|
||||
|
||||
func hostRule(hostnames []gatev1.Hostname) string {
|
||||
func buildHostRule(hostnames []gatev1.Hostname) (string, int) {
|
||||
var rules []string
|
||||
var priority int
|
||||
|
||||
for _, hostname := range hostnames {
|
||||
host := string(hostname)
|
||||
|
||||
if priority < len(host) {
|
||||
priority = len(host)
|
||||
}
|
||||
|
||||
wildcard := strings.Count(host, "*")
|
||||
if wildcard == 0 {
|
||||
rules = append(rules, fmt.Sprintf("Host(`%s`)", host))
|
||||
|
@ -436,86 +457,97 @@ func hostRule(hostnames []gatev1.Hostname) string {
|
|||
|
||||
switch len(rules) {
|
||||
case 0:
|
||||
return ""
|
||||
return "", 0
|
||||
case 1:
|
||||
return rules[0]
|
||||
return rules[0], priority
|
||||
default:
|
||||
return fmt.Sprintf("(%s)", strings.Join(rules, " || "))
|
||||
return fmt.Sprintf("(%s)", strings.Join(rules, " || ")), priority
|
||||
}
|
||||
}
|
||||
|
||||
func routerRule(routeRule gatev1.HTTPRouteRule, hostRule string) string {
|
||||
var rule string
|
||||
var matchesRules []string
|
||||
// buildMatchRule builds the route rule and computes its priority.
|
||||
// The current priority computing is rather naive but aims to fulfill Conformance tests suite requirement.
|
||||
// The priority is computed to match the following precedence order:
|
||||
//
|
||||
// * "Exact" path match. (+100000)
|
||||
// * "Prefix" path match with largest number of characters. (+10000) PathRegex (+1000)
|
||||
// * Method match. (not implemented)
|
||||
// * Largest number of header matches. (+100 each) or with PathRegex (+10 each)
|
||||
// * Largest number of query param matches. (not implemented)
|
||||
//
|
||||
// In case of multiple matches for a route, the maximum priority among all matches is retain.
|
||||
func buildMatchRule(hostnames []gatev1.Hostname, match gatev1.HTTPRouteMatch) (string, int) {
|
||||
path := ptr.Deref(match.Path, gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
})
|
||||
|
||||
for _, match := range routeRule.Matches {
|
||||
path := ptr.Deref(match.Path, gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
})
|
||||
pathType := ptr.Deref(path.Type, gatev1.PathMatchPathPrefix)
|
||||
pathValue := ptr.Deref(path.Value, "/")
|
||||
var priority int
|
||||
var matchRules []string
|
||||
|
||||
var matchRules []string
|
||||
switch pathType {
|
||||
case gatev1.PathMatchExact:
|
||||
matchRules = append(matchRules, fmt.Sprintf("Path(`%s`)", pathValue))
|
||||
case gatev1.PathMatchPathPrefix:
|
||||
matchRules = append(matchRules, buildPathMatchPathPrefixRule(pathValue))
|
||||
case gatev1.PathMatchRegularExpression:
|
||||
matchRules = append(matchRules, fmt.Sprintf("PathRegexp(`%s`)", pathValue))
|
||||
}
|
||||
pathRule, pathPriority := buildPathRule(path)
|
||||
matchRules = append(matchRules, pathRule)
|
||||
priority += pathPriority
|
||||
|
||||
matchRules = append(matchRules, headerRules(match.Headers)...)
|
||||
matchesRules = append(matchesRules, strings.Join(matchRules, " && "))
|
||||
headerRules, headersPriority := buildHeaderRules(match.Headers)
|
||||
matchRules = append(matchRules, headerRules...)
|
||||
priority += headersPriority
|
||||
|
||||
matchRulesStr := strings.Join(matchRules, " && ")
|
||||
|
||||
hostRule, hostPriority := buildHostRule(hostnames)
|
||||
|
||||
if hostRule == "" {
|
||||
return matchRulesStr, priority
|
||||
}
|
||||
|
||||
// If no matches are specified, the default is a prefix
|
||||
// path match on "/", which has the effect of matching every
|
||||
// HTTP request.
|
||||
if len(routeRule.Matches) == 0 {
|
||||
matchesRules = append(matchesRules, "PathPrefix(`/`)")
|
||||
}
|
||||
|
||||
if hostRule != "" {
|
||||
if len(matchesRules) == 0 {
|
||||
return hostRule
|
||||
}
|
||||
rule += hostRule + " && "
|
||||
}
|
||||
|
||||
if len(matchesRules) == 1 {
|
||||
return rule + matchesRules[0]
|
||||
}
|
||||
|
||||
if len(rule) == 0 {
|
||||
return strings.Join(matchesRules, " || ")
|
||||
}
|
||||
|
||||
return rule + "(" + strings.Join(matchesRules, " || ") + ")"
|
||||
// A route with a host should match over the same route with no host.
|
||||
priority += hostPriority
|
||||
return hostRule + " && " + matchRulesStr, priority
|
||||
}
|
||||
|
||||
func headerRules(headers []gatev1.HTTPHeaderMatch) []string {
|
||||
var headerRules []string
|
||||
func buildPathRule(pathMatch gatev1.HTTPPathMatch) (string, int) {
|
||||
pathType := ptr.Deref(pathMatch.Type, gatev1.PathMatchPathPrefix)
|
||||
pathValue := ptr.Deref(pathMatch.Value, "/")
|
||||
|
||||
switch pathType {
|
||||
case gatev1.PathMatchExact:
|
||||
return fmt.Sprintf("Path(`%s`)", pathValue), 100000
|
||||
|
||||
case gatev1.PathMatchPathPrefix:
|
||||
// PathPrefix(`/`) rule is a catch-all,
|
||||
// here we ensure it would be evaluated last.
|
||||
if pathValue == "/" {
|
||||
return "PathPrefix(`/`)", 1
|
||||
}
|
||||
|
||||
pv := strings.TrimSuffix(pathValue, "/")
|
||||
return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", pv), 10000 + len(pathValue)*100
|
||||
|
||||
case gatev1.PathMatchRegularExpression:
|
||||
return fmt.Sprintf("PathRegexp(`%s`)", pathValue), 1000 + len(pathValue)*100
|
||||
|
||||
default:
|
||||
return "PathPrefix(`/`)", 1
|
||||
}
|
||||
}
|
||||
|
||||
func buildHeaderRules(headers []gatev1.HTTPHeaderMatch) ([]string, int) {
|
||||
var rules []string
|
||||
var priority int
|
||||
for _, header := range headers {
|
||||
typ := ptr.Deref(header.Type, gatev1.HeaderMatchExact)
|
||||
switch typ {
|
||||
case gatev1.HeaderMatchExact:
|
||||
headerRules = append(headerRules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value))
|
||||
rules = append(rules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value))
|
||||
priority += 100
|
||||
case gatev1.HeaderMatchRegularExpression:
|
||||
headerRules = append(headerRules, fmt.Sprintf("HeaderRegexp(`%s`,`%s`)", header.Name, header.Value))
|
||||
rules = append(rules, fmt.Sprintf("HeaderRegexp(`%s`,`%s`)", header.Name, header.Value))
|
||||
priority += 10
|
||||
}
|
||||
}
|
||||
return headerRules
|
||||
}
|
||||
|
||||
func buildPathMatchPathPrefixRule(path string) string {
|
||||
if path == "/" {
|
||||
return "PathPrefix(`/`)"
|
||||
}
|
||||
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", path)
|
||||
return rules, priority
|
||||
}
|
||||
|
||||
// createRequestHeaderModifier does not enforce/check the configuration,
|
||||
|
@ -540,31 +572,76 @@ func createRequestHeaderModifier(filter *gatev1.HTTPHeaderFilter) *dynamic.Middl
|
|||
}
|
||||
}
|
||||
|
||||
func createRedirectRegexMiddleware(listenerProtocol gatev1.ProtocolType, filter *gatev1.HTTPRequestRedirectFilter) *dynamic.Middleware {
|
||||
// The spec allows for an empty string in which case we should use the
|
||||
// scheme of the request which in this case is the listener scheme.
|
||||
filterScheme := ptr.Deref(filter.Scheme, strings.ToLower(string(listenerProtocol)))
|
||||
statusCode := ptr.Deref(filter.StatusCode, http.StatusFound)
|
||||
|
||||
port := "${port}"
|
||||
if filter.Port != nil {
|
||||
port = fmt.Sprintf(":%d", *filter.Port)
|
||||
func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch gatev1.HTTPPathMatch) *dynamic.Middleware {
|
||||
var hostname *string
|
||||
if filter.Hostname != nil {
|
||||
hostname = ptr.To(string(*filter.Hostname))
|
||||
}
|
||||
|
||||
hostname := "${hostname}"
|
||||
if filter.Hostname != nil && *filter.Hostname != "" {
|
||||
hostname = string(*filter.Hostname)
|
||||
var port *string
|
||||
filterScheme := ptr.Deref(filter.Scheme, "")
|
||||
if filterScheme == "http" || filterScheme == "https" {
|
||||
port = ptr.To("")
|
||||
}
|
||||
if filter.Port != nil {
|
||||
port = ptr.To(fmt.Sprintf("%d", *filter.Port))
|
||||
}
|
||||
|
||||
var path *string
|
||||
var pathPrefix *string
|
||||
if filter.Path != nil {
|
||||
switch filter.Path.Type {
|
||||
case gatev1.FullPathHTTPPathModifier:
|
||||
path = filter.Path.ReplaceFullPath
|
||||
case gatev1.PrefixMatchHTTPPathModifier:
|
||||
path = filter.Path.ReplacePrefixMatch
|
||||
pathPrefix = pathMatch.Value
|
||||
}
|
||||
}
|
||||
|
||||
return &dynamic.Middleware{
|
||||
RedirectRegex: &dynamic.RedirectRegex{
|
||||
Regex: `^[a-z]+:\/\/(?P<userInfo>.+@)?(?P<hostname>\[[\w:\.]+\]|[\w\._-]+)(?P<port>:\d+)?\/(?P<path>.*)`,
|
||||
Replacement: fmt.Sprintf("%s://${userinfo}%s%s/${path}", filterScheme, hostname, port),
|
||||
Permanent: statusCode == http.StatusMovedPermanently,
|
||||
RequestRedirect: &dynamic.RequestRedirect{
|
||||
Scheme: filter.Scheme,
|
||||
Hostname: hostname,
|
||||
Port: port,
|
||||
Path: path,
|
||||
PathPrefix: pathPrefix,
|
||||
StatusCode: ptr.Deref(filter.StatusCode, http.StatusFound),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createURLRewrite(filter *gatev1.HTTPURLRewriteFilter, pathMatch gatev1.HTTPPathMatch) (*dynamic.Middleware, error) {
|
||||
if filter.Path == nil && filter.Hostname == nil {
|
||||
return nil, errors.New("empty configuration")
|
||||
}
|
||||
|
||||
var host *string
|
||||
if filter.Hostname != nil {
|
||||
host = ptr.To(string(*filter.Hostname))
|
||||
}
|
||||
|
||||
var path *string
|
||||
var pathPrefix *string
|
||||
if filter.Path != nil {
|
||||
switch filter.Path.Type {
|
||||
case gatev1.FullPathHTTPPathModifier:
|
||||
path = filter.Path.ReplaceFullPath
|
||||
case gatev1.PrefixMatchHTTPPathModifier:
|
||||
path = filter.Path.ReplacePrefixMatch
|
||||
pathPrefix = pathMatch.Value
|
||||
}
|
||||
}
|
||||
|
||||
return &dynamic.Middleware{
|
||||
URLRewrite: &dynamic.URLRewrite{
|
||||
Hostname: host,
|
||||
Path: path,
|
||||
PathPrefix: pathPrefix,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getProtocol(portSpec corev1.ServicePort) string {
|
||||
protocol := "http"
|
||||
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") {
|
||||
|
@ -573,3 +650,35 @@ func getProtocol(portSpec corev1.ServicePort) string {
|
|||
|
||||
return protocol
|
||||
}
|
||||
|
||||
func mergeHTTPConfiguration(from, to *dynamic.Configuration) {
|
||||
if from == nil || from.HTTP == nil || to == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if to.HTTP == nil {
|
||||
to.HTTP = from.HTTP
|
||||
return
|
||||
}
|
||||
|
||||
if to.HTTP.Routers == nil {
|
||||
to.HTTP.Routers = map[string]*dynamic.Router{}
|
||||
}
|
||||
for routerName, router := range from.HTTP.Routers {
|
||||
to.HTTP.Routers[routerName] = router
|
||||
}
|
||||
|
||||
if to.HTTP.Middlewares == nil {
|
||||
to.HTTP.Middlewares = map[string]*dynamic.Middleware{}
|
||||
}
|
||||
for middlewareName, middleware := range from.HTTP.Middlewares {
|
||||
to.HTTP.Middlewares[middlewareName] = middleware
|
||||
}
|
||||
|
||||
if to.HTTP.Services == nil {
|
||||
to.HTTP.Services = map[string]*dynamic.Service{}
|
||||
}
|
||||
for serviceName, service := range from.HTTP.Services {
|
||||
to.HTTP.Services[serviceName] = service
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,15 +8,16 @@ import (
|
|||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
func Test_hostRule(t *testing.T) {
|
||||
func Test_buildHostRule(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
hostnames []gatev1.Hostname
|
||||
expectedRule string
|
||||
expectErr bool
|
||||
desc string
|
||||
hostnames []gatev1.Hostname
|
||||
expectedRule string
|
||||
expectedPriority int
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
desc: "Empty rule and matches",
|
||||
desc: "Empty (should not happen)",
|
||||
expectedRule: "",
|
||||
},
|
||||
{
|
||||
|
@ -24,7 +25,8 @@ func Test_hostRule(t *testing.T) {
|
|||
hostnames: []gatev1.Hostname{
|
||||
"Foo",
|
||||
},
|
||||
expectedRule: "Host(`Foo`)",
|
||||
expectedRule: "Host(`Foo`)",
|
||||
expectedPriority: 3,
|
||||
},
|
||||
{
|
||||
desc: "Multiple Hosts",
|
||||
|
@ -33,7 +35,8 @@ func Test_hostRule(t *testing.T) {
|
|||
"Bar",
|
||||
"Bir",
|
||||
},
|
||||
expectedRule: "(Host(`Foo`) || Host(`Bar`) || Host(`Bir`))",
|
||||
expectedRule: "(Host(`Foo`) || Host(`Bar`) || Host(`Bir`))",
|
||||
expectedPriority: 3,
|
||||
},
|
||||
{
|
||||
desc: "Several Host and wildcard",
|
||||
|
@ -42,14 +45,16 @@ func Test_hostRule(t *testing.T) {
|
|||
"bar.foo",
|
||||
"foo.foo",
|
||||
},
|
||||
expectedRule: "(HostRegexp(`^[a-z0-9-\\.]+\\.bar\\.foo$`) || Host(`bar.foo`) || Host(`foo.foo`))",
|
||||
expectedRule: "(HostRegexp(`^[a-z0-9-\\.]+\\.bar\\.foo$`) || Host(`bar.foo`) || Host(`foo.foo`))",
|
||||
expectedPriority: 9,
|
||||
},
|
||||
{
|
||||
desc: "Host with wildcard",
|
||||
hostnames: []gatev1.Hostname{
|
||||
"*.bar.foo",
|
||||
},
|
||||
expectedRule: "HostRegexp(`^[a-z0-9-\\.]+\\.bar\\.foo$`)",
|
||||
expectedRule: "HostRegexp(`^[a-z0-9-\\.]+\\.bar\\.foo$`)",
|
||||
expectedPriority: 9,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -57,216 +62,134 @@ func Test_hostRule(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rule := hostRule(test.hostnames)
|
||||
rule, priority := buildHostRule(test.hostnames)
|
||||
assert.Equal(t, test.expectedRule, rule)
|
||||
assert.Equal(t, test.expectedPriority, priority)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_routerRule(t *testing.T) {
|
||||
func Test_buildMatchRule(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
routeRule gatev1.HTTPRouteRule
|
||||
hostRule string
|
||||
expectedRule string
|
||||
expectedError bool
|
||||
desc string
|
||||
routeMatch gatev1.HTTPRouteMatch
|
||||
hostnames []gatev1.Hostname
|
||||
expectedRule string
|
||||
expectedPriority int
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
desc: "Empty rule and matches",
|
||||
expectedRule: "PathPrefix(`/`)",
|
||||
desc: "Empty rule and matches ",
|
||||
expectedRule: "PathPrefix(`/`)",
|
||||
expectedPriority: 1,
|
||||
},
|
||||
{
|
||||
desc: "One Host rule without matches",
|
||||
hostRule: "Host(`foo.com`)",
|
||||
expectedRule: "Host(`foo.com`) && PathPrefix(`/`)",
|
||||
desc: "One Host rule without match",
|
||||
hostnames: []gatev1.Hostname{"foo.com"},
|
||||
expectedRule: "Host(`foo.com`) && PathPrefix(`/`)",
|
||||
expectedPriority: 8,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
}),
|
||||
Headers: nil,
|
||||
},
|
||||
},
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
}),
|
||||
Headers: nil,
|
||||
},
|
||||
expectedRule: "PathPrefix(`/`)",
|
||||
expectedRule: "PathPrefix(`/`)",
|
||||
expectedPriority: 1,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch Type",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
}),
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
}),
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
expectedRule: "PathPrefix(`/`) && Header(`foo`,`bar`)",
|
||||
expectedRule: "PathPrefix(`/`) && Header(`foo`,`bar`)",
|
||||
expectedPriority: 101,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
{Path: nil},
|
||||
},
|
||||
},
|
||||
expectedRule: "PathPrefix(`/`)",
|
||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch",
|
||||
routeMatch: gatev1.HTTPRouteMatch{Path: nil},
|
||||
expectedRule: "PathPrefix(`/`)",
|
||||
expectedPriority: 1,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch Type",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: nil,
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
},
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: nil,
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
},
|
||||
expectedRule: "(Path(`/foo`) || PathPrefix(`/foo/`))",
|
||||
expectedRule: "(Path(`/foo`) || PathPrefix(`/foo/`))",
|
||||
expectedPriority: 10500,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch Values",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: nil,
|
||||
},
|
||||
},
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: nil,
|
||||
},
|
||||
},
|
||||
expectedRule: "Path(`/`)",
|
||||
expectedRule: "Path(`/`)",
|
||||
expectedPriority: 100000,
|
||||
},
|
||||
{
|
||||
desc: "One Path in matches",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
},
|
||||
desc: "One Path",
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
},
|
||||
expectedRule: "Path(`/foo/`)",
|
||||
expectedRule: "Path(`/foo/`)",
|
||||
expectedPriority: 100000,
|
||||
},
|
||||
{
|
||||
desc: "One Path in matches and another empty",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
desc: "Path && Header",
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||
Name: "my-header",
|
||||
Value: "foo",
|
||||
},
|
||||
{},
|
||||
},
|
||||
},
|
||||
expectedRule: "Path(`/foo/`) || PathPrefix(`/`)",
|
||||
expectedRule: "Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||
expectedPriority: 100100,
|
||||
},
|
||||
{
|
||||
desc: "Path OR Header rules",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
desc: "Host && Path && Header",
|
||||
hostnames: []gatev1.Hostname{"foo.com"},
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{
|
||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||
Name: "my-header",
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||
Name: "my-header",
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRule: "Path(`/foo/`) || PathPrefix(`/`) && Header(`my-header`,`foo`)",
|
||||
},
|
||||
{
|
||||
desc: "Path && Header rules",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{
|
||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||
Name: "my-header",
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRule: "Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||
},
|
||||
{
|
||||
desc: "Host && Path && Header rules",
|
||||
hostRule: "Host(`foo.com`)",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{
|
||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||
Name: "my-header",
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||
},
|
||||
{
|
||||
desc: "Host && (Path || Header) rules",
|
||||
hostRule: "Host(`foo.com`)",
|
||||
routeRule: gatev1.HTTPRouteRule{
|
||||
Matches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{
|
||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||
Name: "my-header",
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRule: "Host(`foo.com`) && (Path(`/foo/`) || PathPrefix(`/`) && Header(`my-header`,`foo`))",
|
||||
expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||
expectedPriority: 100107,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -274,8 +197,9 @@ func Test_routerRule(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rule := routerRule(test.routeRule, test.hostRule)
|
||||
rule, priority := buildMatchRule(test.hostnames, test.routeMatch)
|
||||
assert.Equal(t, test.expectedRule, rule)
|
||||
assert.Equal(t, test.expectedPriority, priority)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@ const (
|
|||
providerName = "kubernetesgateway"
|
||||
controllerName = "traefik.io/gateway-controller"
|
||||
|
||||
groupCore = "core"
|
||||
groupCore = "core"
|
||||
groupGateway = "gateway.networking.k8s.io"
|
||||
|
||||
kindGateway = "Gateway"
|
||||
kindTraefikService = "TraefikService"
|
||||
|
@ -67,6 +68,7 @@ type Provider struct {
|
|||
lastConfiguration safe.Safe
|
||||
|
||||
routerTransform k8s.RouterTransform
|
||||
client *clientWrapper
|
||||
}
|
||||
|
||||
// Entrypoint defines the available entry points.
|
||||
|
@ -193,6 +195,14 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
|||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
logger := log.With().Str(logs.ProviderName, providerName).Logger()
|
||||
|
||||
var err error
|
||||
p.client, err = p.newK8sClient(logger.WithContext(context.Background()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating k8s client: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -201,14 +211,9 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
logger := log.With().Str(logs.ProviderName, providerName).Logger()
|
||||
ctxLog := logger.WithContext(context.Background())
|
||||
|
||||
k8sClient, err := p.newK8sClient(ctxLog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pool.GoCtx(func(ctxPool context.Context) {
|
||||
operation := func() error {
|
||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
eventsChan, err := p.client.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Error watching kubernetes events")
|
||||
timer := time.NewTimer(1 * time.Second)
|
||||
|
@ -234,7 +239,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
// Note that event is the *first* event that came in during this throttling interval -- if we're hitting our throttle, we may have dropped events.
|
||||
// This is fine, because we don't treat different event types differently.
|
||||
// But if we do in the future, we'll need to track more information about the dropped events.
|
||||
conf := p.loadConfigurationFromGateways(ctxLog, k8sClient)
|
||||
conf := p.loadConfigurationFromGateways(ctxLog)
|
||||
|
||||
confHash, err := hashstructure.Hash(conf, nil)
|
||||
switch {
|
||||
|
@ -271,7 +276,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
}
|
||||
|
||||
// TODO Handle errors and update resources statuses (gatewayClass, gateway).
|
||||
func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Client) *dynamic.Configuration {
|
||||
func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.Configuration {
|
||||
conf := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
|
@ -292,13 +297,13 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
TLS: &dynamic.TLSConfiguration{},
|
||||
}
|
||||
|
||||
addresses, err := p.gatewayAddresses(client)
|
||||
addresses, err := p.gatewayAddresses()
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to get Gateway status addresses")
|
||||
return nil
|
||||
}
|
||||
|
||||
gatewayClasses, err := client.ListGatewayClasses()
|
||||
gatewayClasses, err := p.client.ListGatewayClasses()
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to list GatewayClasses")
|
||||
return nil
|
||||
|
@ -312,7 +317,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
|
||||
gatewayClassNames[gatewayClass.Name] = struct{}{}
|
||||
|
||||
err := client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{
|
||||
err := p.client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{
|
||||
Type: string(gatev1.GatewayClassConditionStatusAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: gatewayClass.Generation,
|
||||
|
@ -329,7 +334,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
}
|
||||
}
|
||||
|
||||
gateways := client.ListGateways()
|
||||
gateways := p.client.ListGateways()
|
||||
|
||||
var gatewayListeners []gatewayListener
|
||||
for _, gateway := range gateways {
|
||||
|
@ -342,14 +347,14 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
continue
|
||||
}
|
||||
|
||||
gatewayListeners = append(gatewayListeners, p.loadGatewayListeners(logger.WithContext(ctx), client, gateway, conf)...)
|
||||
gatewayListeners = append(gatewayListeners, p.loadGatewayListeners(logger.WithContext(ctx), gateway, conf)...)
|
||||
}
|
||||
|
||||
p.loadHTTPRoutes(ctx, client, gatewayListeners, conf)
|
||||
p.loadHTTPRoutes(ctx, gatewayListeners, conf)
|
||||
|
||||
if p.ExperimentalChannel {
|
||||
p.loadTCPRoutes(ctx, client, gatewayListeners, conf)
|
||||
p.loadTLSRoutes(ctx, client, gatewayListeners, conf)
|
||||
p.loadTCPRoutes(ctx, gatewayListeners, conf)
|
||||
p.loadTLSRoutes(ctx, gatewayListeners, conf)
|
||||
}
|
||||
|
||||
for _, gateway := range gateways {
|
||||
|
@ -366,7 +371,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
}
|
||||
|
||||
gatewayStatus, errG := p.makeGatewayStatus(gateway, listeners, addresses)
|
||||
if err = client.UpdateGatewayStatus(gateway, gatewayStatus); err != nil {
|
||||
if err = p.client.UpdateGatewayStatus(gateway, gatewayStatus); err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Msg("Unable to update Gateway status")
|
||||
|
@ -381,7 +386,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
return conf
|
||||
}
|
||||
|
||||
func (p *Provider) loadGatewayListeners(ctx context.Context, client Client, gateway *gatev1.Gateway, conf *dynamic.Configuration) []gatewayListener {
|
||||
func (p *Provider) loadGatewayListeners(ctx context.Context, gateway *gatev1.Gateway, conf *dynamic.Configuration) []gatewayListener {
|
||||
tlsConfigs := make(map[string]*tls.CertAndStores)
|
||||
allocatedListeners := make(map[string]struct{})
|
||||
gatewayListeners := make([]gatewayListener, len(gateway.Spec.Listeners))
|
||||
|
@ -419,7 +424,7 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, client Client, gate
|
|||
gatewayListeners[i].EPName = ep
|
||||
|
||||
allowedRoutes := ptr.Deref(listener.AllowedRoutes, gatev1.AllowedRoutes{Namespaces: &gatev1.RouteNamespaces{From: ptr.To(gatev1.NamespacesFromSame)}})
|
||||
gatewayListeners[i].AllowedNamespaces, err = allowedNamespaces(client, gateway.Namespace, allowedRoutes.Namespaces)
|
||||
gatewayListeners[i].AllowedNamespaces, err = p.allowedNamespaces(gateway.Namespace, allowedRoutes.Namespaces)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
|
||||
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
||||
|
@ -564,40 +569,22 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, client Client, gate
|
|||
certificateNamespace = string(*certificateRef.Namespace)
|
||||
}
|
||||
|
||||
if certificateNamespace != gateway.Namespace {
|
||||
referenceGrants, err := client.ListReferenceGrants(certificateNamespace)
|
||||
if err != nil {
|
||||
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonRefNotPermitted),
|
||||
Message: fmt.Sprintf("Cannot find any ReferenceGrant: %v", err),
|
||||
})
|
||||
if err := p.isReferenceGranted(groupGateway, kindGateway, gateway.Namespace, groupCore, "Secret", string(certificateRef.Name), certificateNamespace); err != nil {
|
||||
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonRefNotPermitted),
|
||||
Message: fmt.Sprintf("Cannot load CertificateRef %s/%s: %s", certificateNamespace, certificateRef.Name, err),
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
referenceGrants = filterReferenceGrantsFrom(referenceGrants, "gateway.networking.k8s.io", "Gateway", gateway.Namespace)
|
||||
referenceGrants = filterReferenceGrantsTo(referenceGrants, groupCore, "Secret", string(certificateRef.Name))
|
||||
if len(referenceGrants) == 0 {
|
||||
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: gateway.Generation,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: string(gatev1.ListenerReasonRefNotPermitted),
|
||||
Message: "Required ReferenceGrant for cross namespace secret reference is missing",
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
configKey := certificateNamespace + "/" + string(certificateRef.Name)
|
||||
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
|
||||
tlsConf, err := getTLS(client, certificateRef.Name, certificateNamespace)
|
||||
tlsConf, err := p.getTLS(certificateRef.Name, certificateNamespace)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status false with "InvalidCertificateRef" reason
|
||||
// update "Programmed" status false with "Invalid" reason
|
||||
|
@ -720,7 +707,7 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listeners []gatewa
|
|||
return gatewayStatus, nil
|
||||
}
|
||||
|
||||
func (p *Provider) gatewayAddresses(client Client) ([]gatev1.GatewayStatusAddress, error) {
|
||||
func (p *Provider) gatewayAddresses() ([]gatev1.GatewayStatusAddress, error) {
|
||||
if p.StatusAddress == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -741,7 +728,7 @@ func (p *Provider) gatewayAddresses(client Client) ([]gatev1.GatewayStatusAddres
|
|||
|
||||
svcRef := p.StatusAddress.Service
|
||||
if svcRef.Name != "" && svcRef.Namespace != "" {
|
||||
svc, exists, err := client.GetService(svcRef.Namespace, svcRef.Name)
|
||||
svc, exists, err := p.client.GetService(svcRef.Namespace, svcRef.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get service: %w", err)
|
||||
}
|
||||
|
@ -789,6 +776,71 @@ func (p *Provider) entryPointName(port gatev1.PortNumber, protocol gatev1.Protoc
|
|||
return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol)
|
||||
}
|
||||
|
||||
func (p *Provider) isReferenceGranted(fromGroup, fromKind, fromNamespace, toGroup, toKind, toName, toNamespace string) error {
|
||||
if toNamespace == fromNamespace {
|
||||
return nil
|
||||
}
|
||||
|
||||
refGrants, err := p.client.ListReferenceGrants(toNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing ReferenceGrant: %w", err)
|
||||
}
|
||||
|
||||
refGrants = filterReferenceGrantsFrom(refGrants, fromGroup, fromKind, fromNamespace)
|
||||
refGrants = filterReferenceGrantsTo(refGrants, toGroup, toKind, toName)
|
||||
if len(refGrants) == 0 {
|
||||
return errors.New("missing ReferenceGrant")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) getTLS(secretName gatev1.ObjectName, namespace string) (*tls.CertAndStores, error) {
|
||||
secret, exists, err := p.client.GetSecret(namespace, string(secretName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch secret %s/%s: %w", namespace, secretName, err)
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("secret %s/%s does not exist", namespace, secretName)
|
||||
}
|
||||
|
||||
cert, key, err := getCertificateBlocks(secret, namespace, string(secretName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.CertAndStores{
|
||||
Certificate: tls.Certificate{
|
||||
CertFile: types.FileOrContent(cert),
|
||||
KeyFile: types.FileOrContent(key),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) allowedNamespaces(gatewayNamespace string, routeNamespaces *gatev1.RouteNamespaces) ([]string, error) {
|
||||
if routeNamespaces == nil || routeNamespaces.From == nil {
|
||||
return []string{gatewayNamespace}, nil
|
||||
}
|
||||
|
||||
switch *routeNamespaces.From {
|
||||
case gatev1.NamespacesFromAll:
|
||||
return []string{metav1.NamespaceAll}, nil
|
||||
|
||||
case gatev1.NamespacesFromSame:
|
||||
return []string{gatewayNamespace}, nil
|
||||
|
||||
case gatev1.NamespacesFromSelector:
|
||||
selector, err := metav1.LabelSelectorAsSelector(routeNamespaces.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed selector: %w", err)
|
||||
}
|
||||
|
||||
return p.client.ListNamespaces(selector)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported RouteSelectType: %q", *routeNamespaces.From)
|
||||
}
|
||||
|
||||
func supportedRouteKinds(protocol gatev1.ProtocolType, experimentalChannel bool) ([]gatev1.RouteGroupKind, []metav1.Condition) {
|
||||
group := gatev1.Group(gatev1.GroupName)
|
||||
|
||||
|
@ -873,30 +925,6 @@ func allowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, suppor
|
|||
return routeKinds, conditions
|
||||
}
|
||||
|
||||
func allowedNamespaces(client Client, gatewayNamespace string, routeNamespaces *gatev1.RouteNamespaces) ([]string, error) {
|
||||
if routeNamespaces == nil || routeNamespaces.From == nil {
|
||||
return []string{gatewayNamespace}, nil
|
||||
}
|
||||
|
||||
switch *routeNamespaces.From {
|
||||
case gatev1.NamespacesFromAll:
|
||||
return []string{metav1.NamespaceAll}, nil
|
||||
|
||||
case gatev1.NamespacesFromSame:
|
||||
return []string{gatewayNamespace}, nil
|
||||
|
||||
case gatev1.NamespacesFromSelector:
|
||||
selector, err := metav1.LabelSelectorAsSelector(routeNamespaces.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed selector: %w", err)
|
||||
}
|
||||
|
||||
return client.ListNamespaces(selector)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported RouteSelectType: %q", *routeNamespaces.From)
|
||||
}
|
||||
|
||||
func findMatchingHostnames(listenerHostname *gatev1.Hostname, routeHostnames []gatev1.Hostname) ([]gatev1.Hostname, bool) {
|
||||
if listenerHostname == nil {
|
||||
return routeHostnames, true
|
||||
|
@ -988,7 +1016,7 @@ func matchListener(listener gatewayListener, routeNamespace string, parentRef ga
|
|||
return true
|
||||
}
|
||||
|
||||
func makeRouterKey(rule, name string) string {
|
||||
func makeRouterName(rule, name string) string {
|
||||
h := sha256.New()
|
||||
|
||||
// As explained in https://pkg.go.dev/hash#Hash,
|
||||
|
@ -998,36 +1026,6 @@ func makeRouterKey(rule, name string) string {
|
|||
return fmt.Sprintf("%s-%.10x", name, h.Sum(nil))
|
||||
}
|
||||
|
||||
func makeID(namespace, name string) string {
|
||||
if namespace == "" {
|
||||
return name
|
||||
}
|
||||
|
||||
return namespace + "-" + name
|
||||
}
|
||||
|
||||
func getTLS(k8sClient Client, secretName gatev1.ObjectName, namespace string) (*tls.CertAndStores, error) {
|
||||
secret, exists, err := k8sClient.GetSecret(namespace, string(secretName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch secret %s/%s: %w", namespace, secretName, err)
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("secret %s/%s does not exist", namespace, secretName)
|
||||
}
|
||||
|
||||
cert, key, err := getCertificateBlocks(secret, namespace, string(secretName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.CertAndStores{
|
||||
Certificate: tls.Certificate{
|
||||
CertFile: types.FileOrContent(cert),
|
||||
KeyFile: types.FileOrContent(key),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores {
|
||||
var secretNames []string
|
||||
for secretName := range tlsConfigs {
|
||||
|
@ -1193,13 +1191,38 @@ func kindToString(p *gatev1.Kind) string {
|
|||
return string(*p)
|
||||
}
|
||||
|
||||
func appendCondition(conditions []metav1.Condition, condition metav1.Condition) []metav1.Condition {
|
||||
res := []metav1.Condition{condition}
|
||||
func updateRouteConditionAccepted(conditions []metav1.Condition, reason string) []metav1.Condition {
|
||||
var conds []metav1.Condition
|
||||
for _, c := range conditions {
|
||||
if c.Type != condition.Type {
|
||||
res = append(res, c)
|
||||
if c.Type == string(gatev1.RouteConditionAccepted) && c.Status != metav1.ConditionTrue {
|
||||
c.Reason = reason
|
||||
c.LastTransitionTime = metav1.Now()
|
||||
|
||||
if reason == string(gatev1.RouteReasonAccepted) {
|
||||
c.Status = metav1.ConditionTrue
|
||||
}
|
||||
}
|
||||
|
||||
conds = append(conds, c)
|
||||
}
|
||||
|
||||
return res
|
||||
return conds
|
||||
}
|
||||
|
||||
func upsertRouteConditionResolvedRefs(conditions []metav1.Condition, condition metav1.Condition) []metav1.Condition {
|
||||
var (
|
||||
curr *metav1.Condition
|
||||
conds []metav1.Condition
|
||||
)
|
||||
for _, c := range conditions {
|
||||
if c.Type == string(gatev1.RouteConditionResolvedRefs) {
|
||||
curr = &c
|
||||
continue
|
||||
}
|
||||
conds = append(conds, c)
|
||||
}
|
||||
if curr != nil && curr.Status == metav1.ConditionFalse && condition.Status == metav1.ConditionTrue {
|
||||
return append(conds, *curr)
|
||||
}
|
||||
return append(conds, condition)
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue