diff --git a/.dockerignore b/.dockerignore index 8390d87b6..bcd757988 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,5 @@ dist/ -!dist/traefik +!dist/**/traefik site/ vendor/ .idea/ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 343b6949d..0d9a9e38c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,11 +2,11 @@ PLEASE READ THIS MESSAGE. Documentation fixes or enhancements: -- for Traefik v2: use branch v2.10 +- for Traefik v2: use branch v2.11 - for Traefik v3: use branch v3.0 Bug fixes: -- for Traefik v2: use branch v2.10 +- for Traefik v2: use branch v2.11 - for Traefik v3: use branch v3.0 Enhancements: diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a219162d1..d7cf99bcb 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,9 +6,8 @@ on: - '*' env: - GO_VERSION: '1.20' + GO_VERSION: '1.21' CGO_ENABLED: 0 - IN_DOCKER: "" jobs: @@ -17,7 +16,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -39,38 +38,22 @@ jobs: os: [ ubuntu-20.04, macos-latest, windows-latest ] needs: - build-webui - defaults: - run: - working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik steps: - - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GO_VERSION }} - - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: - path: go/src/github.com/traefik/traefik fetch-depth: 0 - - name: Cache Go modules - uses: actions/cache@v3 + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - ~/Library/Caches/go-build - '%LocalAppData%\go-build' - key: ${{ runner.os }}-build-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-build-go- + go-version: ${{ env.GO_VERSION }} - name: Artifact webui uses: actions/download-artifact@v2 with: name: webui.tar.gz - path: ${{ github.workspace }}/go/src/github.com/traefik/traefik - name: Untar webui run: tar xvf webui.tar.gz diff --git a/.github/workflows/check_doc.yml b/.github/workflows/check_doc.yml index c800ad248..9f08efbb1 100644 --- a/.github/workflows/check_doc.yml +++ b/.github/workflows/check_doc.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f8a3f018f..d3ef51b41 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,10 +5,6 @@ on: branches: - master - v* - pull_request: - # The branches below must be a subset of the branches above - branches: - - '*' schedule: - cron: '11 22 * * 1' @@ -32,7 +28,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 4aec39fbc..b52635030 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/experimental.yaml b/.github/workflows/experimental.yaml index 2b002a484..a26178315 100644 --- a/.github/workflows/experimental.yaml +++ b/.github/workflows/experimental.yaml @@ -6,6 +6,10 @@ on: - master - v* +env: + GO_VERSION: '1.21' + CGO_ENABLED: 0 + jobs: experimental: @@ -17,21 +21,39 @@ jobs: # https://github.com/marketplace/actions/checkout - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Build webui + run: | + make clean-webui generate-webui + + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Build + run: make generate binary + - name: Branch name run: echo ${GITHUB_REF##*/} - - name: Build docker experimental image - run: docker build -t traefik/traefik:experimental-${GITHUB_REF##*/} -f exp.Dockerfile . - - name: Login to Docker Hub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Push to Docker Hub - run: docker push traefik/traefik:experimental-${GITHUB_REF##*/} + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build docker experimental image + env: + DOCKER_BUILDX_ARGS: "--push" + run: | + make multi-arch-image-experimental-${GITHUB_REF##*/} diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml new file mode 100644 index 000000000..d5c4e5ca1 --- /dev/null +++ b/.github/workflows/test-integration.yaml @@ -0,0 +1,75 @@ +name: Test Integration + +on: + pull_request: + branches: + - '*' + push: + branches: + - 'gh-actions' + +env: + GO_VERSION: '1.21' + CGO_ENABLED: 0 + +jobs: + + build: + runs-on: ubuntu-20.04 + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Avoid generating webui + run: touch webui/static/index.html + + - name: Build binary + run: make binary + + test-integration: + runs-on: ubuntu-20.04 + needs: + - build + strategy: + fail-fast: true + matrix: + parallel: [12] + index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11] + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Avoid generating webui + run: touch webui/static/index.html + + - name: Build binary + run: make binary + + - name: Generate go test Slice + id: test_split + uses: hashicorp-forge/go-test-split-action@v1 + with: + packages: ./integration + total: ${{ matrix.parallel }} + index: ${{ matrix.index }} + + - name: Run Integration tests + run: | + TESTS=$(echo "${{ steps.test_split.outputs.run}}" | sed 's/\$/\$\$/g') + TESTFLAGS="-run \"${TESTS}\"" make test-integration diff --git a/.github/workflows/test-unit.yaml b/.github/workflows/test-unit.yaml index ad9edcf41..7719186f7 100644 --- a/.github/workflows/test-unit.yaml +++ b/.github/workflows/test-unit.yaml @@ -6,38 +6,23 @@ on: - '*' env: - GO_VERSION: '1.20' - IN_DOCKER: "" + GO_VERSION: '1.21' jobs: test-unit: runs-on: ubuntu-20.04 - defaults: - run: - working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik - steps: - - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GO_VERSION }} - - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: - path: go/src/github.com/traefik/traefik fetch-depth: 0 - - name: Cache Go modules - uses: actions/cache@v3 + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - key: ${{ runner.os }}-test-unit-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-test-unit-go- + go-version: ${{ env.GO_VERSION }} - name: Avoid generating webui run: touch webui/static/index.html diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index e40e51dbb..f836e94ee 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -6,40 +6,25 @@ on: - '*' env: - GO_VERSION: '1.20' - GOLANGCI_LINT_VERSION: v1.53.1 - MISSSPELL_VERSION: v0.4.0 - IN_DOCKER: "" + GO_VERSION: '1.21' + GOLANGCI_LINT_VERSION: v1.55.2 + MISSSPELL_VERSION: v0.4.1 jobs: validate: runs-on: ubuntu-20.04 - defaults: - run: - working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik - steps: - - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GO_VERSION }} - - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: - path: go/src/github.com/traefik/traefik fetch-depth: 0 - - name: Cache Go modules - uses: actions/cache@v3 + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - key: ${{ runner.os }}-validate-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-validate-go- + go-version: ${{ env.GO_VERSION }} - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} @@ -56,34 +41,20 @@ jobs: validate-generate: runs-on: ubuntu-20.04 - defaults: - run: - working-directory: ${{ github.workspace }}/go/src/github.com/traefik/traefik - steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - - name: Check out code - uses: actions/checkout@v2 - with: - path: go/src/github.com/traefik/traefik - fetch-depth: 0 - - - name: Cache Go modules - uses: actions/cache@v3 - with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - key: ${{ runner.os }}-validate-generate-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-validate-generate-go- - - name: go generate run: | - go generate + make generate git diff --exit-code - name: go mod tidy diff --git a/.golangci.yml b/.golangci.yml index 3b42c4aad..7128a4177 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -87,8 +87,6 @@ linters-settings: pkg: "sigs.k8s.io/gateway-api/apis/v1alpha2" # Traefik Kubernetes rewrites: - - alias: containousv1alpha1 - pkg: "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikcontainous/v1alpha1" - alias: traefikv1alpha1 pkg: "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" - alias: traefikclientset @@ -146,11 +144,23 @@ linters-settings: gomoddirectives: replace-allow-list: - github.com/abbot/go-http-auth - - github.com/go-check/check - github.com/gorilla/mux - github.com/mailgun/minheap - github.com/mailgun/multibuf - github.com/jaguilar/vt100 + - github.com/cucumber/godog + testifylint: + enable: + - bool-compare + - compares + - empty + - error-is-as + - error-nil + - expected-actual + - float-compare + - len + - suite-extra-assert-call + - suite-thelper linters: enable-all: true @@ -242,6 +252,12 @@ issues: text: 'SA1019: config.ClientCAs.Subjects has been deprecated since Go 1.18' - path: pkg/types/tls_test.go text: 'SA1019: tlsConfig.RootCAs.Subjects has been deprecated since Go 1.18' + - path: pkg/provider/kubernetes/crd/kubernetes.go + text: 'SA1019: middleware.Spec.IPWhiteList is deprecated: please use IPAllowList instead.' + - path: pkg/server/middleware/tcp/middlewares.go + text: 'SA1019: config.IPWhiteList is deprecated: please use IPAllowList instead.' + - path: pkg/server/middleware/middlewares.go + text: 'SA1019: config.IPWhiteList is deprecated: please use IPAllowList instead.' - path: pkg/provider/kubernetes/(crd|gateway)/client.go linters: - interfacebloat diff --git a/.goreleaser.yml b/.goreleaser.yml.tmpl similarity index 93% rename from .goreleaser.yml rename to .goreleaser.yml.tmpl index 7ca926721..b8320bcbd 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml.tmpl @@ -1,8 +1,12 @@ project_name: traefik +dist: "./dist/[[ .GOOS ]]" + +[[ if eq .GOOS "linux" ]] before: hooks: - go generate +[[ end ]] builds: - binary: traefik @@ -15,11 +19,7 @@ builds: flags: - -trimpath goos: - - linux - - darwin - - windows - - freebsd - - openbsd + - "[[ .GOOS ]]" goarch: - amd64 - '386' diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index e4bd6a9d6..a8fc18b6f 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -19,36 +19,18 @@ global_job_config: prologue: commands: - curl -sSfL https://raw.githubusercontent.com/ldez/semgo/master/godownloader.sh | sudo sh -s -- -b "/usr/local/bin" - - sudo semgo go1.20 + - sudo semgo go1.21 - export "GOPATH=$(go env GOPATH)" - export "SEMAPHORE_GIT_DIR=${GOPATH}/src/github.com/traefik/${SEMAPHORE_PROJECT_NAME}" - 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.52.2 + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.55.2 - curl -sSfL https://gist.githubusercontent.com/traefiker/6d7ac019c11d011e4f131bb2cca8900e/raw/goreleaser.sh | bash -s -- -b "${GOPATH}/bin" - checkout - cache restore traefik-$(checksum go.sum) blocks: - - name: Test Integration - dependencies: [] - run: - when: "branch =~ '.*' OR pull_request =~'.*'" - task: - jobs: - - name: Test Integration - commands: - - make pull-images - - touch webui/static/index.html # Avoid generating webui - - IN_DOCKER="" make binary - - make test-integration - - df -h - epilogue: - always: - commands: - - cache store traefik-$(checksum go.sum) $HOME/go/pkg/mod - - name: Release dependencies: [] run: @@ -62,11 +44,9 @@ blocks: - name: traefik env_vars: - name: GH_VERSION - value: 1.12.1 + value: 2.32.1 - name: CODENAME value: "beaufort" - - name: IN_DOCKER - value: "" prologue: commands: - export VERSION=${SEMAPHORE_GIT_TAG_NAME} @@ -79,5 +59,5 @@ blocks: - name: Release commands: - make release-packages - - gh release create ${SEMAPHORE_GIT_TAG_NAME} ./dist/traefik*.* --repo traefik/traefik --title ${SEMAPHORE_GIT_TAG_NAME} --notes ${SEMAPHORE_GIT_TAG_NAME} + - gh release create ${SEMAPHORE_GIT_TAG_NAME} ./dist/**/traefik*.{zip,tar.gz} ./dist/traefik*.{tar.gz,txt} --repo traefik/traefik --title ${SEMAPHORE_GIT_TAG_NAME} --notes ${SEMAPHORE_GIT_TAG_NAME} - ./script/deploy.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index e0acb6bdb..d0cabd687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,123 @@ +## [v2.11.0-rc1](https://github.com/traefik/traefik/tree/v2.11.0-rc1) (2024-01-02) +[All Commits](https://github.com/traefik/traefik/compare/0a7964300166d167f68d5502bc245b3b9c8842b4...v2.11.0-rc1) + +**Enhancements:** +- **[middleware]** Deprecate IPWhiteList middleware in favor of IPAllowList ([#10249](https://github.com/traefik/traefik/pull/10249) by [lbenguigui](https://github.com/lbenguigui)) +- **[redis]** Add Redis Sentinel support ([#10245](https://github.com/traefik/traefik/pull/10245) by [youkoulayley](https://github.com/youkoulayley)) +- **[server]** Add KeepAliveMaxTime and KeepAliveMaxRequests features to entrypoints ([#10247](https://github.com/traefik/traefik/pull/10247) by [juliens](https://github.com/juliens)) +- **[sticky-session]** Hash WRR sticky cookies ([#10243](https://github.com/traefik/traefik/pull/10243) by [youkoulayley](https://github.com/youkoulayley)) + +**Bug fixes:** +- **[file]** Update github.com/fsnotify/fsnotify to v1.7.0 ([#10313](https://github.com/traefik/traefik/pull/10313) by [ldez](https://github.com/ldez)) +- **[http3]** Update quic-go to v0.40.1 ([#10296](https://github.com/traefik/traefik/pull/10296) by [ldez](https://github.com/ldez)) +- **[server]** Fix ReadHeaderTimeout for PROXY protocol ([#10320](https://github.com/traefik/traefik/pull/10320) by [juliens](https://github.com/juliens)) + +**Documentation:** +- **[acme]** Fix TLS challenge explanation ([#10293](https://github.com/traefik/traefik/pull/10293) by [cavokz](https://github.com/cavokz)) +- **[docker,acme]** Fix typo ([#10294](https://github.com/traefik/traefik/pull/10294) by [youpsla](https://github.com/youpsla)) +- **[docker]** Update wording of compose example ([#10276](https://github.com/traefik/traefik/pull/10276) by [svx](https://github.com/svx)) +- **[k8s/crd]** Adjust deprecation notice for Kubernetes CRD provider ([#10317](https://github.com/traefik/traefik/pull/10317) by [rtribotte](https://github.com/rtribotte)) +- Fix description for anonymous usage statistics references ([#10287](https://github.com/traefik/traefik/pull/10287) by [ariyonaty](https://github.com/ariyonaty)) +- Documentation enhancements ([#10261](https://github.com/traefik/traefik/pull/10261) by [svx](https://github.com/svx)) + +## [v2.10.7](https://github.com/traefik/traefik/tree/v2.10.7) (2023-12-06) +[All Commits](https://github.com/traefik/traefik/compare/v2.10.6...v2.10.7) + +**Bug fixes:** +- **[logs]** Fixed datadog logs json format issue ([#10233](https://github.com/traefik/traefik/pull/10233) by [sssash18](https://github.com/sssash18)) + +## [v3.0.0-beta5](https://github.com/traefik/traefik/tree/v3.0.0-beta5) (2023-11-29) +[All Commits](https://github.com/traefik/traefik/compare/v3.0.0-beta4...v3.0.0-beta5) + +**Enhancements:** +- **[metrics]** Upgrade OpenTelemetry dependencies ([#10181](https://github.com/traefik/traefik/pull/10181) by [mmatur](https://github.com/mmatur)) + +**Misc:** +- Merge current v2.10 into v3.0 ([#10272](https://github.com/traefik/traefik/pull/10272) by [rtribotte](https://github.com/rtribotte)) + +## [v2.10.6](https://github.com/traefik/traefik/tree/v2.10.6) (2023-11-28) +[All Commits](https://github.com/traefik/traefik/compare/v2.10.5...v2.10.6) + +**Bug fixes:** +- **[acme]** Remove backoff for http challenge ([#10224](https://github.com/traefik/traefik/pull/10224) by [youkoulayley](https://github.com/youkoulayley)) +- **[consul,consulcatalog]** Update github.com/hashicorp/consul/api ([#10220](https://github.com/traefik/traefik/pull/10220) by [kevinpollet](https://github.com/kevinpollet)) +- **[http3]** Update quic-go to v0.39.1 ([#10171](https://github.com/traefik/traefik/pull/10171) by [tomMoulard](https://github.com/tomMoulard)) +- **[middleware]** Fix stripPrefix middleware is not applied to retried attempts ([#10255](https://github.com/traefik/traefik/pull/10255) by [niki-timofe](https://github.com/niki-timofe)) +- **[provider]** Refuse recursive requests ([#10242](https://github.com/traefik/traefik/pull/10242) by [rtribotte](https://github.com/rtribotte)) +- **[server]** Deny request with fragment in URL path ([#10229](https://github.com/traefik/traefik/pull/10229) by [lbenguigui](https://github.com/lbenguigui)) +- **[tracing]** Remove deprecated code usage for datadog tracer ([#10196](https://github.com/traefik/traefik/pull/10196) by [mmatur](https://github.com/mmatur)) + +**Documentation:** +- **[governance]** Update the review process and maintainers team documentation ([#10230](https://github.com/traefik/traefik/pull/10230) by [geraldcroes](https://github.com/geraldcroes)) +- **[governance]** Guidelines Update ([#10197](https://github.com/traefik/traefik/pull/10197) by [geraldcroes](https://github.com/geraldcroes)) +- **[metrics]** Add a mention for the host header in metrics headers labels doc ([#10172](https://github.com/traefik/traefik/pull/10172) by [rtribotte](https://github.com/rtribotte)) +- **[middleware]** Rephrase BasicAuth and DigestAuth docs ([#10226](https://github.com/traefik/traefik/pull/10226) by [sssash18](https://github.com/sssash18)) +- **[middleware]** Improve ErrorPages examples ([#10209](https://github.com/traefik/traefik/pull/10209) by [arendhummeling](https://github.com/arendhummeling)) +- Add @lbenguigui to maintainers ([#10222](https://github.com/traefik/traefik/pull/10222) by [kevinpollet](https://github.com/kevinpollet)) + +## [v3.0.0-beta4](https://github.com/traefik/traefik/tree/v3.0.0-beta4) (2023-10-11) +[All Commits](https://github.com/traefik/traefik/compare/v3.0.0-beta3...v3.0.0-beta4) + +**Bug fixes:** +- **[consul,tls]** Enable TLS for Consul Connect TCP services ([#10140](https://github.com/traefik/traefik/pull/10140) by [rtribotte](https://github.com/rtribotte)) +- **[middleware]** Allow short healthcheck interval with long timeout ([#9832](https://github.com/traefik/traefik/pull/9832) by [kevinmcconnell](https://github.com/kevinmcconnell)) +- **[middleware]** Fix GrpcWeb middleware to clear ContentLength after translating to normal gRPC message ([#9782](https://github.com/traefik/traefik/pull/9782) by [CleverUnderDog](https://github.com/CleverUnderDog)) +- **[sticky-session,server]** Set sameSite field for wrr load balancer sticky cookie ([#10066](https://github.com/traefik/traefik/pull/10066) by [sunyakun](https://github.com/sunyakun)) + +**Documentation:** +- **[docker/swarm]** Fix minor typo in swarm example ([#10071](https://github.com/traefik/traefik/pull/10071) by [kaznovac](https://github.com/kaznovac)) +- **[docker/swarm]** Remove documentation of old swarm options ([#10001](https://github.com/traefik/traefik/pull/10001) by [ldez](https://github.com/ldez)) +- Fix bad anchor on documentation ([#10041](https://github.com/traefik/traefik/pull/10041) by [mmatur](https://github.com/mmatur)) +- Fix migration guide heading ([#9989](https://github.com/traefik/traefik/pull/9989) by [ldez](https://github.com/ldez)) + +**Misc:** +- Merge current v2.10 into v3.0 ([#10038](https://github.com/traefik/traefik/pull/10038) by [mmatur](https://github.com/mmatur)) + +## [v2.10.5](https://github.com/traefik/traefik/tree/v2.10.5) (2023-10-11) +[All Commits](https://github.com/traefik/traefik/compare/v2.10.4...v2.10.5) + +**Bug fixes:** +- **[accesslogs]** Move origin fields capture to service level ([#10126](https://github.com/traefik/traefik/pull/10126) by [rtribotte](https://github.com/rtribotte)) +- **[accesslogs]** Fix preflight response status in access logs ([#10142](https://github.com/traefik/traefik/pull/10142) by [rtribotte](https://github.com/rtribotte)) +- **[acme]** Update go-acme/lego to v4.14.0 ([#10087](https://github.com/traefik/traefik/pull/10087) by [ldez](https://github.com/ldez)) +- **[acme]** Update go-acme/lego to v4.13.3 ([#10077](https://github.com/traefik/traefik/pull/10077) by [ldez](https://github.com/ldez)) +- **[http3]** Update quic-go to v0.37.5 ([#10083](https://github.com/traefik/traefik/pull/10083) by [ldez](https://github.com/ldez)) +- **[http3]** Update quic-go to v0.39.0 ([#10137](https://github.com/traefik/traefik/pull/10137) by [ldez](https://github.com/ldez)) +- **[http3]** Update quic-go to v0.37.6 ([#10085](https://github.com/traefik/traefik/pull/10085) by [ldez](https://github.com/ldez)) +- **[http3]** Update quic-go to v0.38.0 ([#10086](https://github.com/traefik/traefik/pull/10086) by [ldez](https://github.com/ldez)) +- **[http3]** Update quic-go to v0.38.1 ([#10090](https://github.com/traefik/traefik/pull/10090) by [ldez](https://github.com/ldez)) +- **[kv]** Ignore ErrKeyNotFound error for the KV provider ([#10082](https://github.com/traefik/traefik/pull/10082) by [sunyakun](https://github.com/sunyakun)) +- **[middleware,authentication]** Adjust forward auth to avoid connection leak ([#10096](https://github.com/traefik/traefik/pull/10096) by [wdhongtw](https://github.com/wdhongtw)) +- **[middleware,server]** Improve CNAME flattening to avoid unnecessary error logging ([#10128](https://github.com/traefik/traefik/pull/10128) by [niallnsec](https://github.com/niallnsec)) +- **[middleware]** Allow X-Forwarded-For delete operation ([#10132](https://github.com/traefik/traefik/pull/10132) by [rtribotte](https://github.com/rtribotte)) +- **[server]** Update x/net and grpc/grpc-go ([#10161](https://github.com/traefik/traefik/pull/10161) by [rtribotte](https://github.com/rtribotte)) +- **[webui]** Add missing accessControlAllowOriginListRegex to middleware view ([#10157](https://github.com/traefik/traefik/pull/10157) by [DBendit](https://github.com/DBendit)) +- Fix false positive in url anonymization ([#10138](https://github.com/traefik/traefik/pull/10138) by [jspdown](https://github.com/jspdown)) + +**Documentation:** +- **[acme]** Change Arvancloud URL ([#10115](https://github.com/traefik/traefik/pull/10115) by [sajjadjafaribojd](https://github.com/sajjadjafaribojd)) +- **[acme]** Correct minor typo in crd-acme docs ([#10067](https://github.com/traefik/traefik/pull/10067) by [ayyron-lmao](https://github.com/ayyron-lmao)) +- **[healthcheck]** Remove healthcheck interval configuration warning ([#10068](https://github.com/traefik/traefik/pull/10068) by [rtribotte](https://github.com/rtribotte)) +- **[kv,redis]** Docs describe the missing db parameter in redis provider ([#10052](https://github.com/traefik/traefik/pull/10052) by [tokers](https://github.com/tokers)) +- **[middleware]** Doc fix accessControlAllowHeaders examples ([#10121](https://github.com/traefik/traefik/pull/10121) by [ebuildy](https://github.com/ebuildy)) +- Updates business callout in the documentation ([#10122](https://github.com/traefik/traefik/pull/10122) by [tomatokoolaid](https://github.com/tomatokoolaid)) + +## [v2.10.4](https://github.com/traefik/traefik/tree/v2.10.4) (2023-07-24) +[All Commits](https://github.com/traefik/traefik/compare/v2.10.3...v2.10.4) + +**Bug fixes:** +- **[acme]** Update go-acme/lego to v4.13.2 ([#10036](https://github.com/traefik/traefik/pull/10036) by [ldez](https://github.com/ldez)) +- **[acme]** Update go-acme/lego to v4.13.0 ([#10029](https://github.com/traefik/traefik/pull/10029) by [ldez](https://github.com/ldez)) +- **[k8s/ingress,k8s]** fix: avoid panic on resource backends ([#10023](https://github.com/traefik/traefik/pull/10023) by [ldez](https://github.com/ldez)) +- **[middleware,tracing,plugins]** fix: traceability of the middleware plugins ([#10028](https://github.com/traefik/traefik/pull/10028) by [ldez](https://github.com/ldez)) + +**Documentation:** +- Update maintainers guidelines ([#9981](https://github.com/traefik/traefik/pull/9981) by [geraldcroes](https://github.com/geraldcroes)) +- Update release documentation ([#9975](https://github.com/traefik/traefik/pull/9975) by [rtribotte](https://github.com/rtribotte)) + +**Misc:** +- **[webui]** Updates the Hub tooltip content using a web component and adds an option to disable Hub button ([#10008](https://github.com/traefik/traefik/pull/10008) by [mdeliatf](https://github.com/mdeliatf)) + ## [v3.0.0-beta3](https://github.com/traefik/traefik/tree/v3.0.0-beta3) (2023-06-21) [All Commits](https://github.com/traefik/traefik/compare/v3.0.0-beta2...v3.0.0-beta3) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 02265d5f9..e917aa340 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -47,6 +47,18 @@ Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +When an inapropriate behavior is reported, maintainers will discuss on the Maintainer's Discord before marking the message as "abuse". +This conversation beforehand avoids one-sided decisions. + +The first message will be edited and marked as abuse. +The second edited message and marked as abuse results in a 7-day ban. +The third edited message and marked as abuse results in a permanent ban. + +The content of edited messages is: +`Dear user, we want traefik to provide a welcoming and respectful environment. Your [comment/issue/PR] has been reported and marked as abuse according to our [Code of Conduct](./CODE_OF_CONDUCT.md). Thank you.` + +The [report must be resolved](https://docs.github.com/en/communities/moderating-comments-and-conversations/managing-reported-content-in-your-organizations-repository#resolving-a-report) accordingly. + ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] diff --git a/Dockerfile b/Dockerfile index 873d55312..fea809225 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,13 @@ -FROM scratch -COPY script/ca-certificates.crt /etc/ssl/certs/ -COPY dist/traefik / +# syntax=docker/dockerfile:1.2 +FROM alpine:3.19 + +RUN apk --no-cache --no-progress add ca-certificates tzdata \ + && rm -rf /var/cache/apk/* + +ARG TARGETPLATFORM +COPY ./dist/$TARGETPLATFORM/traefik / + EXPOSE 80 VOLUME ["/tmp"] + ENTRYPOINT ["/traefik"] diff --git a/LICENSE.md b/LICENSE.md index d56287566..fda9f5084 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 8de756967..15565a3ce 100644 --- a/Makefile +++ b/Makefile @@ -6,56 +6,28 @@ VERSION_GIT := $(if $(TAG_NAME),$(TAG_NAME),$(SHA)) VERSION := $(if $(VERSION),$(VERSION),$(VERSION_GIT)) GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)) -TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(subst /,-,$(GIT_BRANCH))) REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]') -TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"traefik/traefik") +BIN_NAME := traefik +CODENAME := cheddar -INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)",-v "/var/run/docker.sock:/var/run/docker.sock") -DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",) +DATE := $(shell date -u '+%Y-%m-%d_%I:%M:%S%p') -# only used when running in docker -TRAEFIK_ENVS := \ - -e OS_ARCH_ARG \ - -e OS_PLATFORM_ARG \ - -e TESTFLAGS \ - -e VERBOSE \ - -e VERSION \ - -e CODENAME \ - -e TESTDIRS \ - -e CI \ - -e IN_DOCKER=true # Indicator for integration tests that we are running inside a container. +# Default build target +GOOS := $(shell go env GOOS) +GOARCH := $(shell go env GOARCH) -TRAEFIK_MOUNT := -v "$(CURDIR)/dist:/go/src/github.com/traefik/traefik/dist" -DOCKER_RUN_OPTS := $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)" -DOCKER_NON_INTERACTIVE ?= false -DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INTERACTIVE), , -it) $(DOCKER_RUN_OPTS) -DOCKER_RUN_TRAEFIK_TEST := docker run --add-host=host.docker.internal:127.0.0.1 --rm --name=traefik --network traefik-test-network -v $(PWD):$(PWD) -w $(PWD) $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INTERACTIVE), , -it) $(DOCKER_RUN_OPTS) -DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INTERACTIVE), , -i) $(DOCKER_RUN_OPTS) +LINT_EXECUTABLES = misspell shellcheck -IN_DOCKER ?= true +DOCKER_BUILD_PLATFORMS ?= linux/amd64,linux/arm64 .PHONY: default -default: binary +default: generate binary ## Create the "dist" directory dist: mkdir -p dist -## Build Dev Docker image -.PHONY: build-dev-image -build-dev-image: dist -ifneq ("$(IN_DOCKER)", "") - docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" --build-arg HOST_PWD="$(PWD)" -f build.Dockerfile . -endif - -## Build Dev Docker image without cache -.PHONY: build-dev-image-no-cache -build-dev-image-no-cache: dist -ifneq ("$(IN_DOCKER)", "") - docker build $(DOCKER_BUILD_ARGS) --no-cache -t "$(TRAEFIK_DEV_IMAGE)" --build-arg HOST_PWD="$(PWD)" -f build.Dockerfile . -endif - ## Build WebUI Docker image .PHONY: build-webui-image build-webui-image: @@ -77,49 +49,57 @@ webui/static/index.html: .PHONY: generate-webui generate-webui: webui/static/index.html +## Generate code +.PHONY: generate +generate: + go generate + ## Build the binary .PHONY: binary -binary: generate-webui build-dev-image - $(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate binary +binary: generate-webui dist + @echo SHA: $(VERSION) $(CODENAME) $(DATE) + CGO_ENABLED=0 GOGC=off GOOS=${GOOS} GOARCH=${GOARCH} go build ${FLAGS[*]} -ldflags "-s -w \ + -X github.com/traefik/traefik/v3/pkg/version.Version=$(VERSION) \ + -X github.com/traefik/traefik/v3/pkg/version.Codename=$(CODENAME) \ + -X github.com/traefik/traefik/v3/pkg/version.BuildDate=$(DATE)" \ + -installsuffix nocgo -o "./dist/${GOOS}/${GOARCH}/$(BIN_NAME)" ./cmd/traefik -## Build the linux binary locally -.PHONY: binary-debug -binary-debug: generate-webui - GOOS=linux ./script/make.sh binary +binary-linux-arm64: export GOOS := linux +binary-linux-arm64: export GOARCH := arm64 +binary-linux-arm64: + @$(MAKE) binary + +binary-linux-amd64: export GOOS := linux +binary-linux-amd64: export GOARCH := amd64 +binary-linux-amd64: + @$(MAKE) binary + +binary-windows-amd64: export GOOS := windows +binary-windows-amd64: export GOARCH := amd64 +binary-windows-amd64: export BIN_NAME := traefik.exe +binary-windows-amd64: + @$(MAKE) binary ## Build the binary for the standard platforms (linux, darwin, windows) .PHONY: crossbinary-default -crossbinary-default: generate-webui build-dev-image - $(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-default - -## Build the binary for the standard platforms (linux, darwin, windows) in parallel -.PHONY: crossbinary-default-parallel -crossbinary-default-parallel: - $(MAKE) generate-webui - $(MAKE) build-dev-image crossbinary-default +crossbinary-default: generate generate-webui + $(CURDIR)/script/crossbinary-default.sh ## Run the unit and integration tests .PHONY: test -test: build-dev-image - -docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24 - trap 'docker network rm traefik-test-network' EXIT; \ - $(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_TEST)) ./script/make.sh generate test-unit binary test-integration +test: test-unit test-integration ## Run the unit tests .PHONY: test-unit -test-unit: build-dev-image - -docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24 - trap 'docker network rm traefik-test-network' EXIT; \ - $(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_TEST)) ./script/make.sh generate test-unit +test-unit: + GOOS=$(GOOS) GOARCH=$(GOARCH) go test -cover "-coverprofile=cover.out" -v $(TESTFLAGS) ./pkg/... ./cmd/... ## Run the integration tests .PHONY: test-integration -test-integration: build-dev-image - -docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24 - trap 'docker network rm traefik-test-network' EXIT; \ - $(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_TEST)) ./script/make.sh generate binary test-integration +test-integration: binary + GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -test.timeout=20m -failfast -v $(TESTFLAGS) -## Pull all images for integration tests +## Pull all Docker images to avoid timeout during integration tests .PHONY: pull-images pull-images: grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml \ @@ -128,37 +108,47 @@ pull-images: | uniq \ | xargs -P 6 -n 1 docker pull +## Lint run golangci-lint +.PHONY: lint +lint: + golangci-lint run + ## Validate code and docs .PHONY: validate-files -validate-files: build-dev-image - $(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell - bash $(CURDIR)/script/validate-shell-script.sh +validate-files: lint + $(foreach exec,$(LINT_EXECUTABLES),\ + $(if $(shell which $(exec)),,$(error "No $(exec) in PATH"))) + $(CURDIR)/script/validate-misspell.sh + $(CURDIR)/script/validate-shell-script.sh ## Validate code, docs, and vendor .PHONY: validate -validate: build-dev-image - $(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell validate-vendor - bash $(CURDIR)/script/validate-shell-script.sh +validate: lint + $(foreach exec,$(EXECUTABLES),\ + $(if $(shell which $(exec)),,$(error "No $(exec) in PATH"))) + $(CURDIR)/script/validate-vendor.sh + $(CURDIR)/script/validate-misspell.sh + $(CURDIR)/script/validate-shell-script.sh + +# Target for building images for multiple architectures. +.PHONY: multi-arch-image-% +multi-arch-image-%: binary-linux-amd64 binary-linux-arm64 + docker buildx build $(DOCKER_BUILDX_ARGS) -t traefik/traefik:$* --platform=$(DOCKER_BUILD_PLATFORMS) -f Dockerfile . + ## Clean up static directory and build a Docker Traefik image .PHONY: build-image -build-image: clean-webui binary - docker build -t $(TRAEFIK_IMAGE) . +build-image: export DOCKER_BUILDX_ARGS := --load +build-image: export DOCKER_BUILD_PLATFORMS := linux/$(GOARCH) +build-image: clean-webui + @$(MAKE) multi-arch-image-latest -## Build a Docker Traefik image without re-building the webui +## Build a Docker Traefik image without re-building the webui when it's already built .PHONY: build-image-dirty -build-image-dirty: binary - docker build -t $(TRAEFIK_IMAGE) . - -## Locally build traefik for linux, then shove it an alpine image, with basic tools. -.PHONY: build-image-debug -build-image-debug: binary-debug - docker build -t $(TRAEFIK_IMAGE) -f debug.Dockerfile . - -## Start a shell inside the build env -.PHONY: shell -shell: build-dev-image - $(DOCKER_RUN_TRAEFIK) /bin/bash +build-image-dirty: export DOCKER_BUILDX_ARGS := --load +build-image-dirty: export DOCKER_BUILD_PLATFORMS := linux/$(GOARCH) +build-image-dirty: + @$(MAKE) multi-arch-image-latest ## Build documentation site .PHONY: docs @@ -178,7 +168,7 @@ docs-pull-images: ## Generate CRD clientset and CRD manifests .PHONY: generate-crd generate-crd: - @$(CURDIR)/script/code-gen.sh + @$(CURDIR)/script/code-gen-docker.sh ## Generate code from dynamic configuration https://github.com/traefik/genconf .PHONY: generate-genconf @@ -187,25 +177,10 @@ generate-genconf: ## Create packages for the release .PHONY: release-packages -release-packages: generate-webui build-dev-image - rm -rf dist - $(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) goreleaser release --skip-publish -p 2 --timeout="90m" - $(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) tar cfz dist/traefik-${VERSION}.src.tar.gz \ - --exclude-vcs \ - --exclude .idea \ - --exclude .travis \ - --exclude .semaphoreci \ - --exclude .github \ - --exclude dist . - $(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) chown -R $(shell id -u):$(shell id -g) dist/ +release-packages: generate-webui + $(CURDIR)/script/release-packages.sh ## Format the Code .PHONY: fmt fmt: gofmt -s -l -w $(SRCS) - -.PHONY: run-dev -run-dev: - go generate - GO111MODULE=on go build ./cmd/traefik - ./traefik diff --git a/README.md b/README.md index cd9b6f363..26e4e5169 100644 --- a/README.md +++ b/README.md @@ -125,8 +125,8 @@ You can find high level and deep dive videos on [videos.traefik.io](https://vide ## Maintainers We are strongly promoting a philosophy of openness and sharing, and firmly standing against the elitist closed approach. Being part of the core team should be accessible to anyone who is motivated and want to be part of that journey! -This [document](docs/content/contributing/maintainers-guidelines.md) describes how to be part of the core team as well as various responsibilities and guidelines for Traefik maintainers. -You can also find more information on our process to review pull requests and manage issues [in this document](docs/content/contributing/maintainers.md). +This [document](docs/content/contributing/maintainers-guidelines.md) describes how to be part of the [maintainers' team](docs/content/contributing/maintainers.md) as well as various responsibilities and guidelines for Traefik maintainers. +You can also find more information on our process to review pull requests and manage issues [in this document](https://github.com/traefik/contributors-guide/blob/master/issue_triage.md). ## Contributing diff --git a/SECURITY.md b/SECURITY.md index 6dc1bd740..7b5c4b953 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,7 +16,7 @@ Each version is supported until the next one is released (e.g. 1.1.x will be sup We use [Semantic Versioning](https://semver.org/). | Version | Supported | -| --------- | ------------------ | +|-----------|--------------------| | `2.2.x` | :white_check_mark: | | `< 2.2.x` | :x: | | `1.7.x` | :white_check_mark: | @@ -25,4 +25,6 @@ We use [Semantic Versioning](https://semver.org/). ## Reporting a Vulnerability We want to keep Traefik safe for everyone. -If you've discovered a security vulnerability in Traefik, we appreciate your help in disclosing it to us in a responsible manner, using [this form](https://security.traefik.io). +If you've discovered a security vulnerability in Traefik, +we appreciate your help in disclosing it to us in a responsible manner, +by creating a [security advisory](https://github.com/traefik/traefik/security/advisories). diff --git a/build.Dockerfile b/build.Dockerfile deleted file mode 100644 index ce037bbfd..000000000 --- a/build.Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -FROM golang:1.20-alpine - -RUN apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \ - && update-ca-certificates \ - && rm -rf /var/cache/apk/* - -# Which docker version to test on -ARG DOCKER_VERSION=18.09.7 - -# Download docker -RUN mkdir -p /usr/local/bin \ - && curl -fL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz \ - | tar -xzC /usr/local/bin --transform 's#^.+/##x' - -# Download golangci-lint binary to bin folder in $GOPATH -RUN curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b $GOPATH/bin v1.52.2 - -# Download misspell binary to bin folder in $GOPATH -RUN curl -sfL https://raw.githubusercontent.com/golangci/misspell/master/install-misspell.sh | bash -s -- -b $GOPATH/bin v0.4.0 - -# Download goreleaser binary to bin folder in $GOPATH -RUN curl -sfL https://gist.githubusercontent.com/traefiker/6d7ac019c11d011e4f131bb2cca8900e/raw/goreleaser.sh | sh - -WORKDIR /go/src/github.com/traefik/traefik - -# Because of CVE-2022-24765 (https://github.blog/2022-04-12-git-security-vulnerability-announced/), -# we configure git to allow the Traefik codebase path on the Host for docker in docker usages. -ARG HOST_PWD="" - -RUN git config --global --add safe.directory "${HOST_PWD}" - -# Download go modules -COPY go.mod . -COPY go.sum . -RUN GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download - -COPY . /go/src/github.com/traefik/traefik diff --git a/cmd/internal/gen/centrifuge.go b/cmd/internal/gen/centrifuge.go index 824f4fa3e..df5d46fd8 100644 --- a/cmd/internal/gen/centrifuge.go +++ b/cmd/internal/gen/centrifuge.go @@ -13,6 +13,7 @@ import ( "path" "path/filepath" "reflect" + "slices" "sort" "strings" @@ -80,7 +81,7 @@ func (c Centrifuge) Run(dest string, pkgName string) error { } for _, p := range c.pkg.Imports() { - if contains(c.IncludedImports, p.Path()) { + if slices.Contains(c.IncludedImports, p.Path()) { fls := c.run(p.Scope(), p.Path(), p.Name()) err = fileWriter{baseDir: filepath.Join(dest, p.Name())}.Write(fls) @@ -97,7 +98,7 @@ func (c Centrifuge) run(sc *types.Scope, rootPkg string, pkgName string) map[str files := map[string]*File{} for _, name := range sc.Names() { - if contains(c.ExcludedTypes, name) { + if slices.Contains(c.ExcludedTypes, name) { continue } @@ -107,7 +108,7 @@ func (c Centrifuge) run(sc *types.Scope, rootPkg string, pkgName string) map[str } filename := filepath.Base(c.fileSet.File(o.Pos()).Name()) - if contains(c.ExcludedFiles, path.Join(rootPkg, filename)) { + if slices.Contains(c.ExcludedFiles, path.Join(rootPkg, filename)) { continue } @@ -237,16 +238,6 @@ func extractPackage(t types.Type) string { } } -func contains(values []string, value string) bool { - for _, val := range values { - if val == value { - return true - } - } - - return false -} - type fileWriter struct { baseDir string } diff --git a/cmd/internal/gen/main.go b/cmd/internal/gen/main.go index f5fd5bb35..a85544688 100644 --- a/cmd/internal/gen/main.go +++ b/cmd/internal/gen/main.go @@ -87,11 +87,11 @@ func run(dest string) error { } func cleanType(typ types.Type, base string) string { - if typ.String() == "github.com/traefik/traefik/v3/pkg/tls.FileOrContent" { + if typ.String() == "github.com/traefik/traefik/v3/pkg/types.FileOrContent" { return "string" } - if typ.String() == "[]github.com/traefik/traefik/v3/pkg/tls.FileOrContent" { + if typ.String() == "[]github.com/traefik/traefik/v3/pkg/types.FileOrContent" { return "[]string" } diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index f3e01f7b8..0ac89fb46 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "encoding/json" "fmt" + "io" stdlog "log" "net/http" "os" @@ -43,9 +44,9 @@ import ( "github.com/traefik/traefik/v3/pkg/tcp" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/tracing" - "github.com/traefik/traefik/v3/pkg/tracing/jaeger" "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" + "go.opentelemetry.io/otel/trace" ) func main() { @@ -265,10 +266,9 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager, acmeHTTPHandler) // Router factory - accessLog := setupAccessLog(staticConfiguration.AccessLog) - tracer := setupTracing(staticConfiguration.Tracing) + tracer, tracerCloser := setupTracing(staticConfiguration.Tracing) chainBuilder := middleware.NewChainBuilder(metricsRegistry, accessLog, tracer) routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder, pluginBuilder, metricsRegistry, dialerManager) @@ -351,7 +351,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err } }) - return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog), nil + return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog, tracerCloser), nil } func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvider http.Handler) http.Handler { @@ -564,78 +564,18 @@ func setupAccessLog(conf *types.AccessLog) *accesslog.Handler { return accessLoggerMiddleware } -func setupTracing(conf *static.Tracing) *tracing.Tracing { +func setupTracing(conf *static.Tracing) (trace.Tracer, io.Closer) { if conf == nil { - return nil + return nil, nil } - var backend tracing.Backend - - if conf.Jaeger != nil { - backend = conf.Jaeger - } - - if conf.Zipkin != nil { - if backend != nil { - log.Error().Msg("Multiple tracing backend are not supported: cannot create Zipkin backend.") - } else { - backend = conf.Zipkin - } - } - - if conf.Datadog != nil { - if backend != nil { - log.Error().Msg("Multiple tracing backend are not supported: cannot create Datadog backend.") - } else { - backend = conf.Datadog - } - } - - if conf.Instana != nil { - if backend != nil { - log.Error().Msg("Multiple tracing backend are not supported: cannot create Instana backend.") - } else { - backend = conf.Instana - } - } - - if conf.Haystack != nil { - if backend != nil { - log.Error().Msg("Multiple tracing backend are not supported: cannot create Haystack backend.") - } else { - backend = conf.Haystack - } - } - - if conf.Elastic != nil { - if backend != nil { - log.Error().Msg("Multiple tracing backend are not supported: cannot create Elastic backend.") - } else { - backend = conf.Elastic - } - } - - if conf.OpenTelemetry != nil { - if backend != nil { - log.Error().Msg("Tracing backends are all mutually exclusive: cannot create OpenTelemetry backend.") - } else { - backend = conf.OpenTelemetry - } - } - - if backend == nil { - log.Debug().Msg("Could not initialize tracing, using Jaeger by default") - defaultBackend := &jaeger.Config{} - defaultBackend.SetDefaults() - backend = defaultBackend - } - - tracer, err := tracing.NewTracing(conf.ServiceName, conf.SpanNameLimit, backend) + tracer, closer, err := tracing.NewTracing(conf) if err != nil { log.Warn().Err(err).Msg("Unable to create tracer") - return nil + return nil, nil } - return tracer + + return tracer, closer } func checkNewVersion() { diff --git a/cmd/traefik/traefik_test.go b/cmd/traefik/traefik_test.go index 6dacb7507..079b3e3a5 100644 --- a/cmd/traefik/traefik_test.go +++ b/cmd/traefik/traefik_test.go @@ -131,12 +131,6 @@ func TestGetDefaultsEntrypoints(t *testing.T) { "traefik": { Address: ":8080", }, - "traefikhub-api": { - Address: ":9900", - }, - "traefikhub-tunl": { - Address: ":9901", - }, }, expected: []string{"web"}, }, diff --git a/debug.Dockerfile b/debug.Dockerfile deleted file mode 100644 index 4dcf88bf8..000000000 --- a/debug.Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM alpine:3.14 -# Feel free to add below any helpful dependency for debugging. -# iproute2 is for ss. -RUN apk --no-cache --no-progress add bash curl ca-certificates tzdata lsof iproute2 \ - && update-ca-certificates \ - && rm -rf /var/cache/apk/* -COPY dist/traefik / -EXPOSE 80 -VOLUME ["/tmp"] -ENTRYPOINT ["/traefik"] diff --git a/docs/content/contributing/advocating.md b/docs/content/contributing/advocating.md index c7bbaacd7..ecfe393d6 100644 --- a/docs/content/contributing/advocating.md +++ b/docs/content/contributing/advocating.md @@ -1,14 +1,14 @@ --- title: "Traefik Advocation Documentation" -description: "There are many ways to contribute to Traefik Proxy. If you're talking about Traefik, let us know and we'll promote your enthusiasm!" +description: "There are many ways to contribute to Traefik Proxy. Let us know if you’re talking about Traefik, and we'll promote your enthusiasm!" --- # Advocating -Spread the Love & Tell Us about It +Spread the Love & Tell Us About It {: .subtitle } -Traefik Proxy was started by the community for the community. +Traefik Proxy was started by the community and for the community. You can contribute to the Traefik community in three main ways: **Spread the word!** Guides, videos, blog posts, how-to articles, and showing off your network design all help spread the word about Traefik Proxy diff --git a/docs/content/contributing/building-testing.md b/docs/content/contributing/building-testing.md index 493a89747..20d05740a 100644 --- a/docs/content/contributing/building-testing.md +++ b/docs/content/contributing/building-testing.md @@ -13,67 +13,13 @@ Let's see how. ## Building -You need either [Docker](https://github.com/docker/docker "Link to website of Docker") and `make` (Method 1), or [Go](https://go.dev/ "Link to website of Go") (Method 2) in order to build Traefik. -For changes to its dependencies, the `dep` dependency management tool is required. - -### Method 1: Using `Docker` and `Makefile` - -Run make with the `binary` target. - -```bash -make binary -``` - -This will create binaries for the Linux platform in the `dist` folder. - -In case when you run build on CI, you may probably want to run docker in non-interactive mode. To achieve that define `DOCKER_NON_INTERACTIVE=true` environment variable. - -```bash -$ make binary -docker build -t traefik-webui -f webui/Dockerfile webui -Sending build context to Docker daemon 2.686MB -Step 1/11 : FROM node:8.15.0 - ---> 1f6c34f7921c -[...] -Successfully built ce4ff439c06a -Successfully tagged traefik-webui:latest -[...] -docker build -t "traefik-dev:4475--feature-documentation" -f build.Dockerfile . -Sending build context to Docker daemon 279MB -Step 1/10 : FROM golang:1.16-alpine - ---> f4bfb3d22bda -[...] -Successfully built 5c3c1a911277 -Successfully tagged traefik-dev:4475--feature-documentation -docker run -e "TEST_CONTAINER=1" -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -e VERBOSE -e VERSION -e CODENAME -e TESTDIRS -e CI -e CONTAINER=DOCKER -v "/home/ldez/sources/go/src/github.com/traefik/traefik/"dist":/go/src/github.com/traefik/traefik/"dist"" "traefik-dev:4475--feature-documentation" ./script/make.sh generate binary ----> Making bundle: generate (in .) -removed 'autogen/genstatic/gen.go' - ----> Making bundle: binary (in .) - -$ ls dist/ -traefik* -``` - -The following targets can be executed outside Docker by setting the variable `IN_DOCKER` to an empty string (although be aware that some of the tests might fail in that context): - -- `test-unit` -- `test-integration` -- `validate` -- `binary` (the webUI is still generated by using Docker) - -ex: - -```bash -IN_DOCKER= make test-unit -``` - -### Method 2: Using `go` - -Requirements: - -- `go` v1.16+ -- environment variable `GO111MODULE=on` +You need: + - [Docker](https://github.com/docker/docker "Link to website of Docker") + - `make` + - [Go](https://go.dev/ "Link to website of Go") + - [misspell](https://github.com/golangci/misspell) + - [shellcheck](https://github.com/koalaman/shellcheck) + - [Tailscale](https://tailscale.com/) if you are using Docker Desktop !!! tip "Source Directory" @@ -106,43 +52,34 @@ Requirements: ## ... and the list goes on ``` -#### Build Traefik +### Build Traefik Once you've set up your go environment and cloned the source repository, you can build Traefik. ```bash -# Generate UI static files -make clean-webui generate-webui +$ make binary +SHA: 8fddfe118288bb5280eb5e77fa952f52def360b4 cheddar 2024-01-11_03:14:57PM +CGO_ENABLED=0 GOGC=off GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w \ + -X github.com/traefik/traefik/v2/pkg/version.Version=8fddfe118288bb5280eb5e77fa952f52def360b4 \ + -X github.com/traefik/traefik/v2/pkg/version.Codename=cheddar \ + -X github.com/traefik/traefik/v2/pkg/version.BuildDate=2024-01-11_03:14:57PM" \ + -installsuffix nocgo -o "./dist/darwin/arm64/traefik" ./cmd/traefik -# required to merge non-code components into the final binary, -# such as the web dashboard/UI -go generate +$ ls dist/ +traefik* ``` -```bash -# Standard go build -go build ./cmd/traefik -``` - -You will find the Traefik executable (`traefik`) in the `~/go/src/github.com/traefik/traefik` directory. +You will find the Traefik executable (`traefik`) in the `./dist` directory. ## Testing -### Method 1: `Docker` and `make` - Run unit tests using the `test-unit` target. Run integration tests using the `test-integration` target. Run all tests (unit and integration) using the `test` target. ```bash $ make test-unit -docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile . -# […] -docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/user/go/src/github/traefik/traefik/dist:/go/src/github.com/traefik/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit ----> Making bundle: generate (in .) -removed 'gen.go' - ----> Making bundle: test-unit (in .) +GOOS=darwin GOARCH=arm64 go test -cover "-coverprofile=cover.out" -v ./pkg/... ./cmd/... + go test -cover -coverprofile=cover.out . ok github.com/traefik/traefik 0.005s coverage: 4.1% of statements @@ -151,28 +88,30 @@ Test success For development purposes, you can specify which tests to run by using (only works the `test-integration` target): +??? note "Configuring Tailscale for Docker Desktop user" + + Create `tailscale.secret` file in `integration` directory. + + This file need to contains a [Tailscale auth key](https://tailscale.com/kb/1085/auth-keys) + (an ephemeral, but reusable, one is recommended). + + Add this section to your tailscale ACLs to auto-approve the routes for the + containers in the docker subnet: + + ```json + "autoApprovers": { + // Allow myself to automatically + // advertize routes for docker networks + "routes": { + "172.31.42.0/24": ["your_tailscale_identity"], + }, + }, + ``` + ```bash # Run every tests in the MyTest suite -TESTFLAGS="-check.f MyTestSuite" make test-integration +TESTFLAGS="-test.run TestAccessLogSuite" make test-integration # Run the test "MyTest" in the MyTest suite -TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration - -# Run every tests starting with "My", in the MyTest suite -TESTFLAGS="-check.f MyTestSuite.My" make test-integration - -# Run every tests ending with "Test", in the MyTest suite -TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration +TESTFLAGS="-test.run TestAccessLogSuite -testify.m ^TestAccessLog$" make test-integration ``` - -Check [gocheck](https://labix.org/gocheck "Link to website of gocheck") for more information. - -### Method 2: `go` - -Unit tests can be run from the cloned directory using `$ go test ./...` which should return `ok`, similar to: - -```test -ok _/home/user/go/src/github/traefik/traefik 0.004s -``` - -Integration tests must be run from the `integration/` directory and require the `-integration` switch: `$ cd integration && go test -integration ./...`. diff --git a/docs/content/contributing/maintainers-guidelines.md b/docs/content/contributing/maintainers-guidelines.md index 9f6fc5cd3..7c229917e 100644 --- a/docs/content/contributing/maintainers-guidelines.md +++ b/docs/content/contributing/maintainers-guidelines.md @@ -7,89 +7,75 @@ description: "Interested in contributing more to the community and becoming a Tr ![Maintainer's Guidelines](../assets/img/maintainers-guidelines.png) -Note: the document is a work in progress. - Welcome to the Traefik Community. -This document describes how to be part of the core team -together with various responsibilities -and guidelines for Traefik maintainers. + We are strongly promoting a philosophy of openness and sharing, and firmly standing against the elitist closed approach. Being part of the core team should be accessible to anyone motivated and wants to be part of that journey! -## Onboarding Process +## Becoming a Maintainer -If you consider joining our community, please drop us a line using Twitter or leave a note in the issue. -We will schedule a quick call to meet you and learn more about your motivation. -During the call, the team will discuss the process of becoming a maintainer. -We will be happy to answer any questions and explain all your doubts. +Before a contributor becomes a maintainer, they should meet the following requirements: -## Maintainer's Requirements +- The contributor enabled [2FA](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication) on their GitHub account -Note: you do not have to meet all the listed requirements, -but must have achieved several. +- The contributor showed a consistent pattern of helpful, non-threatening, and friendly behavior towards other community members in the past. + +- The contributor has read and accepted the maintainer's guidelines. + +The contributor should also meet one or several of the following requirements: -- Enabled [2FA](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication) on your GitHub account - The contributor has opened and successfully run medium to large PR’s in the past 6 months. + - The contributor has participated in multiple code reviews of other PR’s, including those of other maintainers and contributors. -- The contributor showed a consistent pattern of helpful, non-threatening, and friendly behavior towards other community members in the past. + - The contributor is active on Traefik Community forums or other technical forums/boards such as K8S slack, Reddit, StackOverflow, hacker news. -- Have read and accepted the contributor guidelines. + +Any existing active maintainer can create an issue to discuss promoting a contributor to maintainer. +Other maintainers can vote on the issue, and if the quorum is reached, the contributor is promoted to maintainer. +If the quorum is not reached within one month after the issue is created, it is closed. ## Maintainer's Responsibilities and Privileges -There are lots of areas where you can contribute to the project, -but we can suggest you start with activities such as: +As a maintainer, you are granted a vote for the following: -- PR reviewing. - - According to our guidelines we require you have at least 3 reviewers, - thus you can review a PR and leave the relevant comment if it is necessary. -- Participating in a daily [issue triage](https://github.com/traefik/contributors-guide/blob/master/issue_triage.md). - - The process helps to understand and prioritize the reported issue according to its importance and severity. - This is crucial to learn how our users implement Traefik. - Each of the issues that are labeled as bug/possible bug/confirmed requires a reproducible use case. - You can help in creating a reproducible use case if it has not been added to the issue - or use the sample code provided by the reporter. - Typically, a simple Docker Compose should be enough to reproduce the issue. -- Code contribution. -- Documentation contribution. - - Technical documentation is one of the most important components of the product. - The ability to set up a testing environment in a few minutes, - using the official documentation, - is a game changer. -- You will be listed on our Maintainers GitHub page - and on our website in the section [maintainers](maintainers.md). -- We will be promoting you on social channels (mostly on Twitter). +- [PR review](https://github.com/traefik/contributors-guide/blob/master/pr_guidelines.md). -## Governance +- [Design review](https://github.com/traefik/contributors-guide/blob/master/proposals.md). -- Roadmap meetings on a regular basis where all maintainers are welcome. +- [Proposals](https://github.com/traefik/contributors-guide/blob/master/proposals.md). + +Maintainers are also added to the maintainer's Discord server where happens the [issue triage](https://github.com/traefik/contributors-guide/blob/master/issue_triage.md) +and appear on the [Maintainers](maintainers.md) page. + +As a maintainer, you should: + +- Prioritize PR reviews, design reviews, and issue triage above any other task. + +Making sure contributors and community members are listened to and have an impact on the project is essential to keeping the project active and develop a thriving community. + +- Prioritize helping contributors reaching the expecting quality level over rewriting contributions. + +Any triage activity on issues and PRs (e.g. labels, marking messages as off-topic, refusing, marking duplicates) should result from a collective decision to ensure knowledge is shared among maintainers. ## Communicating - All of our maintainers are added to the Traefik Maintainers Discord server that belongs to Traefik labs. Having the team in one place helps us to communicate effectively. - You can reach Traefik core developers directly, - which offers the possibility to discuss issues, pull requests, enhancements more efficiently + Maintainers can discuss issues, pull requests, enhancements more efficiently and get the feedback almost immediately. Fewer blockers mean more fun and engaging work. -- On a daily basis, we publish a report that includes all the activities performed during the day. - You are updated in regard to the workload that has been processed including: - working on the new features and enhancements, - activities related to the reported issues and PR’s, - other important project-related announcements. +- Every decision made on the discord server among maintainers is documented so it's visible to the rest of the community. -- At 2:15pm CET every Monday and Thursday we review all the created issues that have been reported, - assign them the appropriate *[labels](maintainers.md#labels)* - and prioritize them based on the severity of the problem. - The process is called *[issue triaging](https://github.com/traefik/contributors-guide/blob/master/issue_triage.md)*. - Each of the maintainers is welcome to join the meeting. - For that purpose, we use the Traefik Maintainers Discord server - where you are invited once you have become an official maintainer. +- Maintainers express their opinions on issues and reviews. + It is fine to have different point of views. + We encourage active and open conversations which goals are to improve Traefik. + +- When discussing issues and proposals, maintainers should share as much information as possible to help solve the issue. ## Maintainers Activity @@ -97,38 +83,45 @@ In order to keep the core team efficient and dynamic, maintainers' activity and involvement will be reviewed on a regular basis. - Has the maintainer engaged with the team and the community by meeting two or more of these benchmarks in the past six months? + - Has the maintainer participated in at least two or three maintainer meetings? + - Substantial review of at least one or two PRs from either contributors or maintainers. + - Opened at least one or two bug fixes or feature request PRs that were eventually merged (or on a trajectory for merge). + - Substantial participation in the Help Wanted program (answered questions, helped identify issues, applied guidelines from the Help Wanted guide to open issues). + - Substantial participation with the community in general. - Has the maintainer shown a consistent pattern of helpful, non-threatening, and friendly behavior towards other people on the maintainer team and with our community? -## Additional Comments for (not only) Maintainers +## Additional Comments for Maintainers (that should apply to any contributor) + +- Be respectful with other maintainers and other community members. + +- Be open minded when participating in conversations: try to put yourself in others’ shoes. -- Be able to put yourself in users’ shoes. -- Be open-minded and respectful with other maintainers and other community members. - Keep the communication public - if anyone tries to communicate with you directly, ask politely to move the conversation to a public communication channel. + - Stay away from defensive comments. + - Please try to express your thoughts clearly enough and note that some of us are not native English speakers. Try to rephrase your sentences, avoiding mental shortcuts; - none of us is able to predict your thoughts. -- There are a lot of use cases of using Traefik - and even more issues that are difficult to reproduce. - If the issue can’t be replicated due to a lack of reproducible case (a simple Docker Compose should be enough) - - set your time limits while working on the issue - and express clearly that you were not able to replicate it. - You can come back later to that case. + none of us is able to predict anyone's thoughts. + - Be proactive. + - Emoji are fine, but if you express yourself clearly enough they are not necessary. They will not replace good communication. -- Embrace mentorship. -- Keep in mind that we all have the same intent to improve the project. + +- Embrace mentorship: help others grow and match the quality level we strive for. + +- Keep in mind that we all have the same goal: improve the project. diff --git a/docs/content/contributing/maintainers.md b/docs/content/contributing/maintainers.md index cfad7eb7a..d379677e8 100644 --- a/docs/content/contributing/maintainers.md +++ b/docs/content/contributing/maintainers.md @@ -5,18 +5,13 @@ description: "Traefik Proxy is an open source software with a thriving community # Maintainers -## The Team +## Active Maintainers * Emile Vauge [@emilevauge](https://github.com/emilevauge) -* Vincent Demeester [@vdemeester](https://github.com/vdemeester) -* Ed Robinson [@errm](https://github.com/errm) -* Daniel Tomcej [@dtomcej](https://github.com/dtomcej) * Manuel Zapf [@SantoDE](https://github.com/SantoDE) -* Timo Reimann [@timoreimann](https://github.com/timoreimann) * Ludovic Fernandez [@ldez](https://github.com/ldez) * Julien Salleyron [@juliens](https://github.com/juliens) * Nicolas Mengin [@nmengin](https://github.com/nmengin) -* Marco Jantke [@mjantke](https://github.com/mjeri) * Michaël Matur [@mmatur](https://github.com/mmatur) * Gérald Croës [@geraldcroes](https://github.com/geraldcroes) * Jean-Baptiste Doumenjou [@jbdoumenjou](https://github.com/jbdoumenjou) @@ -25,108 +20,18 @@ description: "Traefik Proxy is an open source software with a thriving community * Kevin Pollet [@kevinpollet](https://github.com/kevinpollet) * Harold Ozouf [@jspdown](https://github.com/jspdown) * Tom Moulard [@tommoulard](https://github.com/tommoulard) +* Landry Benguigui [@lbenguigui](https://github.com/lbenguigui) + +## Past Maintainers + +People who have had an incredibly positive impact on the project, and are now focusing on other projects. + +* Vincent Demeester [@vdemeester](https://github.com/vdemeester) +* Ed Robinson [@errm](https://github.com/errm) +* Daniel Tomcej [@dtomcej](https://github.com/dtomcej) +* Timo Reimann [@timoreimann](https://github.com/timoreimann) +* Marco Jantke [@mjantke](https://github.com/mjeri) ## Maintainer's Guidelines -Please read the [maintainer's guidelines](maintainers-guidelines.md) - -## Issue Triage - -Issues and PRs are triaged daily and the process for triaging may be found under [triaging issues](https://github.com/traefik/contributors-guide/blob/master/issue_triage.md) in our [contributors guide repository](https://github.com/traefik/contributors-guide). - -## PR Review Process - -The process for reviewing PRs may be found under [review guidelines](https://github.com/traefik/contributors-guide/blob/master/review_guidelines.md) in our contributors guide repository. - -## Labels - -A maintainer that looks at an issue/PR must define its `kind/*`, `area/*`, and `status/*`. - -### Status - Workflow - -The `status/*` labels represent the desired state in the workflow. - -* `status/0-needs-triage`: all the new issues and PRs have this status. _[bot only]_ -* `status/1-needs-design-review`: needs a design review. **(only for PR)** -* `status/2-needs-review`: needs a code/documentation review. **(only for PR)** -* `status/3-needs-merge`: ready to merge. **(only for PR)** -* `status/4-merge-in-progress`: merge is in progress. _[bot only]_ - -### Contributor - -* `contributor/need-more-information`: we need more information from the contributor in order to analyze a problem. -* `contributor/waiting-for-feedback`: we need the contributor to give us feedback. -* `contributor/waiting-for-corrections`: we need the contributor to take actions in order to move forward with a PR. **(only for PR)** _[bot, humans]_ -* `contributor/needs-resolve-conflicts`: use it only when there is some conflicts (and an automatic rebase is not possible). **(only for PR)** _[bot, humans]_ - -### Kind - -* `kind/enhancement`: a new or improved feature. -* `kind/question`: a question. **(only for issue)** -* `kind/proposal`: a proposal that needs to be discussed. - * _Proposal issues_ are design proposals - * _Proposal PRs_ are technical prototypes that need to be refined with multiple contributors. - -* `kind/bug/possible`: a possible bug that needs analysis before it is confirmed or fixed. **(only for issues)** -* `kind/bug/confirmed`: a confirmed bug (reproducible). **(only for issues)** -* `kind/bug/fix`: a bug fix. **(only for PR)** - -### Resolution - -* `resolution/duplicate`: a duplicate issue/PR. -* `resolution/declined`: declined (Rule #1 of open-source: no is temporary, yes is forever). -* `WIP`: Work In Progress. **(only for PR)** - -### Platform - -* `platform/windows`: Windows related. - -### Area - -* `area/acme`: ACME related. -* `area/api`: Traefik API related. -* `area/authentication`: Authentication related. -* `area/cluster`: Traefik clustering related. -* `area/documentation`: Documentation related. -* `area/infrastructure`: CI or Traefik building scripts related. -* `area/healthcheck`: Health-check related. -* `area/logs`: Logs related. -* `area/middleware`: Middleware related. -* `area/middleware/metrics`: Metrics related. (Prometheus, StatsD, ...) -* `area/middleware/tracing`: Tracing related. (Jaeger, Zipkin, ...) -* `area/oxy`: Oxy related. -* `area/provider`: related to all providers. -* `area/provider/boltdb`: Boltd DB related. -* `area/provider/consul`: Consul related. -* `area/provider/docker`: Docker and Swarm related. -* `area/provider/ecs`: ECS related. -* `area/provider/etcd`: Etcd related. -* `area/provider/eureka`: Eureka related. -* `area/provider/file`: file provider related. -* `area/provider/k8s`: Kubernetes related. -* `area/provider/kv`: KV related. -* `area/provider/marathon`: Marathon related. -* `area/provider/mesos`: Mesos related. -* `area/provider/servicefabric`: Azure service fabric related. -* `area/provider/zk`: Zoo Keeper related. -* `area/rules`: Rules related. -* `area/server`: Server related. -* `area/sticky-session`: Sticky session related. -* `area/tls`: TLS related. -* `area/websocket`: WebSocket related. -* `area/webui`: Web UI related. - -### Issues Priority - -* `priority/P0`: needs hot fix. -* `priority/P1`: need to be fixed in next release. -* `priority/P2`: need to be fixed in the future. -* `priority/P3`: maybe. - -### PR Size - -Automatically set by a bot. - -* `size/S`: small PR. -* `size/M`: medium PR. -* `size/L`: Large PR. +Please read the [maintainer's guidelines](maintainers-guidelines.md). diff --git a/docs/content/contributing/submitting-issues.md b/docs/content/contributing/submitting-issues.md index 89826aac9..abb0d3421 100644 --- a/docs/content/contributing/submitting-issues.md +++ b/docs/content/contributing/submitting-issues.md @@ -11,8 +11,8 @@ Help Us Help You! Issues are perfect for requesting a feature/enhancement or reporting a suspected bug. We use the [GitHub issue tracker](https://github.com/traefik/traefik/issues) to keep track of issues in Traefik. -The process of sorting and checking the issues is a daunting task, and requires a lot of work (more than an hour a day ... just for sorting). -To help us (and other community members) quickly and effortlessly understand what you need, +The process of sorting and checking the issues is a daunting task, and requires a lot of work. +To help maintainers (and other community members) quickly and effortlessly understand what you need, be sure to follow the guidelines below. !!! important "Getting Help Vs Reporting an Issue" diff --git a/docs/content/contributing/submitting-pull-requests.md b/docs/content/contributing/submitting-pull-requests.md index 2e6a666fb..8dfbaeb5c 100644 --- a/docs/content/contributing/submitting-pull-requests.md +++ b/docs/content/contributing/submitting-pull-requests.md @@ -17,12 +17,9 @@ or the list of [confirmed bugs](https://github.com/traefik/traefik/labels/kind%2 ## How We Prioritize -We wish we could review every pull request right away. -Unfortunately, our team has to prioritize pull requests (PRs) for review -(but we are welcoming new [maintainers](https://github.com/traefik/traefik/blob/master/docs/content/contributing/maintainers-guidelines.md) to speed this up, -if you are interested, check it out and apply). +We wish we could review every pull request right away, but because it's a time consuming operation, it's not always possible. -The PRs we are able to handle fastest are: +The PRs we are able to handle the fastest are: * Documentation updates. * Bug fixes. @@ -59,7 +56,7 @@ Merging a PR requires the following steps to be completed before it is merged au * Ensure your PR is not a draft. We do not review drafts, but do answer questions and confer with developers on them as needed. * Pass the validation check. * Pass all tests. -* Receive 3 approving reviews maintainers. +* Receive 3 approving reviews from maintainers. ## Pull Request Review Cycle @@ -198,7 +195,7 @@ here are some things you can do to move the process along: * If you have fixed all the issues from a review, remember to re-request a review (using the designated button) to let your reviewer know that you are ready. You can choose to comment with the changes you made. -* Ping `@tfny` if you have not been assigned to a reviewer. +* Kindly comment on the pull request. Doing so will automatically give your PR visibility during the triage process. For more information on best practices, try these links: diff --git a/docs/content/contributing/submitting-security-issues.md b/docs/content/contributing/submitting-security-issues.md index 5a59408a2..08fbb79a2 100644 --- a/docs/content/contributing/submitting-security-issues.md +++ b/docs/content/contributing/submitting-security-issues.md @@ -18,4 +18,6 @@ Reported vulnerabilities can be found on ## Report a Vulnerability We want to keep Traefik safe for everyone. -If you've discovered a security vulnerability in Traefik, we appreciate your help in disclosing it to us in a responsible manner, using [this form](https://security.traefik.io). +If you've discovered a security vulnerability in Traefik, +we appreciate your help in disclosing it to us in a responsible manner, +by creating a [security advisory](https://github.com/traefik/traefik/security/advisories). diff --git a/docs/content/contributing/thank-you.md b/docs/content/contributing/thank-you.md index 8766d98b6..950ee50ae 100644 --- a/docs/content/contributing/thank-you.md +++ b/docs/content/contributing/thank-you.md @@ -9,7 +9,7 @@ _You_ Made It {: .subtitle} Traefik Proxy truly is an [open-source project](https://github.com/traefik/traefik/), -and wouldn't have become what it is today without the help of our [many contributors](https://github.com/traefik/traefik/graphs/contributors) (at the time of writing this), +and wouldn't have become what it is today without the help of our [many contributors](https://github.com/traefik/traefik/graphs/contributors), not accounting for people having helped with issues, tests, comments, articles, ... or just enjoy using Traefik Proxy and share with others. So once again, thank you for your invaluable help in making Traefik such a good product! @@ -17,16 +17,16 @@ So once again, thank you for your invaluable help in making Traefik such a good !!! question "Where to Go Next?" If you want to: - - Propose and idea, request a feature a report a bug, - read the page [Submitting Issues](./submitting-issues.md). + - Propose an idea, request a feature, or report a bug, + then read [Submitting Issues](./submitting-issues.md). - Discover how to make an efficient contribution, - read the page [Submitting Pull Requests](./submitting-pull-requests.md). + then read [Submitting Pull Requests](./submitting-pull-requests.md). - Learn how to build and test Traefik, - the page [Building and Testing](./building-testing.md) is for you. + then the page [Building and Testing](./building-testing.md) is for you. - Contribute to the documentation, - read the related page [Documentation](./documentation.md). + then read the page about [Documentation](./documentation.md). - Understand how do we learn about Traefik usage, read the [Data Collection](./data-collection.md) page. - Spread the love about Traefik, please check the [Advocating](./advocating.md) page. - Learn about who are the maintainers and how they work on the project, - read the [Maintainers](./maintainers.md) page. + read the [Maintainers](./maintainers.md) and [Maintainer Guidelines](./maintainers-guidelines.md) pages. diff --git a/docs/content/deprecation/features.md b/docs/content/deprecation/features.md index c5f0f2dfe..1cf2355df 100644 --- a/docs/content/deprecation/features.md +++ b/docs/content/deprecation/features.md @@ -4,20 +4,23 @@ This page is maintained and updated periodically to reflect our roadmap and any | Feature | Deprecated | End of Support | Removal | |----------------------------------------------------------------------------------------------------------------------|------------|----------------|---------| -| [Kubernetes CRDs API Version `traefik.io/v1alpha1`](#kubernetes-crds-api-version-traefikiov1alpha1) | N/A | N/A | 3.0 | +| [Kubernetes CRD Provider API Version `traefik.io/v1alpha1`](#kubernetes-crd-provider-api-version-traefikiov1alpha1) | 3.0 | N/A | 4.0 | | [Kubernetes Ingress API Version `networking.k8s.io/v1beta1`](#kubernetes-ingress-api-version-networkingk8siov1beta1) | N/A | N/A | 3.0 | | [CRD API Version `apiextensions.k8s.io/v1beta1`](#kubernetes-ingress-api-version-networkingk8siov1beta1) | N/A | N/A | 3.0 | ## Impact -### Kubernetes CRDs API Version `traefik.io/v1alpha1` +### Kubernetes CRD Provider API Version `traefik.io/v1alpha1` -The newly introduced Kubernetes CRD API Version `traefik.io/v1alpha1` will subsequently be removed in Traefik v3. The following version will be `traefik.io/v1`. +The Kubernetes CRD provider API Version `traefik.io/v1alpha1` is deprecated in Traefik v3. +Please use the API Group `traefik.io/v1` instead. ### Kubernetes Ingress API Version `networking.k8s.io/v1beta1` -The Kubernetes Ingress API Version `networking.k8s.io/v1beta1` is removed in v3. Please use the API Group `networking.k8s.io/v1` instead. +The Kubernetes Ingress API Version `networking.k8s.io/v1beta1` support is removed in v3. +Please use the API Group `networking.k8s.io/v1` instead. -### Traefik CRD API Version `apiextensions.k8s.io/v1beta1` +### Traefik CRD Definitions API Version `apiextensions.k8s.io/v1beta1` -The Traefik CRD API Version `apiextensions.k8s.io/v1beta1` is removed in v3. Please use the API Group `apiextensions.k8s.io/v1` instead. +The Traefik CRD definitions API Version `apiextensions.k8s.io/v1beta1` support is removed in v3. +Please use the API Group `apiextensions.k8s.io/v1` instead. diff --git a/docs/content/getting-started/concepts.md b/docs/content/getting-started/concepts.md index 10e1787e4..21e1817f7 100644 --- a/docs/content/getting-started/concepts.md +++ b/docs/content/getting-started/concepts.md @@ -25,7 +25,7 @@ The main features include dynamic configuration, automatic service discovery, an ## Edge Router -Traefik is an *Edge Router*, it means that it's the door to your platform, and that it intercepts and routes every incoming request: +Traefik is an *Edge Router*; this means that it's the door to your platform, and that it intercepts and routes every incoming request: it knows all the logic and every [rule](../routing/routers/index.md#rule "Link to docs about routing rules") that determine which services handle which requests (based on the *path*, the *host*, *headers*, etc.). ![The Door to Your Infrastructure](../assets/img/traefik-concepts-1.png "Picture explaining the infrastructure") @@ -38,7 +38,7 @@ Deploying your services, you attach information that tells Traefik the character ![Decentralized Configuration](../assets/img/traefik-concepts-2.png "Picture about Decentralized Configuration") -It means that when a service is deployed, Traefik detects it immediately and updates the routing rules in real time. +This means that when a service is deployed, Traefik detects it immediately and updates the routing rules in real time. Similarly, when a service is removed from the infrastructure, the corresponding route is deleted accordingly. You no longer need to create and synchronize configuration files cluttered with IP addresses or other rules. diff --git a/docs/content/getting-started/configuration-overview.md b/docs/content/getting-started/configuration-overview.md index 261103b87..c194562e1 100644 --- a/docs/content/getting-started/configuration-overview.md +++ b/docs/content/getting-started/configuration-overview.md @@ -82,11 +82,11 @@ docker run traefik[:version] --help # ex: docker run traefik:v3.0 --help ``` -All available arguments can also be found [here](../reference/static-configuration/cli.md). +Check the [CLI reference](../reference/static-configuration/cli.md "Link to CLI reference overview") for an overview about all available arguments. ### Environment Variables -All available environment variables can be found [here](../reference/static-configuration/env.md) +All available environment variables can be found in the [static configuration environment overview](../reference/static-configuration/env.md). ## Available Configuration Options diff --git a/docs/content/getting-started/faq.md b/docs/content/getting-started/faq.md index caa82058f..761e8d3a7 100644 --- a/docs/content/getting-started/faq.md +++ b/docs/content/getting-started/faq.md @@ -29,7 +29,7 @@ Not to mention that dynamic configuration changes potentially make that kind of Therefore, in this dynamic context, the static configuration of an `entryPoint` does not give any hint whatsoever about how the traffic going through that `entryPoint` is going to be routed. Or whether it's even going to be routed at all, -i.e. whether there is a Router matching the kind of traffic going through it. +that is whether there is a Router matching the kind of traffic going through it. ### `404 Not found` @@ -71,7 +71,7 @@ Traefik returns a `502` response code when an error happens while contacting the ### `503 Service Unavailable` -Traefik returns a `503` response code when a Router has been matched +Traefik returns a `503` response code when a Router has been matched, but there are no servers ready to handle the request. This situation is encountered when a service has been explicitly configured without servers, @@ -84,7 +84,7 @@ Sometimes, the `404` response code doesn't play well with other parties or servi In these situations, you may want Traefik to always reply with a `503` response code, instead of a `404` response code. -To achieve this behavior, a simple catchall router, +To achieve this behavior, a catchall router, with the lowest possible priority and routing to a service without servers, can handle all the requests when no other router has been matched. @@ -130,7 +130,7 @@ http: the principle of the above example above (a catchall router) still stands, but the `unavailable` service should be adapted to fit such a need. -## Why Is My TLS Certificate Not Reloaded When Its Contents Change? +## Why Is My TLS Certificate Not Reloaded When Its Contents Change? With the file provider, a configuration update is only triggered when one of the [watched](../providers/file.md#provider-configuration) configuration files is modified. @@ -216,7 +216,7 @@ error: field not found, node: -badField- The "field not found" error occurs, when an unknown property is encountered in the dynamic or static configuration. -One easy way to check whether a configuration file is well-formed, is to validate it with: +One way to check whether a configuration file is well-formed, is to validate it with: - [JSON Schema of the static configuration](https://json.schemastore.org/traefik-v2.json) - [JSON Schema of the dynamic configuration](https://json.schemastore.org/traefik-v2-file-provider.json) @@ -226,11 +226,11 @@ One easy way to check whether a configuration file is well-formed, is to validat As a common tip, if a resource is dropped/not created by Traefik after the dynamic configuration was evaluated, one should look for an error in the logs. -If found, the error obviously confirms that something went wrong while creating the resource, +If found, the error confirms that something went wrong while creating the resource, and the message should help in figuring out the mistake(s) in the configuration, and how to fix it. When using the file provider, -one easy way to check if the dynamic configuration is well-formed is to validate it with the [JSON Schema of the dynamic configuration](https://json.schemastore.org/traefik-v2-file-provider.json). +one way to check if the dynamic configuration is well-formed is to validate it with the [JSON Schema of the dynamic configuration](https://json.schemastore.org/traefik-v2-file-provider.json). ## Why does Let's Encrypt wildcard certificate renewal/generation with DNS challenge fail? @@ -248,6 +248,6 @@ then it could be due to `CNAME` support. In which case, you should make sure your infrastructure is properly set up for a `DNS` challenge that does not rely on `CNAME`, and you should try disabling `CNAME` support with: -```bash +```shell LEGO_DISABLE_CNAME_SUPPORT=true ``` diff --git a/docs/content/getting-started/install-traefik.md b/docs/content/getting-started/install-traefik.md index a0a5df6ea..630202288 100644 --- a/docs/content/getting-started/install-traefik.md +++ b/docs/content/getting-started/install-traefik.md @@ -19,7 +19,7 @@ Choose one of the [official Docker images](https://hub.docker.com/_/traefik) and * [YAML](https://raw.githubusercontent.com/traefik/traefik/v3.0/traefik.sample.yml) * [TOML](https://raw.githubusercontent.com/traefik/traefik/v3.0/traefik.sample.toml) -```bash +```shell docker run -d -p 8080:8080 -p 80:80 \ -v $PWD/traefik.yml:/etc/traefik/traefik.yml traefik:v3.0 ``` @@ -59,7 +59,7 @@ You can update the chart repository by running: helm repo update ``` -And install it with the `helm` command line: +And install it with the Helm command line: ```bash helm install traefik traefik/traefik @@ -69,7 +69,7 @@ helm install traefik traefik/traefik All [Helm features](https://helm.sh/docs/intro/using_helm/) are supported. - Examples are provided [here](https://github.com/traefik/traefik-helm-chart/blob/master/EXAMPLES.md). + Examples are provided [here](https://github.com/traefik/traefik-helm-chart/blob/master/EXAMPLES.md). For instance, installing the chart in a dedicated namespace: @@ -106,7 +106,7 @@ helm install traefik traefik/traefik ### Exposing the Traefik dashboard -This HelmChart does not expose the Traefik dashboard by default, for security concerns. +This Helm chart does not expose the Traefik dashboard by default, for security concerns. Thus, there are multiple ways to expose the dashboard. For instance, the dashboard access could be achieved through a port-forward: diff --git a/docs/content/getting-started/quick-start-with-kubernetes.md b/docs/content/getting-started/quick-start-with-kubernetes.md index e77cb71fe..30530522d 100644 --- a/docs/content/getting-started/quick-start-with-kubernetes.md +++ b/docs/content/getting-started/quick-start-with-kubernetes.md @@ -1,23 +1,23 @@ --- title: "Traefik Getting Started With Kubernetes" -description: "Looking to get started with Traefik Proxy? Read the technical documentation to learn a simple use case that leverages Kubernetes." +description: "Get started with Traefik Proxy and Kubernetes." --- # Quick Start -A Simple Use Case of Traefik Proxy and Kubernetes +A Use Case of Traefik Proxy and Kubernetes {: .subtitle } -This guide is an introduction to using Traefik Proxy in a Kubernetes environment. -The objective is to learn how to run an application behind a Traefik reverse proxy in Kubernetes. +This guide is an introduction to using Traefik Proxy in a Kubernetes environment. +The objective is to learn how to run an application behind a Traefik reverse proxy in Kubernetes. It presents and explains the basic blocks required to start with Traefik such as Ingress Controller, Ingresses, Deployments, static, and dynamic configuration. ## Permissions and Accesses Traefik uses the Kubernetes API to discover running services. -In order to use the Kubernetes API, Traefik needs some permissions. -This [permission mechanism](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) is based on roles defined by the cluster administrator. +To use the Kubernetes API, Traefik needs some permissions. +This [permission mechanism](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) is based on roles defined by the cluster administrator. The role is then bound to an account used by an application, in this case, Traefik Proxy. The first step is to create the role. @@ -88,7 +88,7 @@ roleRef: subjects: - kind: ServiceAccount name: traefik-account - namespace: default # Using "default" because we did not specify a namespace when creating the ClusterAccount. + namespace: default # This tutorial uses the "default" K8s namespace. ``` !!! info "`roleRef` is the Kubernetes reference to the role created in `00-role.yml`." @@ -102,7 +102,7 @@ subjects: !!! info "This section can be managed with the help of the [Traefik Helm chart](../install-traefik/#use-the-helm-chart)." The [ingress controller](https://traefik.io/glossary/kubernetes-ingress-and-ingress-controller-101/#what-is-a-kubernetes-ingress-controller) -is a software that runs in the same way as any other application on a cluster. +is a software that runs in the same way as any other application on a cluster. To start Traefik on the Kubernetes cluster, a [`Deployment`](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/deployment-v1/) resource must exist to describe how to configure and scale containers horizontally to support larger workloads. @@ -141,12 +141,12 @@ spec: containerPort: 8080 ``` -The deployment contains an important attribute for customizing Traefik: `args`. -These arguments are the static configuration for Traefik. +The deployment contains an important attribute for customizing Traefik: `args`. +These arguments are the static configuration for Traefik. From here, it is possible to enable the dashboard, configure entry points, select dynamic configuration providers, -and [more](../reference/static-configuration/cli.md)... +and [more](../reference/static-configuration/cli.md). In this deployment, the static configuration enables the Traefik dashboard, @@ -159,10 +159,10 @@ and uses Kubernetes native Ingress resources as router definitions to route inco !!! info "When enabling the [`api.insecure`](../../operations/api/#insecure) mode, Traefik exposes the dashboard on the port `8080`." A deployment manages scaling and then can create lots of containers, called [Pods](https://kubernetes.io/docs/concepts/workloads/pods/). -Each Pod is configured following the `spec` field in the deployment. +Each Pod is configured following the `spec` field in the deployment. Given that, a Deployment can run multiple Traefik Proxy Pods, a piece is required to forward the traffic to any of the instance: -namely a [`Service`](https://kubernetes.io/docs/reference/kubernetes-api/service-resources/service-v1/#Service). +namely a [`Service`](https://kubernetes.io/docs/reference/kubernetes-api/service-resources/service-v1/#Service). Create a file called `02-traefik-services.yml` and insert the two `Service` resources: ```yaml tab="02-traefik-services.yml" @@ -195,7 +195,7 @@ spec: !!! warning "It is possible to expose a service in different ways." - Depending on your working environment and use case, the `spec.type` might change. + Depending on your working environment and use case, the `spec.type` might change. It is strongly recommended to understand the available [service types](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) before proceeding to the next step. It is now time to apply those files on your cluster to start Traefik. @@ -210,11 +210,11 @@ kubectl apply -f 00-role.yml \ ## Proxying applications -The only part still missing is the business application behind the reverse proxy. +The only part still missing is the business application behind the reverse proxy. For this guide, we use the example application [traefik/whoami](https://github.com/traefik/whoami), but the principles are applicable to any other application. -The `whoami` application is a simple HTTP server running on port 80 which answers host-related information to the incoming requests. +The `whoami` application is an HTTP server running on port 80 which answers host-related information to the incoming requests. As usual, start by creating a file called `03-whoami.yml` and paste the following `Deployment` resource: ```yaml tab="03-whoami.yml" @@ -262,8 +262,8 @@ spec: ``` Thanks to the Kubernetes API, -Traefik is notified when an Ingress resource is created, updated, or deleted. -This makes the process dynamic. +Traefik is notified when an Ingress resource is created, updated, or deleted. +This makes the process dynamic. The ingresses are, in a way, the [dynamic configuration](../../providers/kubernetes-ingress/) for Traefik. !!! tip @@ -317,4 +317,4 @@ curl -v http://localhost/ - Use [IngressRoute CRD](../providers/kubernetes-crd.md) - Protect [ingresses with TLS](../routing/providers/kubernetes-ingress.md#enabling-tls-via-annotations) -{!traefik-api-management-kubernetes.md!} +{!traefik-for-business-applications.md!} diff --git a/docs/content/getting-started/quick-start.md b/docs/content/getting-started/quick-start.md index e4007910d..a33eb4b22 100644 --- a/docs/content/getting-started/quick-start.md +++ b/docs/content/getting-started/quick-start.md @@ -1,11 +1,11 @@ --- title: "Traefik Getting Started Quickly" -description: "Looking to get started with Traefik Proxy quickly? Read the technical documentation to see a basic use case that leverages Docker." +description: "Get started with Traefik Proxy and Docker." --- # Quick Start -A Basic Use Case Using Docker +A Use Case Using Docker {: .subtitle } ![quickstart-diagram](../assets/img/quickstart-diagram.png) @@ -19,9 +19,9 @@ version: '3' services: reverse-proxy: - # The official v3 Traefik Docker image + # The official v2 Traefik docker image image: traefik:v3.0 - # Enables the web UI and tells Traefik to listen to Docker + # Enables the web UI and tells Traefik to listen to docker command: --api.insecure=true --providers.docker ports: # The HTTP port @@ -41,11 +41,11 @@ Start your `reverse-proxy` with the following command: docker-compose up -d reverse-proxy ``` -You can open a browser and go to `http://localhost:8080/api/rawdata` to see Traefik's API rawdata (we'll go back there once we have launched a service in step 2). +You can open a browser and go to `http://localhost:8080/api/rawdata` to see Traefik's API rawdata (you'll go back there once you have launched a service in step 2). ## Traefik Detects New Services and Creates the Route for You -Now that we have a Traefik instance up and running, we will deploy new services. +Now that you have a Traefik instance up and running, you will deploy new services. Edit your `docker-compose.yml` file and add the following at the end of your file. @@ -63,7 +63,7 @@ services: - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" ``` -The above defines [`whoami`](https://github.com/traefik/whoami "Link to whoami app on GitHub"), a web service that outputs information about the machine it is deployed on (its IP address, host, etc.). +The above defines `whoami`: a web service that outputs information about the machine it is deployed on (its IP address, host, and others). Start the `whoami` service with the following command: @@ -73,7 +73,7 @@ docker-compose up -d whoami Browse `http://localhost:8080/api/rawdata` and see that Traefik has automatically detected the new container and updated its own configuration. -When Traefik detects new services, it creates the corresponding routes, so you can call them ... _let's see!_ (Here, we're using curl) +When Traefik detects new services, it creates the corresponding routes, so you can call them ... _let's see!_ (Here, you're using curl) ```shell curl -H Host:whoami.docker.localhost http://127.0.0.1 @@ -103,7 +103,7 @@ Finally, see that Traefik load-balances between the two instances of your servic curl -H Host:whoami.docker.localhost http://127.0.0.1 ``` -The output will show alternatively one of the followings: +The output will show alternatively one of the following: ```yaml Hostname: a656c8ddca6c diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index 31e926c3f..b614beedd 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -313,7 +313,7 @@ For complete details, refer to your provider's _Additional configuration_ link. | [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) | | [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) | | [all-inkl](https://all-inkl.com) | `allinkl` | `ALL_INKL_LOGIN`, `ALL_INKL_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/allinkl) | -| [ArvanCloud](https://www.arvancloud.com/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) | +| [ArvanCloud](https://www.arvancloud.ir/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) | | [Auroradns](https://www.pcextreme.com/dns-health-checks) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/auroradns) | | [Autodns](https://www.internetx.com/domains/autodns/) | `autodns` | `AUTODNS_API_USER`, `AUTODNS_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/autodns) | | [Azure](https://azure.microsoft.com/services/dns/) (DEPRECATED) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | [Additional configuration](https://go-acme.github.io/lego/dns/azure) | @@ -324,6 +324,7 @@ For complete details, refer to your provider's _Additional configuration_ link. | [Bunny](https://bunny.net) | `bunny` | `BUNNY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/bunny) | | [Checkdomain](https://www.checkdomain.de/) | `checkdomain` | `CHECKDOMAIN_TOKEN`, | [Additional configuration](https://go-acme.github.io/lego/dns/checkdomain/) | | [Civo](https://www.civo.com/) | `civo` | `CIVO_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/civo) | +| [Cloud.ru](https://cloud.ru) | `cloudru` | `CLOUDRU_SERVICE_INSTANCE_ID`, `CLOUDRU_KEY_ID`, `CLOUDRU_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudru) | | [CloudDNS](https://vshosting.eu/) | `clouddns` | `CLOUDDNS_CLIENT_ID`, `CLOUDDNS_EMAIL`, `CLOUDDNS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/clouddns) | | [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` [^5] or `CF_DNS_API_TOKEN`, `[CF_ZONE_API_TOKEN]` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudflare) | | [ClouDNS](https://www.cloudns.net/) | `cloudns` | `CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/cloudns) | @@ -350,7 +351,7 @@ For complete details, refer to your provider's _Additional configuration_ link. | [Exoscale](https://www.exoscale.com) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/exoscale) | | [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) | | [Freemyip.com](https://freemyip.com) | `freemyip` | `FREEMYIP_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/freemyip) | -| [G-Core Lab](https://gcorelabs.com/dns/) | `gcore` | `GCORE_PERMANENT_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/gcore) | +| [G-Core](https://gcore.com/dns/) | `gcore` | `GCORE_PERMANENT_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/gcore) | | [Gandi v5](https://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandiv5) | | [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandi) | | [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | [Additional configuration](https://go-acme.github.io/lego/dns/glesys) | @@ -427,6 +428,7 @@ For complete details, refer to your provider's _Additional configuration_ link. | [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/vultr) | | [Websupport](https://websupport.sk) | `websupport` | `WEBSUPPORT_API_KEY`, `WEBSUPPORT_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/websupport) | | [WEDOS](https://www.wedos.com) | `wedos` | `WEDOS_USERNAME`, `WEDOS_WAPI_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/wedos) | +| [Yandex 360](https://360.yandex.ru) | `yandex360` | `YANDEX360_OAUTH_TOKEN`, `YANDEX360_ORG_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/yandex360) | | [Yandex Cloud](https://cloud.yandex.com/en/) | `yandexcloud` | `YANDEX_CLOUD_FOLDER_ID`, `YANDEX_CLOUD_IAM_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/yandexcloud) | | [Yandex](https://yandex.com) | `yandex` | `YANDEX_PDD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/yandex) | | [Zone.ee](https://www.zone.ee) | `zoneee` | `ZONEEE_API_USER`, `ZONEEE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/zoneee) | diff --git a/docs/content/includes/traefik-api-management-kubernetes.md b/docs/content/includes/traefik-api-management-kubernetes.md deleted file mode 100644 index f89712319..000000000 --- a/docs/content/includes/traefik-api-management-kubernetes.md +++ /dev/null @@ -1,11 +0,0 @@ ---- - -!!! question "Managing APIs in Kubernetes?" - - If your organization is publishing, securing, and managing APIs, consider [Traefik Hub](https://traefik.io/traefik-hub/) for your API management solution. - - - K8s services auto-discovery, 100% CRDs configuration, & full GitOps compliance - - Centralized control plane for all APIs, users, & infrastructure components - - Self-serve API portal with API discovery, documentation, testing, & access control - - Traefik Hub makes managing APIs easier than ever before. See for yourself in this [short video walkthrough](https://info.traefik.io/watch-traefik-hub-demo). diff --git a/docs/content/includes/traefik-for-business-applications.md b/docs/content/includes/traefik-for-business-applications.md index c07d1a41d..236efaf52 100644 --- a/docs/content/includes/traefik-for-business-applications.md +++ b/docs/content/includes/traefik-for-business-applications.md @@ -2,10 +2,13 @@ !!! question "Using Traefik for Business Applications?" - If you are using Traefik in your organization, consider [Traefik Enterprise](https://traefik.io/traefik-enterprise/). You can use it as your: + If you are using Traefik in your organization, consider our enterprise-grade solutions: - - [API Gateway](https://traefik.io/solutions/api-gateway/) - - [Kubernetes Ingress Controller](https://traefik.io/solutions/kubernetes-ingress/) - - [Docker Swarm Ingress Controller](https://traefik.io/solutions/docker-swarm-ingress/) + - API Management + [Explore](https://traefik.io/solutions/api-management/) // [Watch Demo Video](https://info.traefik.io/watch-traefik-hub-demo) + - API Gateway + [Explore](https://traefik.io/solutions/api-gateway/) // [Watch Demo Video](https://info.traefik.io/watch-traefikee-demo) + - Ingress Controller + [Kubernetes](https://traefik.io/solutions/kubernetes-ingress/) // [Docker Swarm](https://traefik.io/solutions/docker-swarm-ingress/) - Traefik Enterprise simplifies the discovery, security, and deployment of APIs and microservices across any environment. See it in action in [this short video walkthrough](https://info.traefik.io/watch-traefikee-demo). + These tools help businesses discover, deploy, secure, and manage microservices and APIs easily, at scale, across any environment. diff --git a/docs/content/index.md b/docs/content/index.md index afcc18db8..072faae0b 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -18,7 +18,7 @@ Traefik is natively compliant with every major cluster technology, such as Kuber With Traefik, there is no need to maintain and synchronize a separate configuration file: everything happens automatically, in real time (no restarts, no connection interruptions). With Traefik, you spend time developing and deploying new features to your system, not on configuring and maintaining its working state. -Developing Traefik, our main goal is to make it simple to use, and we're sure you'll enjoy it. +Developing Traefik, our main goal is to make it effortless to use, and we're sure you'll enjoy it. -- The Traefik Maintainer Team diff --git a/docs/content/middlewares/http/basicauth.md b/docs/content/middlewares/http/basicauth.md index 5c77aad19..de5655bf3 100644 --- a/docs/content/middlewares/http/basicauth.md +++ b/docs/content/middlewares/http/basicauth.md @@ -10,7 +10,7 @@ Adding Basic Authentication ![BasicAuth](../../assets/img/middleware/basicauth.png) -The BasicAuth middleware restricts access to your services to known users. +The BasicAuth middleware grants access to services to authorized users only. ## Configuration Examples diff --git a/docs/content/middlewares/http/compress.md b/docs/content/middlewares/http/compress.md index eec8e73b0..e98954660 100644 --- a/docs/content/middlewares/http/compress.md +++ b/docs/content/middlewares/http/compress.md @@ -55,10 +55,10 @@ 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, it is meant as br compression is requested. + If the `Accept-Encoding` request header is absent, 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). + * The response`Content-Type` header is not one among the [excludedContentTypes options](#excludedcontenttypes), or is one among the [includedContentTypes options](#includedcontenttypes). * The response body is larger than the [configured minimum amount of bytes](#minresponsebodybytes) (default is `1024`). ## Configuration Options @@ -73,6 +73,10 @@ The responses with content types defined in `excludedContentTypes` are not compr Content types are compared in a case-insensitive, whitespace-ignored manner. +!!! info + + The `excludedContentTypes` and `includedContentTypes` options are mutually exclusive. + !!! info "In the case of gzip" If the `Content-Type` header is not defined, or empty, the compress middleware will automatically [detect](https://mimesniff.spec.whatwg.org/) a content type. @@ -117,6 +121,59 @@ http: excludedContentTypes = ["text/event-stream"] ``` +### `includedContentTypes` + +_Optional, Default=""_ + +`includedContentTypes` specifies a list of content types to compare the `Content-Type` header of the responses before compressing. + +The responses with content types defined in `includedContentTypes` are compressed. + +Content types are compared in a case-insensitive, whitespace-ignored manner. + +!!! info + + The `excludedContentTypes` and `includedContentTypes` options are mutually exclusive. + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.middlewares.test-compress.compress.includedcontenttypes=application/json,text/html,text/plain" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-compress +spec: + compress: + includedContentTypes: + - application/json + - text/html + - text/plain +``` + +```yaml tab="Consul Catalog" +- "traefik.http.middlewares.test-compress.compress.includedcontenttypes=application/json,text/html,text/plain" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-compress: + compress: + includedContentTypes: + - application/json + - text/html + - text/plain +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-compress.compress] + includedContentTypes = ["application/json","text/html","text/plain"] +``` + ### `minResponseBodyBytes` _Optional, Default=1024_ diff --git a/docs/content/middlewares/http/digestauth.md b/docs/content/middlewares/http/digestauth.md index 72b868c05..a5e2f3d06 100644 --- a/docs/content/middlewares/http/digestauth.md +++ b/docs/content/middlewares/http/digestauth.md @@ -10,7 +10,7 @@ Adding Digest Authentication ![BasicAuth](../../assets/img/middleware/digestauth.png) -The DigestAuth middleware restricts access to your services to known users. +The DigestAuth middleware grants access to services to authorized users only. ## Configuration Examples diff --git a/docs/content/middlewares/http/errorpages.md b/docs/content/middlewares/http/errorpages.md index 9456bf978..c4569133e 100644 --- a/docs/content/middlewares/http/errorpages.md +++ b/docs/content/middlewares/http/errorpages.md @@ -21,7 +21,7 @@ The Errors middleware returns a custom page in lieu of the default, according to ```yaml tab="Docker & Swarm" # Dynamic Custom Error Page for 5XX Status Code labels: - - "traefik.http.middlewares.test-errors.errors.status=500-599" + - "traefik.http.middlewares.test-errors.errors.status=500,501,503,505-599" - "traefik.http.middlewares.test-errors.errors.service=serviceError" - "traefik.http.middlewares.test-errors.errors.query=/{status}.html" ``` @@ -34,7 +34,10 @@ metadata: spec: errors: status: - - "500-599" + - "500" + - "501" + - "503" + - "505-599" query: /{status}.html service: name: whoami @@ -42,20 +45,23 @@ spec: ``` ```yaml tab="Consul Catalog" -# Dynamic Custom Error Page for 5XX Status Code -- "traefik.http.middlewares.test-errors.errors.status=500-599" +# Dynamic Custom Error Page for 5XX Status Code excluding 502 and 504 +- "traefik.http.middlewares.test-errors.errors.status=500,501,503,505-599" - "traefik.http.middlewares.test-errors.errors.service=serviceError" - "traefik.http.middlewares.test-errors.errors.query=/{status}.html" ``` ```yaml tab="File (YAML)" -# Custom Error Page for 5XX +# Dynamic Custom Error Page for 5XX Status Code excluding 502 and 504 http: middlewares: test-errors: errors: status: - - "500-599" + - "500" + - "501" + - "503" + - "505-599" service: serviceError query: "/{status}.html" @@ -64,10 +70,10 @@ http: ``` ```toml tab="File (TOML)" -# Custom Error Page for 5XX +# Dynamic Custom Error Page for 5XX Status Code excluding 502 and 504 [http.middlewares] [http.middlewares.test-errors.errors] - status = ["500-599"] + status = ["500","501","503","505-599"] service = "serviceError" query = "/{status}.html" @@ -85,14 +91,16 @@ http: The `status` option defines which status or range of statuses should result in an error page. -The status code ranges are inclusive (`500-599` will trigger with every code between `500` and `599`, `500` and `599` included). +The status code ranges are inclusive (`505-599` will trigger with every code between `505` and `599`, `505` and `599` included). !!! note "" You can define either a status code as a number (`500`), as multiple comma-separated numbers (`500,502`), - as ranges by separating two codes with a dash (`500-599`), - or a combination of the two (`404,418,500-599`). + as ranges by separating two codes with a dash (`505-599`), + or a combination of the two (`404,418,505-599`). + The comma-separated syntax is only available for label-based providers. + The examples above demonstrate which syntax is appropriate for each provider. ### `service` diff --git a/docs/content/middlewares/http/forwardauth.md b/docs/content/middlewares/http/forwardauth.md index 6b69c620b..25b58480c 100644 --- a/docs/content/middlewares/http/forwardauth.md +++ b/docs/content/middlewares/http/forwardauth.md @@ -285,6 +285,55 @@ http: authRequestHeaders = "Accept,X-CustomHeader" ``` +### `addAuthCookiesToResponse` + +The `addAuthCookiesToResponse` option is the list of cookies to copy from the authentication server to the response, +replacing any existing conflicting cookie from the forwarded response. + +!!! info + + Please note that all backend cookies matching the configured list will not be added to the response. + +```yaml tab="Docker" +labels: + - "traefik.http.middlewares.test-auth.forwardauth.addAuthCookiesToResponse=Session-Cookie,State-Cookie" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://example.com/auth + addAuthCookiesToResponse: + - Session-Cookie + - State-Cookie +``` + +```yaml tab="Consul Catalog" +- "traefik.http.middlewares.test-auth.forwardauth.addAuthCookiesToResponse=Session-Cookie,State-Cookie" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://example.com/auth" + addAuthCookiesToResponse = ["Session-Cookie", "State-Cookie"] +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://example.com/auth" + addAuthCookiesToResponse: + - "Session-Cookie" + - "State-Cookie" +``` + ### `tls` _Optional_ diff --git a/docs/content/middlewares/http/headers.md b/docs/content/middlewares/http/headers.md index 4c30b349e..7c4599b18 100644 --- a/docs/content/middlewares/http/headers.md +++ b/docs/content/middlewares/http/headers.md @@ -190,7 +190,8 @@ spec: - "GET" - "OPTIONS" - "PUT" - accessControlAllowHeaders: "*" + accessControlAllowHeaders: + - "*" accessControlAllowOriginList: - "https://foo.bar.org" - "https://example.org" @@ -226,8 +227,8 @@ http: ```toml tab="File (TOML)" [http.middlewares] [http.middlewares.testHeader.headers] - accessControlAllowMethods= ["GET", "OPTIONS", "PUT"] - accessControlAllowHeaders= "*" + accessControlAllowMethods = ["GET", "OPTIONS", "PUT"] + accessControlAllowHeaders = [ "*" ] accessControlAllowOriginList = ["https://foo.bar.org","https://example.org"] accessControlMaxAge = 100 addVaryHeader = true diff --git a/docs/content/middlewares/http/ipwhitelist.md b/docs/content/middlewares/http/ipwhitelist.md new file mode 100644 index 000000000..e093ad0a9 --- /dev/null +++ b/docs/content/middlewares/http/ipwhitelist.md @@ -0,0 +1,201 @@ +--- +title: "Traefik HTTP Middlewares IPWhiteList" +description: "Learn how to use IPWhiteList in HTTP middleware for limiting clients to specific IPs in Traefik Proxy. Read the technical documentation." +--- + +# IPWhiteList + +Limiting Clients to Specific IPs +{: .subtitle } + +![IPWhiteList](../../assets/img/middleware/ipwhitelist.png) + +IPWhiteList accepts / refuses requests based on the client IP. + +!!! warning + + This middleware is deprecated, please use the [IPAllowList](./ipallowlist.md) middleware instead. + +## Configuration Examples + +```yaml tab="Docker" +# Accepts request from defined IP +labels: + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ipwhitelist +spec: + ipWhiteList: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.7 +``` + +```yaml tab="Consul Catalog" +# Accepts request from defined IP +- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="File (YAML)" +# Accepts request from defined IP +http: + middlewares: + test-ipwhitelist: + ipWhiteList: + sourceRange: + - "127.0.0.1/32" + - "192.168.1.7" +``` + +```toml tab="File (TOML)" +# Accepts request from defined IP +[http.middlewares] + [http.middlewares.test-ipwhitelist.ipWhiteList] + sourceRange = ["127.0.0.1/32", "192.168.1.7"] +``` + +## Configuration Options + +### `sourceRange` + +The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation). + +### `ipStrategy` + +The `ipStrategy` option defines two parameters that set how Traefik determines the client IP: `depth`, and `excludedIPs`. +If no strategy is set, the default behavior is to match `sourceRange` against the Remote address found in the request. + +!!! important "As a middleware, whitelisting happens before the actual proxying to the backend takes place. In addition, the previous network hop only gets appended to `X-Forwarded-For` during the last stages of proxying, i.e. after it has already passed through whitelisting. Therefore, during whitelisting, as the previous network hop is not yet present in `X-Forwarded-For`, it cannot be matched against `sourceRange`." + +#### `ipStrategy.depth` + +The `depth` option tells Traefik to use the `X-Forwarded-For` header and take the IP located at the `depth` position (starting from the right). + +- If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP will be empty. +- `depth` is ignored if its value is less than or equal to 0. + +!!! example "Examples of Depth & X-Forwarded-For" + + If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP is `"10.0.0.1"` (at depth 4) but the IP used for the whitelisting is `"12.0.0.1"` (`depth=2`). + + | `X-Forwarded-For` | `depth` | clientIP | + |-----------------------------------------|---------|--------------| + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | + +```yaml tab="Docker" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +labels: + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.depth=2" +``` + +```yaml tab="Kubernetes" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ipwhitelist +spec: + ipWhiteList: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.7 + ipStrategy: + depth: 2 +``` + +```yaml tab="Consul Catalog" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.depth=2" +``` + +```yaml tab="File (YAML)" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +http: + middlewares: + test-ipwhitelist: + ipWhiteList: + sourceRange: + - "127.0.0.1/32" + - "192.168.1.7" + ipStrategy: + depth: 2 +``` + +```toml tab="File (TOML)" +# Whitelisting Based on `X-Forwarded-For` with `depth=2` +[http.middlewares] + [http.middlewares.test-ipwhitelist.ipWhiteList] + sourceRange = ["127.0.0.1/32", "192.168.1.7"] + [http.middlewares.test-ipwhitelist.ipWhiteList.ipStrategy] + depth = 2 +``` + +#### `ipStrategy.excludedIPs` + +`excludedIPs` configures Traefik to scan the `X-Forwarded-For` header and select the first IP not in the list. + +!!! important "If `depth` is specified, `excludedIPs` is ignored." + +!!! example "Example of ExcludedIPs & X-Forwarded-For" + + | `X-Forwarded-For` | `excludedIPs` | clientIP | + |-----------------------------------------|-----------------------|--------------| + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` | + | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` | + | `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` | + +```yaml tab="Docker" +# Exclude from `X-Forwarded-For` +labels: + - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="Kubernetes" +# Exclude from `X-Forwarded-For` +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ipwhitelist +spec: + ipWhiteList: + ipStrategy: + excludedIPs: + - 127.0.0.1/32 + - 192.168.1.7 +``` + +```yaml tab="Consul Catalog" +# Exclude from `X-Forwarded-For` +- "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="File (YAML)" +# Exclude from `X-Forwarded-For` +http: + middlewares: + test-ipwhitelist: + ipWhiteList: + ipStrategy: + excludedIPs: + - "127.0.0.1/32" + - "192.168.1.7" +``` + +```toml tab="File (TOML)" +# Exclude from `X-Forwarded-For` +[http.middlewares] + [http.middlewares.test-ipwhitelist.ipWhiteList] + [http.middlewares.test-ipwhitelist.ipWhiteList.ipStrategy] + excludedIPs = ["127.0.0.1/32", "192.168.1.7"] +``` diff --git a/docs/content/middlewares/tcp/ipwhitelist.md b/docs/content/middlewares/tcp/ipwhitelist.md new file mode 100644 index 000000000..c3d1ccb43 --- /dev/null +++ b/docs/content/middlewares/tcp/ipwhitelist.md @@ -0,0 +1,64 @@ +--- +title: "Traefik TCP Middlewares IPWhiteList" +description: "Learn how to use IPWhiteList in TCP middleware for limiting clients to specific IPs in Traefik Proxy. Read the technical documentation." +--- + +# IPWhiteList + +Limiting Clients to Specific IPs +{: .subtitle } + +IPWhiteList accepts / refuses connections based on the client IP. + +!!! warning + + This middleware is deprecated, please use the [IPAllowList](./ipallowlist.md) middleware instead. + +## Configuration Examples + +```yaml tab="Docker" +# Accepts connections from defined IP +labels: + - "traefik.tcp.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: MiddlewareTCP +metadata: + name: test-ipwhitelist +spec: + ipWhiteList: + sourceRange: + - 127.0.0.1/32 + - 192.168.1.7 +``` + +```yaml tab="Consul Catalog" +# Accepts request from defined IP +- "traefik.tcp.middlewares.test-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.1.7" +``` + +```toml tab="File (TOML)" +# Accepts request from defined IP +[tcp.middlewares] + [tcp.middlewares.test-ipwhitelist.ipWhiteList] + sourceRange = ["127.0.0.1/32", "192.168.1.7"] +``` + +```yaml tab="File (YAML)" +# Accepts request from defined IP +tcp: + middlewares: + test-ipwhitelist: + ipWhiteList: + sourceRange: + - "127.0.0.1/32" + - "192.168.1.7" +``` + +## Configuration Options + +### `sourceRange` + +The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation). diff --git a/docs/content/migration/v2-to-v3.md b/docs/content/migration/v2-to-v3.md index b9a2fd769..70b27f820 100644 --- a/docs/content/migration/v2-to-v3.md +++ b/docs/content/migration/v2-to-v3.md @@ -10,28 +10,335 @@ How to Migrate from Traefik v2 to Traefik v3. 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. +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. -## IPWhiteList +## Static configuration + +### Docker & Docker Swarm + +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 + ``` + +### HTTP3 Experimental Configuration + +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. + +### Consul provider + +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 + ``` + +### ConsulCatalog provider + +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 + ``` + +### Nomad provider + +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 + ``` + +### Rancher v1 Provider + +In v3, the Rancher v1 provider has been removed because Rancher v1 is [no longer actively maintaned](https://rancher.com/docs/os/v1.x/en/support/), +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. + +### 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 + +### IPWhiteList In v3, we renamed the `IPWhiteList` middleware to `IPAllowList` without changing anything to the configuration. -## gRPC Metrics +### Deprecated Options Removal -In v3, the reported status code for gRPC requests is now the value of the `Grpc-Status` header. - -## Deprecated Options Removal - -- The `pilot` option has been removed from the static configuration. - The `tracing.datadog.globaltag` option has been removed. -- The `namespace` option of Consul, Consul Catalog and Nomad providers has been removed. - The `tls.caOptional` option has been removed from the ForwardAuth middleware, as well as from the HTTP, Consul, Etcd, Redis, ZooKeeper, Consul Catalog, and Docker providers. - `sslRedirect`, `sslTemporaryRedirect`, `sslHost`, `sslForceHost` and `featurePolicy` options of the Headers middleware have been removed. - The `forceSlash` option of the StripPrefix middleware has been removed. - The `preferServerCipherSuites` option has been removed. -## Matchers +### Matchers In v3, the `Headers` and `HeadersRegexp` matchers have been renamed to `Header` and `HeaderRegexp` respectively. @@ -48,61 +355,63 @@ and should be explicitly combined using logical operators to mimic previous beha `HostHeader` has been removed, use `Host` instead. -## 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. - -## HTTP/3 - -In v3, HTTP/3 is no longer an experimental feature. -The `experimental.http3` option has been removed from the static configuration. - -## TCP ServersTransport - -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. - ### 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) -## Rancher v1 - -In v3, the rancher v1 provider has been removed because Rancher v1 is [no longer actively maintaned](https://rancher.com/docs/os/v1.x/en/support/) and v2 is supported as a standard Kubernetes provider. - -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. - -## Marathon provider - -In v3, the Marathon provider has been removed. - -## InfluxDB v1 - -In v3, the InfluxDB v1 metrics provider has been removed because InfluxDB v1.x maintenance [ended in 2021](https://www.influxdata.com/blog/influxdb-oss-and-enterprise-roadmap-update-from-influxdays-emea/). - -## Kubernetes CRDs API Group `traefik.containo.us` +### 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. -## Docker & Docker Swarm - -In v3, the provider Docker has been split into 2 providers: - -- Docker provider (without Swarm support) -- Swarm provider (Swarm support only) - -## Kubernetes Ingress API Group `networking.k8s.io/v1beta1` +### 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` +### 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. diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 7d14ae9aa..6db72ea76 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -526,3 +526,13 @@ kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v2.10/docs/co ### Traefik Hub In `v2.10`, Traefik Hub configuration has been removed because Traefik Hub v2 doesn't require this configuration. + +## v2.11 + +### IPWhiteList (HTTP) + +In `v2.11`, the `IPWhiteList` middleware is deprecated, please use the [IPAllowList](../middlewares/http/ipallowlist.md) middleware instead. + +### IPWhiteList (TCP) + +In `v2.11`, the `IPWhiteList` middleware is deprecated, please use the [IPAllowList](../middlewares/tcp/ipallowlist.md) middleware instead. diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 64c16039b..dac71805f 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -54,7 +54,7 @@ If the given format is unsupported, the default (CLF) is used instead. !!! info "Common Log Format" ```html - - [] " " "" "" "" "" ms + - [] " " "" "" "" "" ms ``` ### `bufferingSize` @@ -218,7 +218,7 @@ accessLog: | `RequestContentSize` | The number of bytes in the request entity (a.k.a. body) sent by the client. | | `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. | | `OriginContentSize` | The content length specified by the origin server, or 0 if unspecified. | - | `OriginStatus` | The HTTP status code returned by the origin server. If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent. | + | `OriginStatus` | The HTTP status code returned by the origin server. If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent (0). | | `OriginStatusLine` | `OriginStatus` + Status code explanation | | `DownstreamStatus` | The HTTP status code returned to the client. | | `DownstreamStatusLine` | `DownstreamStatus` + Status code explanation | diff --git a/docs/content/observability/metrics/prometheus.md b/docs/content/observability/metrics/prometheus.md index be3e29cb6..4a3ec0a32 100644 --- a/docs/content/observability/metrics/prometheus.md +++ b/docs/content/observability/metrics/prometheus.md @@ -227,4 +227,12 @@ The following metric is produced : ```bash traefik_entrypoint_requests_total{code="200",entrypoint="web",method="GET",protocol="http",useragent="foobar"} 1 -``` \ No newline at end of file +``` + +!!! info "`Host` header value" + + The `Host` header is never present in the Header map of a request, as per go documentation says: + // For incoming requests, the Host header is promoted to the + // Request.Host field and removed from the Header map. + + As a workaround, to obtain the Host of a request as a label, one should use instead the `X-Forwarded-For` header. diff --git a/docs/content/observability/tracing/datadog.md b/docs/content/observability/tracing/datadog.md deleted file mode 100644 index fe14bc72f..000000000 --- a/docs/content/observability/tracing/datadog.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: "Traefik Datadog Tracing Documentation" -description: "Traefik Proxy supports Datadog for tracing. Read the technical documentation to enable Datadog for observability." ---- - -# Datadog - -To enable the Datadog tracer: - -```yaml tab="File (YAML)" -tracing: - datadog: {} -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.datadog] -``` - -```bash tab="CLI" ---tracing.datadog=true -``` - -#### `localAgentHostPort` - -_Optional, Default="localhost:8126"_ - -Local Agent Host Port instructs the reporter to send spans to the Datadog Agent at this address (host:port). - -```yaml tab="File (YAML)" -tracing: - datadog: - localAgentHostPort: localhost:8126 -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.datadog] - localAgentHostPort = "localhost:8126" -``` - -```bash tab="CLI" ---tracing.datadog.localAgentHostPort=localhost:8126 -``` - -#### `localAgentSocket` - -_Optional, Default=""_ - -Local Agent Socket instructs the reporter to send spans to the Datadog Agent at this UNIX socket. - -```yaml tab="File (YAML)" -tracing: - datadog: - localAgentSocket: /var/run/datadog/apm.socket -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.datadog] - localAgentSocket = "/var/run/datadog/apm.socket" -``` - -```bash tab="CLI" ---tracing.datadog.localAgentSocket=/var/run/datadog/apm.socket -``` - -#### `debug` - -_Optional, Default=false_ - -Enables Datadog debug. - -```yaml tab="File (YAML)" -tracing: - datadog: - debug: true -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.datadog] - debug = true -``` - -```bash tab="CLI" ---tracing.datadog.debug=true -``` - -#### `globalTags` - -_Optional, Default=empty_ - -Applies a list of shared key:value tags on all spans. - -```yaml tab="File (YAML)" -tracing: - datadog: - globalTags: - tag1: foo - tag2: bar -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.datadog] - [tracing.datadog.globalTags] - tag1 = "foo" - tag2 = "bar" -``` - -```bash tab="CLI" ---tracing.datadog.globalTags.tag1=foo ---tracing.datadog.globalTags.tag2=bar -``` - -#### `prioritySampling` - -_Optional, Default=false_ - -Enables priority sampling. -When using distributed tracing, -this option must be enabled in order to get all the parts of a distributed trace sampled. - -```yaml tab="File (YAML)" -tracing: - datadog: - prioritySampling: true -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.datadog] - prioritySampling = true -``` - -```bash tab="CLI" ---tracing.datadog.prioritySampling=true -``` diff --git a/docs/content/observability/tracing/elastic.md b/docs/content/observability/tracing/elastic.md deleted file mode 100644 index 0cb0dccee..000000000 --- a/docs/content/observability/tracing/elastic.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: "Traefik Elastic Documentation" -description: "Traefik supports several tracing backends, including Elastic. Learn how to implement it for observability in Traefik Proxy. Read the technical documentation." ---- - -# Elastic - -To enable the Elastic tracer: - -```yaml tab="File (YAML)" -tracing: - elastic: {} -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.elastic] -``` - -```bash tab="CLI" ---tracing.elastic=true -``` - -#### `serverURL` - -_Optional, Default="http://localhost:8200"_ - -URL of the Elastic APM server. - -```yaml tab="File (YAML)" -tracing: - elastic: - serverURL: "http://apm:8200" -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.elastic] - serverURL = "http://apm:8200" -``` - -```bash tab="CLI" ---tracing.elastic.serverurl="http://apm:8200" -``` - -#### `secretToken` - -_Optional, Default=""_ - -Token used to connect to Elastic APM Server. - -```yaml tab="File (YAML)" -tracing: - elastic: - secretToken: "mytoken" -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.elastic] - secretToken = "mytoken" -``` - -```bash tab="CLI" ---tracing.elastic.secrettoken="mytoken" -``` - -#### `serviceEnvironment` - -_Optional, Default=""_ - -Environment's name where Traefik is deployed in, e.g. `production` or `staging`. - -```yaml tab="File (YAML)" -tracing: - elastic: - serviceEnvironment: "production" -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.elastic] - serviceEnvironment = "production" -``` - -```bash tab="CLI" ---tracing.elastic.serviceenvironment="production" -``` - -### Further - -Additional configuration of Elastic APM Go agent can be done using environment variables. -See [APM Go agent reference](https://www.elastic.co/guide/en/apm/agent/go/current/configuration.html). diff --git a/docs/content/observability/tracing/haystack.md b/docs/content/observability/tracing/haystack.md deleted file mode 100644 index af9945ed7..000000000 --- a/docs/content/observability/tracing/haystack.md +++ /dev/null @@ -1,176 +0,0 @@ ---- -title: "Traefik Haystack Documentation" -description: "Traefik supports several tracing backends, including Haystack. Learn how to implement it for observability in Traefik Proxy. Read the technical documentation." ---- - -# Haystack - -To enable the Haystack tracer: - -```yaml tab="File (YAML)" -tracing: - haystack: {} -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.haystack] -``` - -```bash tab="CLI" ---tracing.haystack=true -``` - -#### `localAgentHost` - -_Required, Default="127.0.0.1"_ - -Local Agent Host instructs reporter to send spans to the Haystack Agent at this address. - -```yaml tab="File (YAML)" -tracing: - haystack: - localAgentHost: 127.0.0.1 -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.haystack] - localAgentHost = "127.0.0.1" -``` - -```bash tab="CLI" ---tracing.haystack.localAgentHost=127.0.0.1 -``` - -#### `localAgentPort` - -_Required, Default=35000_ - -Local Agent Port instructs reporter to send spans to the Haystack Agent at this port. - -```yaml tab="File (YAML)" -tracing: - haystack: - localAgentPort: 35000 -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.haystack] - localAgentPort = 35000 -``` - -```bash tab="CLI" ---tracing.haystack.localAgentPort=35000 -``` - -#### `globalTag` - -_Optional, Default=empty_ - -Applies shared key:value tag on all spans. - -```yaml tab="File (YAML)" -tracing: - haystack: - globalTag: sample:test -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.haystack] - globalTag = "sample:test" -``` - -```bash tab="CLI" ---tracing.haystack.globalTag=sample:test -``` - -#### `traceIDHeaderName` - -_Optional, Default=empty_ - -Sets the header name used to store the trace ID. - -```yaml tab="File (YAML)" -tracing: - haystack: - traceIDHeaderName: Trace-ID -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.haystack] - traceIDHeaderName = "Trace-ID" -``` - -```bash tab="CLI" ---tracing.haystack.traceIDHeaderName=Trace-ID -``` - -#### `parentIDHeaderName` - -_Optional, Default=empty_ - -Sets the header name used to store the parent ID. - -```yaml tab="File (YAML)" -tracing: - haystack: - parentIDHeaderName: Parent-Message-ID -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.haystack] - parentIDHeaderName = "Parent-Message-ID" -``` - -```bash tab="CLI" ---tracing.haystack.parentIDHeaderName=Parent-Message-ID -``` - -#### `spanIDHeaderName` - -_Optional, Default=empty_ - -Sets the header name used to store the span ID. - -```yaml tab="File (YAML)" -tracing: - haystack: - spanIDHeaderName: Message-ID -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.haystack] - spanIDHeaderName = "Message-ID" -``` - -```bash tab="CLI" ---tracing.haystack.spanIDHeaderName=Message-ID -``` - -#### `baggagePrefixHeaderName` - -_Optional, Default=empty_ - -Sets the header name prefix used to store baggage items in a map. - -```yaml tab="File (YAML)" -tracing: - haystack: - baggagePrefixHeaderName: "sample" -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.haystack] - baggagePrefixHeaderName = "sample" -``` - -```bash tab="CLI" ---tracing.haystack.baggagePrefixHeaderName=sample -``` diff --git a/docs/content/observability/tracing/instana.md b/docs/content/observability/tracing/instana.md deleted file mode 100644 index 914d9d3f4..000000000 --- a/docs/content/observability/tracing/instana.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: "Traefik Instana Documentation" -description: "Traefik supports several tracing backends, including Instana. Learn how to implement it for observability in Traefik Proxy. Read the technical documentation." ---- - -# Instana - -To enable the Instana tracer: - -```yaml tab="File (YAML)" -tracing: - instana: {} -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.instana] -``` - -```bash tab="CLI" ---tracing.instana=true -``` - -#### `localAgentHost` - -_Required, Default="127.0.0.1"_ - -Local Agent Host instructs reporter to send spans to the Instana Agent at this address. - -```yaml tab="File (YAML)" -tracing: - instana: - localAgentHost: 127.0.0.1 -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.instana] - localAgentHost = "127.0.0.1" -``` - -```bash tab="CLI" ---tracing.instana.localAgentHost=127.0.0.1 -``` - -#### `localAgentPort` - -_Required, Default=42699_ - -Local Agent port instructs reporter to send spans to the Instana Agent listening on this port. - -```yaml tab="File (YAML)" -tracing: - instana: - localAgentPort: 42699 -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.instana] - localAgentPort = 42699 -``` - -```bash tab="CLI" ---tracing.instana.localAgentPort=42699 -``` - -#### `logLevel` - -_Required, Default="info"_ - -Sets Instana tracer log level. - -Valid values are: - -- `error` -- `warn` -- `debug` -- `info` - -```yaml tab="File (YAML)" -tracing: - instana: - logLevel: info -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.instana] - logLevel = "info" -``` - -```bash tab="CLI" ---tracing.instana.logLevel=info -``` - -#### `enableAutoProfile` - -_Required, Default=false_ - -Enables [automatic profiling](https://www.ibm.com/docs/en/obi/current?topic=instana-profile-processes) for the Traefik process. - -```yaml tab="File (YAML)" -tracing: - instana: - enableAutoProfile: true -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.instana] - enableAutoProfile = true -``` - -```bash tab="CLI" ---tracing.instana.enableAutoProfile=true -``` diff --git a/docs/content/observability/tracing/jaeger.md b/docs/content/observability/tracing/jaeger.md deleted file mode 100644 index d40ffed6d..000000000 --- a/docs/content/observability/tracing/jaeger.md +++ /dev/null @@ -1,294 +0,0 @@ ---- -title: "Traefik Jaeger Documentation" -description: "Traefik supports several tracing backends, including Jaeger. Learn how to implement it for observability in Traefik Proxy. Read the technical documentation." ---- - -# Jaeger - -To enable the Jaeger tracer: - -```yaml tab="File (YAML)" -tracing: - jaeger: {} -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger] -``` - -```bash tab="CLI" ---tracing.jaeger=true -``` - -!!! warning - Traefik is able to send data over the compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent) - or a [Jaeger collector](https://www.jaegertracing.io/docs/deployment/#collector). - -!!! info - All Jaeger configuration can be overridden by [environment variables](https://github.com/jaegertracing/jaeger-client-go#environment-variables) - -#### `samplingServerURL` - -_Required, Default="http://localhost:5778/sampling"_ - -Address of the Jaeger Agent HTTP sampling server. - -```yaml tab="File (YAML)" -tracing: - jaeger: - samplingServerURL: http://localhost:5778/sampling -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger] - samplingServerURL = "http://localhost:5778/sampling" -``` - -```bash tab="CLI" ---tracing.jaeger.samplingServerURL=http://localhost:5778/sampling -``` - -#### `samplingType` - -_Required, Default="const"_ - -Type of the sampler. - -Valid values are: - -- `const` -- `probabilistic` -- `rateLimiting` - -```yaml tab="File (YAML)" -tracing: - jaeger: - samplingType: const -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger] - samplingType = "const" -``` - -```bash tab="CLI" ---tracing.jaeger.samplingType=const -``` - -#### `samplingParam` - -_Required, Default=1.0_ - -Value passed to the sampler. - -Valid values are: - -- for `const` sampler, 0 or 1 for always false/true respectively -- for `probabilistic` sampler, a probability between 0 and 1 -- for `rateLimiting` sampler, the number of spans per second - -```yaml tab="File (YAML)" -tracing: - jaeger: - samplingParam: 1.0 -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger] - samplingParam = 1.0 -``` - -```bash tab="CLI" ---tracing.jaeger.samplingParam=1.0 -``` - -#### `localAgentHostPort` - -_Required, Default="127.0.0.1:6831"_ - -Local Agent Host Port instructs the reporter to send spans to the Jaeger Agent at this address (host:port). - -```yaml tab="File (YAML)" -tracing: - jaeger: - localAgentHostPort: 127.0.0.1:6831 -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger] - localAgentHostPort = "127.0.0.1:6831" -``` - -```bash tab="CLI" ---tracing.jaeger.localAgentHostPort=127.0.0.1:6831 -``` - -#### `gen128Bit` - -_Optional, Default=false_ - -Generates 128 bits trace IDs, compatible with OpenCensus. - -```yaml tab="File (YAML)" -tracing: - jaeger: - gen128Bit: true -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger] - gen128Bit = true -``` - -```bash tab="CLI" ---tracing.jaeger.gen128Bit -``` - -#### `propagation` - -_Required, Default="jaeger"_ - -Sets the propagation header type. - -Valid values are: - -- `jaeger`, jaeger's default trace header. -- `b3`, compatible with OpenZipkin - -```yaml tab="File (YAML)" -tracing: - jaeger: - propagation: jaeger -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger] - propagation = "jaeger" -``` - -```bash tab="CLI" ---tracing.jaeger.propagation=jaeger -``` - -#### `traceContextHeaderName` - -_Required, Default="uber-trace-id"_ - -HTTP header name used to propagate tracing context. -This must be in lower-case to avoid mismatches when decoding incoming headers. - -```yaml tab="File (YAML)" -tracing: - jaeger: - traceContextHeaderName: uber-trace-id -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger] - traceContextHeaderName = "uber-trace-id" -``` - -```bash tab="CLI" ---tracing.jaeger.traceContextHeaderName=uber-trace-id -``` - -### disableAttemptReconnecting - -_Optional, Default=true_ - -Disables the UDP connection helper that periodically re-resolves the agent's hostname and reconnects if there was a change. -Enabling the re-resolving of UDP address make the client more robust in Kubernetes deployments. - -```yaml tab="File (YAML)" -tracing: - jaeger: - disableAttemptReconnecting: false -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger] - disableAttemptReconnecting = false -``` - -```bash tab="CLI" ---tracing.jaeger.disableAttemptReconnecting=false -``` - -### `collector` -#### `endpoint` - -_Optional, Default=""_ - -Collector Endpoint instructs the reporter to send spans to the Jaeger Collector at this URL. - -```yaml tab="File (YAML)" -tracing: - jaeger: - collector: - endpoint: http://127.0.0.1:14268/api/traces?format=jaeger.thrift -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger.collector] - endpoint = "http://127.0.0.1:14268/api/traces?format=jaeger.thrift" -``` - -```bash tab="CLI" ---tracing.jaeger.collector.endpoint=http://127.0.0.1:14268/api/traces?format=jaeger.thrift -``` - -#### `user` - -_Optional, Default=""_ - -User instructs the reporter to include a user for basic HTTP authentication when sending spans to the Jaeger Collector. - -```yaml tab="File (YAML)" -tracing: - jaeger: - collector: - user: my-user -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger.collector] - user = "my-user" -``` - -```bash tab="CLI" ---tracing.jaeger.collector.user=my-user -``` - -#### `password` - -_Optional, Default=""_ - -Password instructs the reporter to include a password for basic HTTP authentication when sending spans to the Jaeger Collector. - -```yaml tab="File (YAML)" -tracing: - jaeger: - collector: - password: my-password -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.jaeger.collector] - password = "my-password" -``` - -```bash tab="CLI" ---tracing.jaeger.collector.password=my-password -``` diff --git a/docs/content/observability/tracing/opentelemetry.md b/docs/content/observability/tracing/opentelemetry.md index d39e09b58..52b37a0a5 100644 --- a/docs/content/observability/tracing/opentelemetry.md +++ b/docs/content/observability/tracing/opentelemetry.md @@ -9,122 +9,75 @@ To enable the OpenTelemetry tracer: ```yaml tab="File (YAML)" tracing: - openTelemetry: {} + otlp: {} ``` ```toml tab="File (TOML)" [tracing] - [tracing.openTelemetry] + [tracing.otlp] ``` ```bash tab="CLI" ---tracing.openTelemetry=true +--tracing.otlp=true ``` -!!! info "The OpenTelemetry trace reporter will export traces to the collector using HTTP by default, see the [gRPC Section](#grpc-configuration) to use gRPC." +!!! info "The OpenTelemetry trace reporter will export traces to the collector using HTTP by default (http://localhost:4318/v1/traces), +see the [gRPC Section](#grpc-configuration) to use gRPC." !!! info "Trace sampling" - By default, the OpenTelemetry trace reporter will sample 100% of traces. + By default, the OpenTelemetry trace reporter will sample 100% of traces. See [OpenTelemetry's SDK configuration](https://opentelemetry.io/docs/reference/specification/sdk-environment-variables/#general-sdk-configuration) to customize the sampling strategy. -#### `address` +### HTTP configuration -_Required, Default="localhost:4318", Format="`:`"_ +_Optional_ -Address of the OpenTelemetry Collector to send spans to. +This instructs the reporter to send spans to the OpenTelemetry Collector using HTTP. ```yaml tab="File (YAML)" tracing: - openTelemetry: - address: localhost:4318 + otlp: + http: {} ``` ```toml tab="File (TOML)" [tracing] - [tracing.openTelemetry] - address = "localhost:4318" + [tracing.otlp.http] ``` ```bash tab="CLI" ---tracing.openTelemetry.address=localhost:4318 +--tracing.otlp.http=true ``` -#### `headers` +#### `endpoint` -_Optional, Default={}_ +_Required, Default="http://localhost:4318/v1/traces", Format="`://:`"_ -Additional headers sent with spans by the reporter to the OpenTelemetry Collector. +URL of the OpenTelemetry Collector to send spans to. ```yaml tab="File (YAML)" tracing: - openTelemetry: - headers: - foo: bar - baz: buz + otlp: + http: + endpoint: http://localhost:4318/v1/traces ``` ```toml tab="File (TOML)" [tracing] - [tracing.openTelemetry.headers] - foo = "bar" - baz = "buz" + [tracing.otlp.http] + endpoint = "http://localhost:4318/v1/traces" ``` ```bash tab="CLI" ---tracing.openTelemetry.headers.foo=bar --tracing.openTelemetry.headers.baz=buz -``` - -#### `insecure` - -_Optional, Default=false_ - -Allows reporter to send spans to the OpenTelemetry Collector without using a secured protocol. - -```yaml tab="File (YAML)" -tracing: - openTelemetry: - insecure: true -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.openTelemetry] - insecure = true -``` - -```bash tab="CLI" ---tracing.openTelemetry.insecure=true -``` - -#### `path` - -_Required, Default="/v1/traces"_ - -Allows to override the default URL path used for sending traces. -This option has no effect when using gRPC transport. - -```yaml tab="File (YAML)" -tracing: - openTelemetry: - path: /foo/v1/traces -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.openTelemetry] - path = "/foo/v1/traces" -``` - -```bash tab="CLI" ---tracing.openTelemetry.path=/foo/v1/traces +--tracing.otlp.http.endpoint=http://localhost:4318/v1/traces ``` #### `tls` _Optional_ -Defines the TLS configuration used by the reporter to send spans to the OpenTelemetry Collector. +Defines the Client TLS configuration used by the reporter to send spans to the OpenTelemetry Collector. ##### `ca` @@ -135,18 +88,19 @@ it defaults to the system bundle. ```yaml tab="File (YAML)" tracing: - openTelemetry: - tls: - ca: path/to/ca.crt + otlp: + http: + tls: + ca: path/to/ca.crt ``` ```toml tab="File (TOML)" -[tracing.openTelemetry.tls] +[tracing.otlp.http.tls] ca = "path/to/ca.crt" ``` ```bash tab="CLI" ---tracing.openTelemetry.tls.ca=path/to/ca.crt +--tracing.otlp.http.tls.ca=path/to/ca.crt ``` ##### `cert` @@ -158,21 +112,22 @@ When using this option, setting the `key` option is required. ```yaml tab="File (YAML)" tracing: - openTelemetry: - tls: - cert: path/to/foo.cert - key: path/to/foo.key + otlp: + http: + tls: + cert: path/to/foo.cert + key: path/to/foo.key ``` ```toml tab="File (TOML)" -[tracing.openTelemetry.tls] +[tracing.otlp.http.tls] cert = "path/to/foo.cert" key = "path/to/foo.key" ``` ```bash tab="CLI" ---tracing.openTelemetry.tls.cert=path/to/foo.cert ---tracing.openTelemetry.tls.key=path/to/foo.key +--tracing.otlp.http.tls.cert=path/to/foo.cert +--tracing.otlp.http.tls.key=path/to/foo.key ``` ##### `key` @@ -184,21 +139,22 @@ When using this option, setting the `cert` option is required. ```yaml tab="File (YAML)" tracing: - openTelemetry: - tls: - cert: path/to/foo.cert - key: path/to/foo.key + otlp: + http: + tls: + cert: path/to/foo.cert + key: path/to/foo.key ``` ```toml tab="File (TOML)" -[tracing.openTelemetry.tls] +[tracing.otlp.http.tls] cert = "path/to/foo.cert" key = "path/to/foo.key" ``` ```bash tab="CLI" ---tracing.openTelemetry.tls.cert=path/to/foo.cert ---tracing.openTelemetry.tls.key=path/to/foo.key +--tracing.otlp.http.tls.cert=path/to/foo.cert +--tracing.otlp.http.tls.key=path/to/foo.key ``` ##### `insecureSkipVerify` @@ -210,18 +166,19 @@ the TLS connection to the OpenTelemetry Collector accepts any certificate presen ```yaml tab="File (YAML)" tracing: - openTelemetry: - tls: - insecureSkipVerify: true + otlp: + http: + tls: + insecureSkipVerify: true ``` ```toml tab="File (TOML)" -[tracing.openTelemetry.tls] +[tracing.otlp.http.tls] insecureSkipVerify = true ``` ```bash tab="CLI" ---tracing.openTelemetry.tls.insecureSkipVerify=true +--tracing.otlp.http.tls.insecureSkipVerify=true ``` #### gRPC configuration @@ -232,15 +189,168 @@ This instructs the reporter to send spans to the OpenTelemetry Collector using g ```yaml tab="File (YAML)" tracing: - openTelemetry: + otlp: grpc: {} ``` ```toml tab="File (TOML)" [tracing] - [tracing.openTelemetry.grpc] + [tracing.otlp.grpc] ``` ```bash tab="CLI" ---tracing.openTelemetry.grpc=true +--tracing.otlp.grpc=true +``` + +#### `endpoint` + +_Required, Default="localhost:4317", Format="`:`"_ + +Address of the OpenTelemetry Collector to send spans to. + +```yaml tab="File (YAML)" +tracing: + otlp: + grpc: + endpoint: localhost:4317 +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.otlp.grpc] + endpoint = "localhost:4317" +``` + +```bash tab="CLI" +--tracing.otlp.grpc.endpoint=localhost:4317 +``` +#### `insecure` + +_Optional, Default=false_ + +Allows reporter to send spans to the OpenTelemetry Collector without using a secured protocol. + +```yaml tab="File (YAML)" +tracing: + otlp: + grpc: + insecure: true +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.otlp.grpc] + insecure = true +``` + +```bash tab="CLI" +--tracing.otlp.grpc.insecure=true +``` + +#### `tls` + +_Optional_ + +Defines the Client TLS configuration used by the reporter to send spans to the OpenTelemetry Collector. + +##### `ca` + +_Optional_ + +`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector, +it defaults to the system bundle. + +```yaml tab="File (YAML)" +tracing: + otlp: + grpc: + tls: + ca: path/to/ca.crt +``` + +```toml tab="File (TOML)" +[tracing.otlp.grpc.tls] + ca = "path/to/ca.crt" +``` + +```bash tab="CLI" +--tracing.otlp.grpc.tls.ca=path/to/ca.crt +``` + +##### `cert` + +_Optional_ + +`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `key` option is required. + +```yaml tab="File (YAML)" +tracing: + otlp: + grpc: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[tracing.otlp.grpc.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--tracing.otlp.grpc.tls.cert=path/to/foo.cert +--tracing.otlp.grpc.tls.key=path/to/foo.key +``` + +##### `key` + +_Optional_ + +`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `cert` option is required. + +```yaml tab="File (YAML)" +tracing: + otlp: + grpc: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[tracing.otlp.grpc.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--tracing.otlp.grpc.tls.cert=path/to/foo.cert +--tracing.otlp.grpc.tls.key=path/to/foo.key +``` + +##### `insecureSkipVerify` + +_Optional, Default=false_ + +If `insecureSkipVerify` is `true`, +the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. + +```yaml tab="File (YAML)" +tracing: + otlp: + grpc: + tls: + insecureSkipVerify: true +``` + +```toml tab="File (TOML)" +[tracing.otlp.grpc.tls] + insecureSkipVerify = true +``` + +```bash tab="CLI" +--tracing.otlp.grpc.tls.insecureSkipVerify=true ``` diff --git a/docs/content/observability/tracing/overview.md b/docs/content/observability/tracing/overview.md index f4126274a..18b8c4f3e 100644 --- a/docs/content/observability/tracing/overview.md +++ b/docs/content/observability/tracing/overview.md @@ -10,21 +10,13 @@ Visualize the Requests Flow The tracing system allows developers to visualize call flows in their infrastructure. -Traefik uses OpenTracing, an open standard designed for distributed tracing. +Traefik uses [OpenTelemetry](https://opentelemetry.io/ "Link to website of OTel"), an open standard designed for distributed tracing. -Traefik supports seven tracing backends: +Please check our dedicated [OTel docs](./opentelemetry.md) to learn more. -- [Jaeger](./jaeger.md) -- [Zipkin](./zipkin.md) -- [Datadog](./datadog.md) -- [Instana](./instana.md) -- [Haystack](./haystack.md) -- [Elastic](./elastic.md) -- [OpenTelemetry](./opentelemetry.md) ## Configuration -By default, Traefik uses Jaeger as tracing backend. To enable the tracing: @@ -62,25 +54,71 @@ tracing: --tracing.serviceName=traefik ``` -#### `spanNameLimit` +#### `sampleRate` -_Required, Default=0_ +_Optional, Default=1.0_ -Span name limit allows for name truncation in case of very long names. -This can prevent certain tracing providers to drop traces that exceed their length limits. - -`0` means no truncation will occur. +The proportion of requests to trace, specified between 0.0 and 1.0. ```yaml tab="File (YAML)" tracing: - spanNameLimit: 150 + sampleRate: 0.2 ``` ```toml tab="File (TOML)" [tracing] - spanNameLimit = 150 + sampleRate = 0.2 ``` ```bash tab="CLI" ---tracing.spanNameLimit=150 +--tracing.sampleRate=0.2 +``` + +#### `headers` + +_Optional, Default={}_ + +Defines additional headers to be sent with the span's payload. + +```yaml tab="File (YAML)" +tracing: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--tracing.headers.foo=bar --tracing.headers.baz=buz +``` + +#### `globalAttributes` + +_Optional, Default=empty_ + +Applies a list of shared key:value attributes on all spans. + +```yaml tab="File (YAML)" +tracing: + globalAttributes: + attr1: foo + attr2: bar +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.globalAttributes] + attr1 = "foo" + attr2 = "bar" +``` + +```bash tab="CLI" +--tracing.globalAttributes.attr1=foo +--tracing.globalAttributes.attr2=bar ``` diff --git a/docs/content/observability/tracing/zipkin.md b/docs/content/observability/tracing/zipkin.md deleted file mode 100644 index 146dae6e1..000000000 --- a/docs/content/observability/tracing/zipkin.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: "Traefik Zipkin Documentation" -description: "Traefik supports several tracing backends, including Zipkin. Learn how to implement it for observability in Traefik Proxy. Read the technical documentation." ---- - -# Zipkin - -To enable the Zipkin tracer: - -```yaml tab="File (YAML)" -tracing: - zipkin: {} -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.zipkin] -``` - -```bash tab="CLI" ---tracing.zipkin=true -``` - -#### `httpEndpoint` - -_Required, Default="http://localhost:9411/api/v2/spans"_ - -HTTP endpoint used to send data. - -```yaml tab="File (YAML)" -tracing: - zipkin: - httpEndpoint: http://localhost:9411/api/v2/spans -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.zipkin] - httpEndpoint = "http://localhost:9411/api/v2/spans" -``` - -```bash tab="CLI" ---tracing.zipkin.httpEndpoint=http://localhost:9411/api/v2/spans -``` - -#### `sameSpan` - -_Optional, Default=false_ - -Uses SameSpan RPC style traces. - -```yaml tab="File (YAML)" -tracing: - zipkin: - sameSpan: true -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.zipkin] - sameSpan = true -``` - -```bash tab="CLI" ---tracing.zipkin.sameSpan=true -``` - -#### `id128Bit` - -_Optional, Default=true_ - -Uses 128 bits trace IDs. - -```yaml tab="File (YAML)" -tracing: - zipkin: - id128Bit: false -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.zipkin] - id128Bit = false -``` - -```bash tab="CLI" ---tracing.zipkin.id128Bit=false -``` - -#### `sampleRate` - -_Required, Default=1.0_ - -The proportion of requests to trace, specified between 0.0 and 1.0. - -```yaml tab="File (YAML)" -tracing: - zipkin: - sampleRate: 0.2 -``` - -```toml tab="File (TOML)" -[tracing] - [tracing.zipkin] - sampleRate = 0.2 -``` - -```bash tab="CLI" ---tracing.zipkin.sampleRate=0.2 -``` diff --git a/docs/content/operations/dashboard.md b/docs/content/operations/dashboard.md index 5b79dfc98..170d5ebe4 100644 --- a/docs/content/operations/dashboard.md +++ b/docs/content/operations/dashboard.md @@ -71,11 +71,11 @@ with a router attached to the service `api@internal` in the to allow defining: - One or more security features through [middlewares](../middlewares/overview.md) - like authentication ([basicAuth](../middlewares/http/basicauth.md) , [digestAuth](../middlewares/http/digestauth.md), + like authentication ([basicAuth](../middlewares/http/basicauth.md), [digestAuth](../middlewares/http/digestauth.md), [forwardAuth](../middlewares/http/forwardauth.md)) or [allowlisting](../middlewares/http/ipallowlist.md). - A [router rule](#dashboard-router-rule) for accessing the dashboard, - through Traefik itself (sometimes referred as "Traefik-ception"). + through Traefik itself (sometimes referred to as "Traefik-ception"). ### Dashboard Router Rule @@ -83,7 +83,7 @@ As underlined in the [documentation for the `api.dashboard` option](./api.md#das the [router rule](../routing/routers/index.md#rule) defined for Traefik must match the path prefixes `/api` and `/dashboard`. -We recommend to use a "Host Based rule" as ```Host(`traefik.example.com`)``` to match everything on the host domain, +We recommend using a "Host Based rule" as ```Host(`traefik.example.com`)``` to match everything on the host domain, or to make sure that the defined rule captures both prefixes: ```bash tab="Host Rule" diff --git a/docs/content/operations/ping.md b/docs/content/operations/ping.md index 3dc1de3d9..a2b4577f3 100644 --- a/docs/content/operations/ping.md +++ b/docs/content/operations/ping.md @@ -33,7 +33,7 @@ whose default value is `traefik` (port `8080`). | Path | Method | Description | |---------|---------------|-----------------------------------------------------------------------------------------------------| -| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Traefik process liveness. Return a code `200` with the content: `OK` | +| `/ping` | `GET`, `HEAD` | An endpoint to check for Traefik process liveness. Return a code `200` with the content: `OK` | !!! note The `cli` comes with a [`healthcheck`](./cli.md#healthcheck) command which can be used for calling this endpoint. @@ -92,10 +92,11 @@ ping: _Optional, Default=503_ During the period in which Traefik is gracefully shutting down, the ping handler -returns a 503 status code by default. If Traefik is behind e.g. a load-balancer +returns a `503` status code by default. +If Traefik is behind, for example a load-balancer doing health checks (such as the Kubernetes LivenessProbe), another code might -be expected as the signal for graceful termination. In which case, the -terminatingStatusCode can be used to set the code returned by the ping +be expected as the signal for graceful termination. +In that case, the terminatingStatusCode can be used to set the code returned by the ping handler during termination. ```yaml tab="File (YAML)" diff --git a/docs/content/providers/consul-catalog.md b/docs/content/providers/consul-catalog.md index b85dba17f..3c601da31 100644 --- a/docs/content/providers/consul-catalog.md +++ b/docs/content/providers/consul-catalog.md @@ -529,6 +529,13 @@ providers: # ... ``` +??? info "Default rule and Traefik service" + + The exposure of the Traefik container, combined with the default rule mechanism, + can lead to create a router targeting itself in a loop. + In this case, to prevent an infinite loop, + Traefik adds an internal middleware to refuse the request if it comes from the same router. + ### `connectAware` _Optional, Default=false_ diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index 3d6ce6b72..312c1ad26 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -163,7 +163,7 @@ See the [Docker API Access](#docker-api-access) section for more information. services: traefik: - image: traefik:v3.0 # The official v2 Traefik docker image + image: traefik:v3.0 # The official v3 Traefik docker image ports: - "80:80" volumes: @@ -362,6 +362,13 @@ providers: # ... ``` +??? info "Default rule and Traefik service" + + The exposure of the Traefik container, combined with the default rule mechanism, + can lead to create a router targeting itself in a loop. + In this case, to prevent an infinite loop, + Traefik adds an internal middleware to refuse the request if it comes from the same router. + ### `httpClientTimeout` _Optional, Default=0_ diff --git a/docs/content/providers/ecs.md b/docs/content/providers/ecs.md index a7edb5c70..d85c6aae3 100644 --- a/docs/content/providers/ecs.md +++ b/docs/content/providers/ecs.md @@ -287,6 +287,13 @@ providers: # ... ``` +??? info "Default rule and Traefik service" + + The exposure of the Traefik container, combined with the default rule mechanism, + can lead to create a router targeting itself in a loop. + In this case, to prevent an infinite loop, + Traefik adds an internal middleware to refuse the request if it comes from the same router. + ### `refreshSeconds` _Optional, Default=15_ diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index f39f89605..f0ef2da39 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -341,4 +341,4 @@ providers: For additional information, refer to the [full example](../user-guides/crd-acme/index.md) with Let's Encrypt. -{!traefik-api-management-kubernetes.md!} +{!traefik-for-business-applications.md!} diff --git a/docs/content/providers/kubernetes-gateway.md b/docs/content/providers/kubernetes-gateway.md index e8fc7b133..e23fef820 100644 --- a/docs/content/providers/kubernetes-gateway.md +++ b/docs/content/providers/kubernetes-gateway.md @@ -80,17 +80,13 @@ This provider is proposed as an experimental feature and partially supports the The Kubernetes Gateway API project provides several guides on how to use the APIs. These guides can help you to go further than the example above. -The [getting started guide](https://gateway-api.sigs.k8s.io/v1alpha2/guides/) details how to install the CRDs from their repository. - -!!! note "" - - Keep in mind that the Traefik Gateway provider only supports the `v0.4.0` (v1alpha2). +The [getting started guide](https://gateway-api.sigs.k8s.io/guides/) details how to install the CRDs from their repository. For now, the Traefik Gateway Provider can be used while following the below guides: -* [Simple Gateway](https://gateway-api.sigs.k8s.io/v1alpha2/guides/simple-gateway/) -* [HTTP routing](https://gateway-api.sigs.k8s.io/v1alpha2/guides/http-routing/) -* [TLS](https://gateway-api.sigs.k8s.io/v1alpha2/guides/tls/) +* [Simple Gateway](https://gateway-api.sigs.k8s.io/guides/simple-gateway/) +* [HTTP routing](https://gateway-api.sigs.k8s.io/guides/http-routing/) +* [TLS](https://gateway-api.sigs.k8s.io/guides/tls/) ## Resource Configuration @@ -271,4 +267,4 @@ providers: --providers.kubernetesgateway.throttleDuration=10s ``` -{!traefik-api-management-kubernetes.md!} +{!traefik-for-business-applications.md!} diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index 8f03d3317..dc08cde09 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -472,4 +472,4 @@ providers: To learn more about the various aspects of the Ingress specification that Traefik supports, many examples of Ingresses definitions are located in the test [examples](https://github.com/traefik/traefik/tree/v3.0/pkg/provider/kubernetes/ingress/fixtures) of the Traefik repository. -{!traefik-api-management-kubernetes.md!} +{!traefik-for-business-applications.md!} diff --git a/docs/content/providers/nomad.md b/docs/content/providers/nomad.md index 90b3eeb4d..f1d0d1fe1 100644 --- a/docs/content/providers/nomad.md +++ b/docs/content/providers/nomad.md @@ -377,6 +377,13 @@ providers: # ... ``` +??? info "Default rule and Traefik service" + + The exposure of the Traefik container, combined with the default rule mechanism, + can lead to create a router targeting itself in a loop. + In this case, to prevent an infinite loop, + Traefik adds an internal middleware to refuse the request if it comes from the same router. + ### `constraints` _Optional, Default=""_ diff --git a/docs/content/providers/redis.md b/docs/content/providers/redis.md index 505e612b6..309531423 100644 --- a/docs/content/providers/redis.md +++ b/docs/content/providers/redis.md @@ -105,6 +105,28 @@ providers: --providers.redis.password=foo ``` +### `db` + +_Optional, Default=0_ + +Defines the database to be selected after connecting to the Redis. + +```yaml tab="File (YAML)" +providers: + redis: + # ... + db: 0 +``` + +```toml tab="File (TOML)" +[providers.redis] + db = 0 +``` + +```bash tab="CLI" +--providers.redis.db=0 +``` + ### `tls` _Optional_ @@ -207,3 +229,166 @@ providers: ```bash tab="CLI" --providers.redis.tls.insecureSkipVerify=true ``` + +### `sentinel` + +_Optional_ + +Defines the Sentinel configuration used to interact with Redis Sentinel. + +#### `masterName` + +_Required_ + +`masterName` is the name of the Sentinel master. + +```yaml tab="File (YAML)" +providers: + redis: + sentinel: + masterName: my-master +``` + +```toml tab="File (TOML)" +[providers.redis.sentinel] + masterName = "my-master" +``` + +```bash tab="CLI" +--providers.redis.sentinel.masterName=my-master +``` + +#### `username` + +_Optional_ + +`username` is the username for Sentinel authentication. + +```yaml tab="File (YAML)" +providers: + redis: + sentinel: + username: user +``` + +```toml tab="File (TOML)" +[providers.redis.sentinel] + username = "user" +``` + +```bash tab="CLI" +--providers.redis.sentinel.username=user +``` + +#### `password` + +_Optional_ + +`password` is the password for Sentinel authentication. + +```yaml tab="File (YAML)" +providers: + redis: + sentinel: + password: password +``` + +```toml tab="File (TOML)" +[providers.redis.sentinel] + password = "password" +``` + +```bash tab="CLI" +--providers.redis.sentinel.password=password +``` + +#### `latencyStrategy` + +_Optional, Default=false_ + +`latencyStrategy` defines whether to route commands to the closest master or replica nodes +(mutually exclusive with RandomStrategy and ReplicaStrategy). + +```yaml tab="File (YAML)" +providers: + redis: + sentinel: + latencyStrategy: true +``` + +```toml tab="File (TOML)" +[providers.redis.sentinel] +latencyStrategy = true +``` + +```bash tab="CLI" +--providers.redis.sentinel.latencyStrategy=true +``` + +#### `randomStrategy` + +_Optional, Default=false_ + +`randomStrategy` defines whether to route commands randomly to master or replica nodes +(mutually exclusive with LatencyStrategy and ReplicaStrategy). + +```yaml tab="File (YAML)" +providers: + redis: + sentinel: + randomStrategy: true +``` + +```toml tab="File (TOML)" +[providers.redis.sentinel] +randomStrategy = true +``` + +```bash tab="CLI" +--providers.redis.sentinel.randomStrategy=true +``` + +#### `replicaStrategy` + +_Optional, Default=false_ + +`replicaStrategy` Defines whether to route all commands to replica nodes +(mutually exclusive with LatencyStrategy and RandomStrategy). + +```yaml tab="File (YAML)" +providers: + redis: + sentinel: + replicaStrategy: true +``` + +```toml tab="File (TOML)" +[providers.redis.sentinel] +replicaStrategy = true +``` + +```bash tab="CLI" +--providers.redis.sentinel.replicaStrategy=true +``` + +#### `useDisconnectedReplicas` + +_Optional, Default=false_ + +`useDisconnectedReplicas` defines whether to use replicas disconnected with master when cannot get connected replicas. + +```yaml tab="File (YAML)" +providers: + redis: + sentinel: + useDisconnectedReplicas: true +``` + +```toml tab="File (TOML)" +[providers.redis.sentinel] +useDisconnectedReplicas = true +``` + +```bash tab="CLI" +--providers.redis.sentinel.useDisconnectedReplicas=true +``` diff --git a/docs/content/providers/swarm.md b/docs/content/providers/swarm.md index 416583281..277b5c557 100644 --- a/docs/content/providers/swarm.md +++ b/docs/content/providers/swarm.md @@ -209,7 +209,7 @@ See the [Docker Swarm API Access](#docker-api-access) section for more informati services: traefik: - image: traefik:v3.0 # The official v2 Traefik docker image + image: traefik:v3.0 # The official v3 Traefik docker image ports: - "80:80" volumes: @@ -407,6 +407,13 @@ providers: # ... ``` +??? info "Default rule and Traefik service" + + The exposure of the Traefik container, combined with the default rule mechanism, + can lead to create a router targeting itself in a loop. + In this case, to prevent an infinite loop, + Traefik adds an internal middleware to refuse the request if it comes from the same router. + ### `refreshSeconds` _Optional, Default=15_ diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index fb497fe41..93a785ff4 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -1,120 +1,133 @@ -- "traefik.http.middlewares.middleware00.addprefix.prefix=foobar" -- "traefik.http.middlewares.middleware01.basicauth.headerfield=foobar" -- "traefik.http.middlewares.middleware01.basicauth.realm=foobar" -- "traefik.http.middlewares.middleware01.basicauth.removeheader=true" -- "traefik.http.middlewares.middleware01.basicauth.users=foobar, foobar" -- "traefik.http.middlewares.middleware01.basicauth.usersfile=foobar" -- "traefik.http.middlewares.middleware02.buffering.maxrequestbodybytes=42" -- "traefik.http.middlewares.middleware02.buffering.maxresponsebodybytes=42" -- "traefik.http.middlewares.middleware02.buffering.memrequestbodybytes=42" -- "traefik.http.middlewares.middleware02.buffering.memresponsebodybytes=42" -- "traefik.http.middlewares.middleware02.buffering.retryexpression=foobar" -- "traefik.http.middlewares.middleware03.chain.middlewares=foobar, foobar" -- "traefik.http.middlewares.middleware04.circuitbreaker.expression=foobar" -- "traefik.http.middlewares.middleware04.circuitbreaker.checkperiod=42s" -- "traefik.http.middlewares.middleware04.circuitbreaker.fallbackduration=42s" -- "traefik.http.middlewares.middleware04.circuitbreaker.recoveryduration=42s" -- "traefik.http.middlewares.middleware05.compress=true" -- "traefik.http.middlewares.middleware05.compress.excludedcontenttypes=foobar, foobar" -- "traefik.http.middlewares.middleware05.compress.minresponsebodybytes=42" -- "traefik.http.middlewares.middleware06.contenttype=true" -- "traefik.http.middlewares.middleware07.digestauth.headerfield=foobar" -- "traefik.http.middlewares.middleware07.digestauth.realm=foobar" -- "traefik.http.middlewares.middleware07.digestauth.removeheader=true" -- "traefik.http.middlewares.middleware07.digestauth.users=foobar, foobar" -- "traefik.http.middlewares.middleware07.digestauth.usersfile=foobar" -- "traefik.http.middlewares.middleware08.errors.query=foobar" -- "traefik.http.middlewares.middleware08.errors.service=foobar" -- "traefik.http.middlewares.middleware08.errors.status=foobar, foobar" -- "traefik.http.middlewares.middleware09.forwardauth.address=foobar" -- "traefik.http.middlewares.middleware09.forwardauth.authresponseheaders=foobar, foobar" -- "traefik.http.middlewares.middleware09.forwardauth.authresponseheadersregex=foobar" -- "traefik.http.middlewares.middleware09.forwardauth.authrequestheaders=foobar, foobar" -- "traefik.http.middlewares.middleware09.forwardauth.tls.ca=foobar" -- "traefik.http.middlewares.middleware09.forwardauth.tls.cert=foobar" -- "traefik.http.middlewares.middleware09.forwardauth.tls.insecureskipverify=true" -- "traefik.http.middlewares.middleware09.forwardauth.tls.key=foobar" -- "traefik.http.middlewares.middleware09.forwardauth.trustforwardheader=true" -- "traefik.http.middlewares.middleware10.headers.accesscontrolallowcredentials=true" -- "traefik.http.middlewares.middleware10.headers.accesscontrolallowheaders=foobar, foobar" -- "traefik.http.middlewares.middleware10.headers.accesscontrolallowmethods=foobar, foobar" -- "traefik.http.middlewares.middleware10.headers.accesscontrolalloworiginlist=foobar, foobar" -- "traefik.http.middlewares.middleware10.headers.accesscontrolalloworiginlistregex=foobar, foobar" -- "traefik.http.middlewares.middleware10.headers.accesscontrolexposeheaders=foobar, foobar" -- "traefik.http.middlewares.middleware10.headers.accesscontrolmaxage=42" -- "traefik.http.middlewares.middleware10.headers.addvaryheader=true" -- "traefik.http.middlewares.middleware10.headers.allowedhosts=foobar, foobar" -- "traefik.http.middlewares.middleware10.headers.browserxssfilter=true" -- "traefik.http.middlewares.middleware10.headers.contentsecuritypolicy=foobar" -- "traefik.http.middlewares.middleware10.headers.contenttypenosniff=true" -- "traefik.http.middlewares.middleware10.headers.custombrowserxssvalue=foobar" -- "traefik.http.middlewares.middleware10.headers.customframeoptionsvalue=foobar" -- "traefik.http.middlewares.middleware10.headers.customrequestheaders.name0=foobar" -- "traefik.http.middlewares.middleware10.headers.customrequestheaders.name1=foobar" -- "traefik.http.middlewares.middleware10.headers.customresponseheaders.name0=foobar" -- "traefik.http.middlewares.middleware10.headers.customresponseheaders.name1=foobar" -- "traefik.http.middlewares.middleware10.headers.forcestsheader=true" -- "traefik.http.middlewares.middleware10.headers.framedeny=true" -- "traefik.http.middlewares.middleware10.headers.hostsproxyheaders=foobar, foobar" -- "traefik.http.middlewares.middleware10.headers.isdevelopment=true" -- "traefik.http.middlewares.middleware10.headers.permissionspolicy=foobar" -- "traefik.http.middlewares.middleware10.headers.publickey=foobar" -- "traefik.http.middlewares.middleware10.headers.referrerpolicy=foobar" -- "traefik.http.middlewares.middleware10.headers.sslproxyheaders.name0=foobar" -- "traefik.http.middlewares.middleware10.headers.sslproxyheaders.name1=foobar" -- "traefik.http.middlewares.middleware10.headers.stsincludesubdomains=true" -- "traefik.http.middlewares.middleware10.headers.stspreload=true" -- "traefik.http.middlewares.middleware10.headers.stsseconds=42" -- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.depth=42" -- "traefik.http.middlewares.middleware11.ipallowlist.ipstrategy.excludedips=foobar, foobar" -- "traefik.http.middlewares.middleware11.ipallowlist.sourcerange=foobar, foobar" -- "traefik.http.middlewares.middleware12.inflightreq.amount=42" -- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.depth=42" -- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar" -- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.requestheadername=foobar" -- "traefik.http.middlewares.middleware12.inflightreq.sourcecriterion.requesthost=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.commonname=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.country=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.domaincomponent=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.locality=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.organization=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.province=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.issuer.serialnumber=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.notafter=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.notbefore=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.sans=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.serialnumber=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.commonname=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.country=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.domaincomponent=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.locality=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.organization=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.organizationalunit=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.province=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.serialnumber=true" -- "traefik.http.middlewares.middleware13.passtlsclientcert.pem=true" -- "traefik.http.middlewares.middleware14.plugin.foobar.foo=bar" -- "traefik.http.middlewares.middleware15.ratelimit.average=42" -- "traefik.http.middlewares.middleware15.ratelimit.burst=42" -- "traefik.http.middlewares.middleware15.ratelimit.period=42" -- "traefik.http.middlewares.middleware15.ratelimit.sourcecriterion.ipstrategy.depth=42" -- "traefik.http.middlewares.middleware15.ratelimit.sourcecriterion.ipstrategy.excludedips=foobar, foobar" -- "traefik.http.middlewares.middleware15.ratelimit.sourcecriterion.requestheadername=foobar" -- "traefik.http.middlewares.middleware15.ratelimit.sourcecriterion.requesthost=true" -- "traefik.http.middlewares.middleware16.redirectregex.permanent=true" -- "traefik.http.middlewares.middleware16.redirectregex.regex=foobar" -- "traefik.http.middlewares.middleware16.redirectregex.replacement=foobar" -- "traefik.http.middlewares.middleware17.redirectscheme.permanent=true" -- "traefik.http.middlewares.middleware17.redirectscheme.port=foobar" -- "traefik.http.middlewares.middleware17.redirectscheme.scheme=foobar" -- "traefik.http.middlewares.middleware18.replacepath.path=foobar" -- "traefik.http.middlewares.middleware19.replacepathregex.regex=foobar" -- "traefik.http.middlewares.middleware19.replacepathregex.replacement=foobar" -- "traefik.http.middlewares.middleware20.retry.attempts=42" -- "traefik.http.middlewares.middleware20.retry.initialinterval=42" -- "traefik.http.middlewares.middleware21.stripprefix.prefixes=foobar, foobar" -- "traefik.http.middlewares.middleware22.stripprefixregex.regex=foobar, foobar" -- "traefik.http.middlewares.middleware23.grpcweb.alloworigins=foobar, foobar" +## CODE GENERATED AUTOMATICALLY +## THIS FILE MUST NOT BE EDITED BY HAND +- "traefik.http.middlewares.middleware01.addprefix.prefix=foobar" +- "traefik.http.middlewares.middleware02.basicauth.headerfield=foobar" +- "traefik.http.middlewares.middleware02.basicauth.realm=foobar" +- "traefik.http.middlewares.middleware02.basicauth.removeheader=true" +- "traefik.http.middlewares.middleware02.basicauth.users=foobar, foobar" +- "traefik.http.middlewares.middleware02.basicauth.usersfile=foobar" +- "traefik.http.middlewares.middleware03.buffering.maxrequestbodybytes=42" +- "traefik.http.middlewares.middleware03.buffering.maxresponsebodybytes=42" +- "traefik.http.middlewares.middleware03.buffering.memrequestbodybytes=42" +- "traefik.http.middlewares.middleware03.buffering.memresponsebodybytes=42" +- "traefik.http.middlewares.middleware03.buffering.retryexpression=foobar" +- "traefik.http.middlewares.middleware04.chain.middlewares=foobar, foobar" +- "traefik.http.middlewares.middleware05.circuitbreaker.checkperiod=42s" +- "traefik.http.middlewares.middleware05.circuitbreaker.expression=foobar" +- "traefik.http.middlewares.middleware05.circuitbreaker.fallbackduration=42s" +- "traefik.http.middlewares.middleware05.circuitbreaker.recoveryduration=42s" +- "traefik.http.middlewares.middleware06.compress=true" +- "traefik.http.middlewares.middleware06.compress.excludedcontenttypes=foobar, foobar" +- "traefik.http.middlewares.middleware06.compress.includedcontenttypes=foobar, foobar" +- "traefik.http.middlewares.middleware06.compress.minresponsebodybytes=42" +- "traefik.http.middlewares.middleware07.contenttype=true" +- "traefik.http.middlewares.middleware08.digestauth.headerfield=foobar" +- "traefik.http.middlewares.middleware08.digestauth.realm=foobar" +- "traefik.http.middlewares.middleware08.digestauth.removeheader=true" +- "traefik.http.middlewares.middleware08.digestauth.users=foobar, foobar" +- "traefik.http.middlewares.middleware08.digestauth.usersfile=foobar" +- "traefik.http.middlewares.middleware09.errors.query=foobar" +- "traefik.http.middlewares.middleware09.errors.service=foobar" +- "traefik.http.middlewares.middleware09.errors.status=foobar, foobar" +- "traefik.http.middlewares.middleware10.forwardauth.addauthcookiestoresponse=foobar, foobar" +- "traefik.http.middlewares.middleware10.forwardauth.address=foobar" +- "traefik.http.middlewares.middleware10.forwardauth.authrequestheaders=foobar, foobar" +- "traefik.http.middlewares.middleware10.forwardauth.authresponseheaders=foobar, foobar" +- "traefik.http.middlewares.middleware10.forwardauth.authresponseheadersregex=foobar" +- "traefik.http.middlewares.middleware10.forwardauth.tls.ca=foobar" +- "traefik.http.middlewares.middleware10.forwardauth.tls.cert=foobar" +- "traefik.http.middlewares.middleware10.forwardauth.tls.insecureskipverify=true" +- "traefik.http.middlewares.middleware10.forwardauth.tls.key=foobar" +- "traefik.http.middlewares.middleware10.forwardauth.trustforwardheader=true" +- "traefik.http.middlewares.middleware11.grpcweb.alloworigins=foobar, foobar" +- "traefik.http.middlewares.middleware12.headers.accesscontrolallowcredentials=true" +- "traefik.http.middlewares.middleware12.headers.accesscontrolallowheaders=foobar, foobar" +- "traefik.http.middlewares.middleware12.headers.accesscontrolallowmethods=foobar, foobar" +- "traefik.http.middlewares.middleware12.headers.accesscontrolalloworiginlist=foobar, foobar" +- "traefik.http.middlewares.middleware12.headers.accesscontrolalloworiginlistregex=foobar, foobar" +- "traefik.http.middlewares.middleware12.headers.accesscontrolexposeheaders=foobar, foobar" +- "traefik.http.middlewares.middleware12.headers.accesscontrolmaxage=42" +- "traefik.http.middlewares.middleware12.headers.addvaryheader=true" +- "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.contenttypenosniff=true" +- "traefik.http.middlewares.middleware12.headers.custombrowserxssvalue=foobar" +- "traefik.http.middlewares.middleware12.headers.customframeoptionsvalue=foobar" +- "traefik.http.middlewares.middleware12.headers.customrequestheaders.name0=foobar" +- "traefik.http.middlewares.middleware12.headers.customrequestheaders.name1=foobar" +- "traefik.http.middlewares.middleware12.headers.customresponseheaders.name0=foobar" +- "traefik.http.middlewares.middleware12.headers.customresponseheaders.name1=foobar" +- "traefik.http.middlewares.middleware12.headers.forcestsheader=true" +- "traefik.http.middlewares.middleware12.headers.framedeny=true" +- "traefik.http.middlewares.middleware12.headers.hostsproxyheaders=foobar, foobar" +- "traefik.http.middlewares.middleware12.headers.isdevelopment=true" +- "traefik.http.middlewares.middleware12.headers.permissionspolicy=foobar" +- "traefik.http.middlewares.middleware12.headers.publickey=foobar" +- "traefik.http.middlewares.middleware12.headers.referrerpolicy=foobar" +- "traefik.http.middlewares.middleware12.headers.sslproxyheaders.name0=foobar" +- "traefik.http.middlewares.middleware12.headers.sslproxyheaders.name1=foobar" +- "traefik.http.middlewares.middleware12.headers.stsincludesubdomains=true" +- "traefik.http.middlewares.middleware12.headers.stspreload=true" +- "traefik.http.middlewares.middleware12.headers.stsseconds=42" +- "traefik.http.middlewares.middleware13.ipallowlist.ipstrategy=true" +- "traefik.http.middlewares.middleware13.ipallowlist.ipstrategy.depth=42" +- "traefik.http.middlewares.middleware13.ipallowlist.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware13.ipallowlist.rejectstatuscode=42" +- "traefik.http.middlewares.middleware13.ipallowlist.sourcerange=foobar, foobar" +- "traefik.http.middlewares.middleware14.ipwhitelist.ipstrategy=true" +- "traefik.http.middlewares.middleware14.ipwhitelist.ipstrategy.depth=42" +- "traefik.http.middlewares.middleware14.ipwhitelist.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware14.ipwhitelist.sourcerange=foobar, foobar" +- "traefik.http.middlewares.middleware15.inflightreq.amount=42" +- "traefik.http.middlewares.middleware15.inflightreq.sourcecriterion.ipstrategy.depth=42" +- "traefik.http.middlewares.middleware15.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware15.inflightreq.sourcecriterion.requestheadername=foobar" +- "traefik.http.middlewares.middleware15.inflightreq.sourcecriterion.requesthost=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.issuer.commonname=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.issuer.country=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.issuer.domaincomponent=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.issuer.locality=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.issuer.organization=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.issuer.province=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.issuer.serialnumber=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.notafter=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.notbefore=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.sans=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.serialnumber=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.subject.commonname=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.subject.country=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.subject.domaincomponent=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.subject.locality=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.subject.organization=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.subject.organizationalunit=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.subject.province=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.info.subject.serialnumber=true" +- "traefik.http.middlewares.middleware16.passtlsclientcert.pem=true" +- "traefik.http.middlewares.middleware17.plugin.pluginconf0.name0=foobar" +- "traefik.http.middlewares.middleware17.plugin.pluginconf0.name1=foobar" +- "traefik.http.middlewares.middleware17.plugin.pluginconf1.name0=foobar" +- "traefik.http.middlewares.middleware17.plugin.pluginconf1.name1=foobar" +- "traefik.http.middlewares.middleware18.ratelimit.average=42" +- "traefik.http.middlewares.middleware18.ratelimit.burst=42" +- "traefik.http.middlewares.middleware18.ratelimit.period=42s" +- "traefik.http.middlewares.middleware18.ratelimit.sourcecriterion.ipstrategy.depth=42" +- "traefik.http.middlewares.middleware18.ratelimit.sourcecriterion.ipstrategy.excludedips=foobar, foobar" +- "traefik.http.middlewares.middleware18.ratelimit.sourcecriterion.requestheadername=foobar" +- "traefik.http.middlewares.middleware18.ratelimit.sourcecriterion.requesthost=true" +- "traefik.http.middlewares.middleware19.redirectregex.permanent=true" +- "traefik.http.middlewares.middleware19.redirectregex.regex=foobar" +- "traefik.http.middlewares.middleware19.redirectregex.replacement=foobar" +- "traefik.http.middlewares.middleware20.redirectscheme.permanent=true" +- "traefik.http.middlewares.middleware20.redirectscheme.port=foobar" +- "traefik.http.middlewares.middleware20.redirectscheme.scheme=foobar" +- "traefik.http.middlewares.middleware21.replacepath.path=foobar" +- "traefik.http.middlewares.middleware22.replacepathregex.regex=foobar" +- "traefik.http.middlewares.middleware22.replacepathregex.replacement=foobar" +- "traefik.http.middlewares.middleware23.retry.attempts=42" +- "traefik.http.middlewares.middleware23.retry.initialinterval=42s" +- "traefik.http.middlewares.middleware24.stripprefix.prefixes=foobar, foobar" +- "traefik.http.middlewares.middleware25.stripprefixregex.regex=foobar, foobar" - "traefik.http.routers.router0.entrypoints=foobar, foobar" - "traefik.http.routers.router0.middlewares=foobar, foobar" - "traefik.http.routers.router0.priority=42" @@ -139,34 +152,37 @@ - "traefik.http.routers.router1.tls.domains[1].main=foobar" - "traefik.http.routers.router1.tls.domains[1].sans=foobar, foobar" - "traefik.http.routers.router1.tls.options=foobar" -- "traefik.http.services.service01.loadbalancer.healthcheck.followredirects=true" -- "traefik.http.services.service01.loadbalancer.healthcheck.headers.name0=foobar" -- "traefik.http.services.service01.loadbalancer.healthcheck.headers.name1=foobar" -- "traefik.http.services.service01.loadbalancer.healthcheck.hostname=foobar" -- "traefik.http.services.service01.loadbalancer.healthcheck.interval=foobar" -- "traefik.http.services.service01.loadbalancer.healthcheck.path=foobar" -- "traefik.http.services.service01.loadbalancer.healthcheck.method=foobar" -- "traefik.http.services.service01.loadbalancer.healthcheck.status=42" -- "traefik.http.services.service01.loadbalancer.healthcheck.port=42" -- "traefik.http.services.service01.loadbalancer.healthcheck.scheme=foobar" -- "traefik.http.services.service01.loadbalancer.healthcheck.mode=foobar" -- "traefik.http.services.service01.loadbalancer.healthcheck.timeout=foobar" -- "traefik.http.services.service01.loadbalancer.passhostheader=true" -- "traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval=foobar" -- "traefik.http.services.service01.loadbalancer.serverstransport=foobar" -- "traefik.http.services.service01.loadbalancer.sticky.cookie=true" -- "traefik.http.services.service01.loadbalancer.sticky.cookie.httponly=true" -- "traefik.http.services.service01.loadbalancer.sticky.cookie.name=foobar" -- "traefik.http.services.service01.loadbalancer.sticky.cookie.samesite=foobar" -- "traefik.http.services.service01.loadbalancer.sticky.cookie.secure=true" -- "traefik.http.services.service01.loadbalancer.server.port=foobar" -- "traefik.http.services.service01.loadbalancer.server.scheme=foobar" -- "traefik.tcp.middlewares.tcpmiddleware00.ipallowlist.sourcerange=foobar, foobar" -- "traefik.tcp.middlewares.tcpmiddleware01.inflightconn.amount=42" +- "traefik.http.services.service02.loadbalancer.healthcheck.followredirects=true" +- "traefik.http.services.service02.loadbalancer.healthcheck.headers.name0=foobar" +- "traefik.http.services.service02.loadbalancer.healthcheck.headers.name1=foobar" +- "traefik.http.services.service02.loadbalancer.healthcheck.hostname=foobar" +- "traefik.http.services.service02.loadbalancer.healthcheck.interval=42s" +- "traefik.http.services.service02.loadbalancer.healthcheck.method=foobar" +- "traefik.http.services.service02.loadbalancer.healthcheck.mode=foobar" +- "traefik.http.services.service02.loadbalancer.healthcheck.path=foobar" +- "traefik.http.services.service02.loadbalancer.healthcheck.port=42" +- "traefik.http.services.service02.loadbalancer.healthcheck.scheme=foobar" +- "traefik.http.services.service02.loadbalancer.healthcheck.status=42" +- "traefik.http.services.service02.loadbalancer.healthcheck.timeout=42s" +- "traefik.http.services.service02.loadbalancer.passhostheader=true" +- "traefik.http.services.service02.loadbalancer.responseforwarding.flushinterval=42s" +- "traefik.http.services.service02.loadbalancer.serverstransport=foobar" +- "traefik.http.services.service02.loadbalancer.sticky=true" +- "traefik.http.services.service02.loadbalancer.sticky.cookie=true" +- "traefik.http.services.service02.loadbalancer.sticky.cookie.httponly=true" +- "traefik.http.services.service02.loadbalancer.sticky.cookie.maxage=42" +- "traefik.http.services.service02.loadbalancer.sticky.cookie.name=foobar" +- "traefik.http.services.service02.loadbalancer.sticky.cookie.samesite=foobar" +- "traefik.http.services.service02.loadbalancer.sticky.cookie.secure=true" +- "traefik.http.services.service02.loadbalancer.server.port=foobar" +- "traefik.http.services.service02.loadbalancer.server.scheme=foobar" +- "traefik.tcp.middlewares.tcpmiddleware01.ipallowlist.sourcerange=foobar, foobar" +- "traefik.tcp.middlewares.tcpmiddleware02.ipwhitelist.sourcerange=foobar, foobar" +- "traefik.tcp.middlewares.tcpmiddleware03.inflightconn.amount=42" - "traefik.tcp.routers.tcprouter0.entrypoints=foobar, foobar" - "traefik.tcp.routers.tcprouter0.middlewares=foobar, foobar" -- "traefik.tcp.routers.tcprouter0.rule=foobar" - "traefik.tcp.routers.tcprouter0.priority=42" +- "traefik.tcp.routers.tcprouter0.rule=foobar" - "traefik.tcp.routers.tcprouter0.service=foobar" - "traefik.tcp.routers.tcprouter0.tls=true" - "traefik.tcp.routers.tcprouter0.tls.certresolver=foobar" @@ -178,8 +194,8 @@ - "traefik.tcp.routers.tcprouter0.tls.passthrough=true" - "traefik.tcp.routers.tcprouter1.entrypoints=foobar, foobar" - "traefik.tcp.routers.tcprouter1.middlewares=foobar, foobar" -- "traefik.tcp.routers.tcprouter1.rule=foobar" - "traefik.tcp.routers.tcprouter1.priority=42" +- "traefik.tcp.routers.tcprouter1.rule=foobar" - "traefik.tcp.routers.tcprouter1.service=foobar" - "traefik.tcp.routers.tcprouter1.tls=true" - "traefik.tcp.routers.tcprouter1.tls.certresolver=foobar" @@ -189,22 +205,13 @@ - "traefik.tcp.routers.tcprouter1.tls.domains[1].sans=foobar, foobar" - "traefik.tcp.routers.tcprouter1.tls.options=foobar" - "traefik.tcp.routers.tcprouter1.tls.passthrough=true" +- "traefik.tcp.services.tcpservice01.loadbalancer.proxyprotocol=true" - "traefik.tcp.services.tcpservice01.loadbalancer.proxyprotocol.version=42" +- "traefik.tcp.services.tcpservice01.loadbalancer.serverstransport=foobar" - "traefik.tcp.services.tcpservice01.loadbalancer.server.port=foobar" - "traefik.tcp.services.tcpservice01.loadbalancer.server.tls=true" -- "traefik.tcp.services.tcpservice01.loadbalancer.serverstransport=foobar" - "traefik.udp.routers.udprouter0.entrypoints=foobar, foobar" - "traefik.udp.routers.udprouter0.service=foobar" - "traefik.udp.routers.udprouter1.entrypoints=foobar, foobar" - "traefik.udp.routers.udprouter1.service=foobar" - "traefik.udp.services.udpservice01.loadbalancer.server.port=foobar" -- "traefik.tls.stores.Store0.defaultcertificate.certfile=foobar" -- "traefik.tls.stores.Store0.defaultcertificate.keyfile=foobar" -- "traefik.tls.stores.Store0.defaultgeneratedcert.domain.main=foobar" -- "traefik.tls.stores.Store0.defaultgeneratedcert.domain.sans=foobar, foobar" -- "traefik.tls.stores.Store0.defaultgeneratedcert.resolver=foobar" -- "traefik.tls.stores.Store1.defaultcertificate.certfile=foobar" -- "traefik.tls.stores.Store1.defaultcertificate.keyfile=foobar" -- "traefik.tls.stores.Store1.defaultgeneratedcert.domain.main=foobar" -- "traefik.tls.stores.Store1.defaultgeneratedcert.domain.sans=foobar, foobar" -- "traefik.tls.stores.Store1.defaultgeneratedcert.resolver=foobar" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 2e72be2aa..a8264d2ed 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -1,3 +1,5 @@ +## CODE GENERATED AUTOMATICALLY +## THIS FILE MUST NOT BE EDITED BY HAND [http] [http.routers] [http.routers.Router0] @@ -36,22 +38,28 @@ sans = ["foobar", "foobar"] [http.services] [http.services.Service01] - [http.services.Service01.loadBalancer] + [http.services.Service01.failover] + service = "foobar" + fallback = "foobar" + [http.services.Service01.failover.healthCheck] + [http.services.Service02] + [http.services.Service02.loadBalancer] passHostHeader = true serversTransport = "foobar" - [http.services.Service01.loadBalancer.sticky] - [http.services.Service01.loadBalancer.sticky.cookie] + [http.services.Service02.loadBalancer.sticky] + [http.services.Service02.loadBalancer.sticky.cookie] name = "foobar" secure = true httpOnly = true sameSite = "foobar" + maxAge = 42 - [[http.services.Service01.loadBalancer.servers]] + [[http.services.Service02.loadBalancer.servers]] url = "foobar" - [[http.services.Service01.loadBalancer.servers]] + [[http.services.Service02.loadBalancer.servers]] url = "foobar" - [http.services.Service01.loadBalancer.healthCheck] + [http.services.Service02.loadBalancer.healthCheck] scheme = "foobar" mode = "foobar" path = "foobar" @@ -62,107 +70,106 @@ timeout = "42s" hostname = "foobar" followRedirects = true - [http.services.Service01.loadBalancer.healthCheck.headers] + [http.services.Service02.loadBalancer.healthCheck.headers] name0 = "foobar" name1 = "foobar" - [http.services.Service01.loadBalancer.responseForwarding] + [http.services.Service02.loadBalancer.responseForwarding] flushInterval = "42s" - [http.services.Service02] - [http.services.Service02.mirroring] + [http.services.Service03] + [http.services.Service03.mirroring] service = "foobar" maxBodySize = 42 - [http.services.Service02.mirroring.healthCheck] - - [[http.services.Service02.mirroring.mirrors]] + [[http.services.Service03.mirroring.mirrors]] name = "foobar" percent = 42 - [[http.services.Service02.mirroring.mirrors]] + [[http.services.Service03.mirroring.mirrors]] name = "foobar" percent = 42 - [http.services.Service03] - [http.services.Service03.weighted] - [http.services.Service03.weighted.healthCheck] + [http.services.Service03.mirroring.healthCheck] + [http.services.Service04] + [http.services.Service04.weighted] - [[http.services.Service03.weighted.services]] + [[http.services.Service04.weighted.services]] name = "foobar" weight = 42 - [[http.services.Service03.weighted.services]] + [[http.services.Service04.weighted.services]] name = "foobar" weight = 42 - [http.services.Service03.weighted.sticky] - [http.services.Service03.weighted.sticky.cookie] + [http.services.Service04.weighted.sticky] + [http.services.Service04.weighted.sticky.cookie] name = "foobar" secure = true httpOnly = true sameSite = "foobar" - [http.services.Service04] - [http.services.Service04.failover] - service = "foobar" - fallback = "foobar" - - [http.services.Service04.failover.healthCheck] + maxAge = 42 + [http.services.Service04.weighted.healthCheck] [http.middlewares] - [http.middlewares.Middleware00] - [http.middlewares.Middleware00.addPrefix] - prefix = "foobar" [http.middlewares.Middleware01] - [http.middlewares.Middleware01.basicAuth] + [http.middlewares.Middleware01.addPrefix] + prefix = "foobar" + [http.middlewares.Middleware02] + [http.middlewares.Middleware02.basicAuth] users = ["foobar", "foobar"] usersFile = "foobar" realm = "foobar" removeHeader = true headerField = "foobar" - [http.middlewares.Middleware02] - [http.middlewares.Middleware02.buffering] + [http.middlewares.Middleware03] + [http.middlewares.Middleware03.buffering] maxRequestBodyBytes = 42 memRequestBodyBytes = 42 maxResponseBodyBytes = 42 memResponseBodyBytes = 42 retryExpression = "foobar" - [http.middlewares.Middleware03] - [http.middlewares.Middleware03.chain] - middlewares = ["foobar", "foobar"] [http.middlewares.Middleware04] - [http.middlewares.Middleware04.circuitBreaker] + [http.middlewares.Middleware04.chain] + middlewares = ["foobar", "foobar"] + [http.middlewares.Middleware05] + [http.middlewares.Middleware05.circuitBreaker] expression = "foobar" checkPeriod = "42s" fallbackDuration = "42s" recoveryDuration = "42s" - [http.middlewares.Middleware05] - [http.middlewares.Middleware05.compress] - excludedContentTypes = ["foobar", "foobar"] - minResponseBodyBytes = 42 [http.middlewares.Middleware06] - [http.middlewares.Middleware06.contentType] + [http.middlewares.Middleware06.compress] + excludedContentTypes = ["foobar", "foobar"] + includedContentTypes = ["foobar", "foobar"] + minResponseBodyBytes = 42 [http.middlewares.Middleware07] - [http.middlewares.Middleware07.digestAuth] + [http.middlewares.Middleware07.contentType] + [http.middlewares.Middleware08] + [http.middlewares.Middleware08.digestAuth] users = ["foobar", "foobar"] usersFile = "foobar" removeHeader = true realm = "foobar" headerField = "foobar" - [http.middlewares.Middleware08] - [http.middlewares.Middleware08.errors] + [http.middlewares.Middleware09] + [http.middlewares.Middleware09.errors] status = ["foobar", "foobar"] service = "foobar" query = "foobar" - [http.middlewares.Middleware09] - [http.middlewares.Middleware09.forwardAuth] + [http.middlewares.Middleware10] + [http.middlewares.Middleware10.forwardAuth] address = "foobar" trustForwardHeader = true authResponseHeaders = ["foobar", "foobar"] authResponseHeadersRegex = "foobar" authRequestHeaders = ["foobar", "foobar"] - [http.middlewares.Middleware09.forwardAuth.tls] + addAuthCookiesToResponse = ["foobar", "foobar"] + [http.middlewares.Middleware10.forwardAuth.tls] ca = "foobar" cert = "foobar" key = "foobar" insecureSkipVerify = true - [http.middlewares.Middleware10] - [http.middlewares.Middleware10.headers] + [http.middlewares.Middleware11] + [http.middlewares.Middleware11.grpcWeb] + allowOrigins = ["foobar", "foobar"] + [http.middlewares.Middleware12] + [http.middlewares.Middleware12.headers] accessControlAllowCredentials = true accessControlAllowHeaders = ["foobar", "foobar"] accessControlAllowMethods = ["foobar", "foobar"] @@ -187,39 +194,46 @@ referrerPolicy = "foobar" permissionsPolicy = "foobar" isDevelopment = true - [http.middlewares.Middleware10.headers.customRequestHeaders] + [http.middlewares.Middleware12.headers.customRequestHeaders] name0 = "foobar" name1 = "foobar" - [http.middlewares.Middleware10.headers.customResponseHeaders] + [http.middlewares.Middleware12.headers.customResponseHeaders] name0 = "foobar" name1 = "foobar" - [http.middlewares.Middleware10.headers.sslProxyHeaders] + [http.middlewares.Middleware12.headers.sslProxyHeaders] name0 = "foobar" name1 = "foobar" - [http.middlewares.Middleware11] - [http.middlewares.Middleware11.ipAllowList] + [http.middlewares.Middleware13] + [http.middlewares.Middleware13.ipAllowList] sourceRange = ["foobar", "foobar"] - [http.middlewares.Middleware11.ipAllowList.ipStrategy] + rejectStatusCode = 42 + [http.middlewares.Middleware13.ipAllowList.ipStrategy] depth = 42 excludedIPs = ["foobar", "foobar"] - [http.middlewares.Middleware12] - [http.middlewares.Middleware12.inFlightReq] + [http.middlewares.Middleware14] + [http.middlewares.Middleware14.ipWhiteList] + sourceRange = ["foobar", "foobar"] + [http.middlewares.Middleware14.ipWhiteList.ipStrategy] + depth = 42 + excludedIPs = ["foobar", "foobar"] + [http.middlewares.Middleware15] + [http.middlewares.Middleware15.inFlightReq] amount = 42 - [http.middlewares.Middleware12.inFlightReq.sourceCriterion] + [http.middlewares.Middleware15.inFlightReq.sourceCriterion] requestHeaderName = "foobar" requestHost = true - [http.middlewares.Middleware12.inFlightReq.sourceCriterion.ipStrategy] + [http.middlewares.Middleware15.inFlightReq.sourceCriterion.ipStrategy] depth = 42 excludedIPs = ["foobar", "foobar"] - [http.middlewares.Middleware13] - [http.middlewares.Middleware13.passTLSClientCert] + [http.middlewares.Middleware16] + [http.middlewares.Middleware16.passTLSClientCert] pem = true - [http.middlewares.Middleware13.passTLSClientCert.info] + [http.middlewares.Middleware16.passTLSClientCert.info] notAfter = true notBefore = true sans = true serialNumber = true - [http.middlewares.Middleware13.passTLSClientCert.info.subject] + [http.middlewares.Middleware16.passTLSClientCert.info.subject] country = true province = true locality = true @@ -228,7 +242,7 @@ commonName = true serialNumber = true domainComponent = true - [http.middlewares.Middleware13.passTLSClientCert.info.issuer] + [http.middlewares.Middleware16.passTLSClientCert.info.issuer] country = true province = true locality = true @@ -236,51 +250,52 @@ commonName = true serialNumber = true domainComponent = true - [http.middlewares.Middleware14] - [http.middlewares.Middleware14.plugin] - [http.middlewares.Middleware14.plugin.PluginConf] - foo = "bar" - [http.middlewares.Middleware15] - [http.middlewares.Middleware15.rateLimit] + [http.middlewares.Middleware17] + [http.middlewares.Middleware17.plugin] + [http.middlewares.Middleware17.plugin.PluginConf0] + name0 = "foobar" + name1 = "foobar" + [http.middlewares.Middleware17.plugin.PluginConf1] + name0 = "foobar" + name1 = "foobar" + [http.middlewares.Middleware18] + [http.middlewares.Middleware18.rateLimit] average = 42 period = "42s" burst = 42 - [http.middlewares.Middleware15.rateLimit.sourceCriterion] + [http.middlewares.Middleware18.rateLimit.sourceCriterion] requestHeaderName = "foobar" requestHost = true - [http.middlewares.Middleware15.rateLimit.sourceCriterion.ipStrategy] + [http.middlewares.Middleware18.rateLimit.sourceCriterion.ipStrategy] depth = 42 excludedIPs = ["foobar", "foobar"] - [http.middlewares.Middleware16] - [http.middlewares.Middleware16.redirectRegex] + [http.middlewares.Middleware19] + [http.middlewares.Middleware19.redirectRegex] regex = "foobar" replacement = "foobar" permanent = true - [http.middlewares.Middleware17] - [http.middlewares.Middleware17.redirectScheme] + [http.middlewares.Middleware20] + [http.middlewares.Middleware20.redirectScheme] scheme = "foobar" port = "foobar" permanent = true - [http.middlewares.Middleware18] - [http.middlewares.Middleware18.replacePath] + [http.middlewares.Middleware21] + [http.middlewares.Middleware21.replacePath] path = "foobar" - [http.middlewares.Middleware19] - [http.middlewares.Middleware19.replacePathRegex] + [http.middlewares.Middleware22] + [http.middlewares.Middleware22.replacePathRegex] regex = "foobar" replacement = "foobar" - [http.middlewares.Middleware20] - [http.middlewares.Middleware20.retry] + [http.middlewares.Middleware23] + [http.middlewares.Middleware23.retry] attempts = 42 initialInterval = "42s" - [http.middlewares.Middleware21] - [http.middlewares.Middleware21.stripPrefix] + [http.middlewares.Middleware24] + [http.middlewares.Middleware24.stripPrefix] prefixes = ["foobar", "foobar"] - [http.middlewares.Middleware22] - [http.middlewares.Middleware22.stripPrefixRegex] + [http.middlewares.Middleware25] + [http.middlewares.Middleware25.stripPrefixRegex] regex = ["foobar", "foobar"] - [http.middlewares.Middleware23] - [http.middlewares.Middleware23.grpcWeb] - allowOrigins = ["foobar", "foobar"] [http.serversTransports] [http.serversTransports.ServersTransport0] serverName = "foobar" @@ -297,18 +312,15 @@ [[http.serversTransports.ServersTransport0.certificates]] certFile = "foobar" keyFile = "foobar" - [http.serversTransports.ServersTransport0.forwardingTimeouts] dialTimeout = "42s" responseHeaderTimeout = "42s" idleConnTimeout = "42s" readIdleTimeout = "42s" pingTimeout = "42s" - [http.serversTransports.ServersTransport0.spiffe] ids = ["foobar", "foobar"] trustDomain = "foobar" - [http.serversTransports.ServersTransport1] serverName = "foobar" insecureSkipVerify = true @@ -324,14 +336,12 @@ [[http.serversTransports.ServersTransport1.certificates]] certFile = "foobar" keyFile = "foobar" - [http.serversTransports.ServersTransport1.forwardingTimeouts] dialTimeout = "42s" responseHeaderTimeout = "42s" idleConnTimeout = "42s" readIdleTimeout = "42s" pingTimeout = "42s" - [http.serversTransports.ServersTransport1.spiffe] ids = ["foobar", "foobar"] trustDomain = "foobar" @@ -398,21 +408,21 @@ [[tcp.services.TCPService02.weighted.services]] name = "foobar" weight = 42 - [tcp.middlewares] - [tcp.middlewares.TCPMiddleware00] - [tcp.middlewares.TCPMiddleware00.ipAllowList] - sourceRange = ["foobar", "foobar"] [tcp.middlewares.TCPMiddleware01] - [tcp.middlewares.TCPMiddleware01.inFlightConn] + [tcp.middlewares.TCPMiddleware01.ipAllowList] + sourceRange = ["foobar", "foobar"] + [tcp.middlewares.TCPMiddleware02] + [tcp.middlewares.TCPMiddleware02.ipWhiteList] + sourceRange = ["foobar", "foobar"] + [tcp.middlewares.TCPMiddleware03] + [tcp.middlewares.TCPMiddleware03.inFlightConn] amount = 42 - [tcp.serversTransports] [tcp.serversTransports.TCPServersTransport0] - dialTimeout = "42s" dialKeepAlive = "42s" + dialTimeout = "42s" terminationDelay = "42s" - [tcp.serversTransports.TCPServersTransport0.tls] serverName = "foobar" insecureSkipVerify = true @@ -426,16 +436,13 @@ [[tcp.serversTransports.TCPServersTransport0.tls.certificates]] certFile = "foobar" keyFile = "foobar" - - [tcp.serversTransports.TCPServersTransport0.spiffe] - ids = ["foobar", "foobar"] - trustDomain = "foobar" - + [tcp.serversTransports.TCPServersTransport0.tls.spiffe] + ids = ["foobar", "foobar"] + trustDomain = "foobar" [tcp.serversTransports.TCPServersTransport1] - dialTimeout = "42s" dialKeepAlive = "42s" + dialTimeout = "42s" terminationDelay = "42s" - [tcp.serversTransports.TCPServersTransport1.tls] serverName = "foobar" insecureSkipVerify = true @@ -449,10 +456,9 @@ [[tcp.serversTransports.TCPServersTransport1.tls.certificates]] certFile = "foobar" keyFile = "foobar" - - [tcp.serversTransports.TCPServersTransport1.spiffe] - ids = ["foobar", "foobar"] - trustDomain = "foobar" + [tcp.serversTransports.TCPServersTransport1.tls.spiffe] + ids = ["foobar", "foobar"] + trustDomain = "foobar" [udp] [udp.routers] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index e760e19ec..7ae72ad7d 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -1,3 +1,5 @@ +## CODE GENERATED AUTOMATICALLY +## THIS FILE MUST NOT BE EDITED BY HAND http: routers: Router0: @@ -46,6 +48,11 @@ http: - foobar services: Service01: + failover: + service: foobar + fallback: foobar + healthCheck: {} + Service02: loadBalancer: sticky: cookie: @@ -53,6 +60,7 @@ http: secure: true httpOnly: true sameSite: foobar + maxAge: 42 servers: - url: foobar - url: foobar @@ -74,19 +82,18 @@ http: responseForwarding: flushInterval: 42s serversTransport: foobar - Service02: + Service03: mirroring: service: foobar maxBodySize: 42 - healthCheck: {} mirrors: - name: foobar percent: 42 - name: foobar percent: 42 - Service03: - weighted: healthCheck: {} + Service04: + weighted: services: - name: foobar weight: 42 @@ -98,16 +105,13 @@ http: secure: true httpOnly: true sameSite: foobar - Service04: - failover: - service: foobar - fallback: foobar + maxAge: 42 healthCheck: {} middlewares: - Middleware00: + Middleware01: addPrefix: prefix: foobar - Middleware01: + Middleware02: basicAuth: users: - foobar @@ -116,33 +120,36 @@ http: realm: foobar removeHeader: true headerField: foobar - Middleware02: + Middleware03: buffering: maxRequestBodyBytes: 42 memRequestBodyBytes: 42 maxResponseBodyBytes: 42 memResponseBodyBytes: 42 retryExpression: foobar - Middleware03: + Middleware04: chain: middlewares: - foobar - foobar - Middleware04: + Middleware05: circuitBreaker: expression: foobar checkPeriod: 42s fallbackDuration: 42s recoveryDuration: 42s - Middleware05: + Middleware06: compress: excludedContentTypes: - foobar - foobar + includedContentTypes: + - foobar + - foobar minResponseBodyBytes: 42 - Middleware06: - contentType: {} Middleware07: + contentType: {} + Middleware08: digestAuth: users: - foobar @@ -151,14 +158,14 @@ http: removeHeader: true realm: foobar headerField: foobar - Middleware08: + Middleware09: errors: status: - foobar - foobar service: foobar query: foobar - Middleware09: + Middleware10: forwardAuth: address: foobar tls: @@ -174,7 +181,15 @@ http: authRequestHeaders: - foobar - foobar - Middleware10: + addAuthCookiesToResponse: + - foobar + - foobar + Middleware11: + grpcWeb: + allowOrigins: + - foobar + - foobar + Middleware12: headers: customRequestHeaders: name0: foobar @@ -223,7 +238,7 @@ http: referrerPolicy: foobar permissionsPolicy: foobar isDevelopment: true - Middleware11: + Middleware13: ipAllowList: sourceRange: - foobar @@ -233,7 +248,18 @@ http: excludedIPs: - foobar - foobar - Middleware12: + rejectStatusCode: 42 + Middleware14: + ipWhiteList: + sourceRange: + - foobar + - foobar + ipStrategy: + depth: 42 + excludedIPs: + - foobar + - foobar + Middleware15: inFlightReq: amount: 42 sourceCriterion: @@ -244,13 +270,14 @@ http: - foobar requestHeaderName: foobar requestHost: true - Middleware13: + Middleware16: passTLSClientCert: pem: true info: notAfter: true notBefore: true sans: true + serialNumber: true subject: country: true province: true @@ -268,12 +295,15 @@ http: commonName: true serialNumber: true domainComponent: true - serialNumber: true - Middleware14: + Middleware17: plugin: - PluginConf: - foo: bar - Middleware15: + PluginConf0: + name0: foobar + name1: foobar + PluginConf1: + name0: foobar + name1: foobar + Middleware18: rateLimit: average: 42 period: 42s @@ -286,42 +316,37 @@ http: - foobar requestHeaderName: foobar requestHost: true - Middleware16: + Middleware19: redirectRegex: regex: foobar replacement: foobar permanent: true - Middleware17: + Middleware20: redirectScheme: scheme: foobar port: foobar permanent: true - Middleware18: + Middleware21: replacePath: path: foobar - Middleware19: + Middleware22: replacePathRegex: regex: foobar replacement: foobar - Middleware20: + Middleware23: retry: attempts: 42 initialInterval: 42s - Middleware21: + Middleware24: stripPrefix: prefixes: - foobar - foobar - Middleware22: + Middleware25: stripPrefixRegex: regex: - foobar - foobar - Middleware23: - grpcWeb: - allowOrigins: - - foobar - - foobar serversTransports: ServersTransport0: serverName: foobar @@ -348,7 +373,6 @@ http: - foobar - foobar trustDomain: foobar - ServersTransport1: serverName: foobar insecureSkipVerify: true @@ -374,7 +398,6 @@ http: - foobar - foobar trustDomain: foobar - tcp: routers: TCPRouter0: @@ -426,7 +449,6 @@ tcp: services: TCPService01: loadBalancer: - serversTransport: foobar proxyProtocol: version: 42 servers: @@ -434,6 +456,7 @@ tcp: tls: true - address: foobar tls: true + serversTransport: foobar TCPService02: weighted: services: @@ -442,18 +465,23 @@ tcp: - name: foobar weight: 42 middlewares: - TCPMiddleware00: + TCPMiddleware01: ipAllowList: sourceRange: - foobar - foobar - TCPMiddleware01: + TCPMiddleware02: + ipWhiteList: + sourceRange: + - foobar + - foobar + TCPMiddleware03: inFlightConn: amount: 42 serversTransports: TCPServersTransport0: - dialTimeout: 42s dialKeepAlive: 42s + dialTimeout: 42s terminationDelay: 42s tls: serverName: foobar @@ -467,14 +495,14 @@ tcp: - certFile: foobar keyFile: foobar peerCertURI: foobar - spiffe: - ids: - - foobar - - foobar - trustDomain: foobar + spiffe: + ids: + - foobar + - foobar + trustDomain: foobar TCPServersTransport1: - dialTimeout: 42s dialKeepAlive: 42s + dialTimeout: 42s terminationDelay: 42s tls: serverName: foobar @@ -488,11 +516,11 @@ tcp: - certFile: foobar keyFile: foobar peerCertURI: foobar - spiffe: - ids: - - foobar - - foobar - trustDomain: foobar + spiffe: + ids: + - foobar + - foobar + trustDomain: foobar udp: routers: UDPRouter0: diff --git a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_backendtlspolicies.yaml b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_backendtlspolicies.yaml new file mode 100644 index 000000000..bb90c54d0 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_backendtlspolicies.yaml @@ -0,0 +1,281 @@ +# Copyright 2023 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Gateway API Experimental channel install +# +# +# config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: backendtlspolicies.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: BackendTLSPolicy + listKind: BackendTLSPolicyList + plural: backendtlspolicies + shortNames: + - btlspolicy + singular: backendtlspolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: BackendTLSPolicy provides a way to configure how a Gateway connects to a Backend via TLS. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTLSPolicy. + properties: + targetRef: + description: "TargetRef identifies an API object to apply the policy to. Only Services have Extended support. Implementations MAY support additional objects, with Implementation Specific support. Note that this config applies to the entire referenced resource by default, but this default may change in the future to provide a more granular application of the policy. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. When unspecified, the local namespace is inferred. Even when policy targets a resource in a different namespace, it MUST only apply to traffic originating from the same namespace as the policy. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + sectionName: + description: "SectionName is the name of a section within the target resource. When unspecified, this targetRef targets the entire resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name * Service: Port Name \n If a SectionName is specified, but does not exist on the targeted object, the Policy must fail to attach, and the policy implementation should record a `ResolvedRefs` or similar Condition in the Policy's status." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + tls: + description: TLS contains backend TLS policy configuration. + properties: + caCertRefs: + description: "CACertRefs contains one or more references to Kubernetes objects that contain a PEM-encoded TLS CA certificate bundle, which is used to validate a TLS handshake between the Gateway and backend Pod. \n If CACertRefs is empty or unspecified, then WellKnownCACerts must be specified. Only one of CACertRefs or WellKnownCACerts may be specified, not both. If CACertRefs is empty or unspecified, the configuration for WellKnownCACerts MUST be honored instead. \n References to a resource in a different namespace are invalid for the moment, although we will revisit this in the future. \n A single CACertRef to a Kubernetes ConfigMap kind has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a backend, but this behavior is implementation-specific. \n Support: Core - An optional single reference to a Kubernetes ConfigMap, with the CA certificate in a key named `ca.crt`. \n Support: Implementation-specific (More than one reference, or other kinds of resources)." + items: + description: "LocalObjectReference identifies an API object within the namespace of the referrer. The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + hostname: + description: "Hostname is used for two purposes in the connection between Gateways and backends: \n 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). 2. Hostname MUST be used for authentication and MUST match the certificate served by the matching backend. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + wellKnownCACerts: + description: "WellKnownCACerts specifies whether system CA certificates may be used in the TLS handshake between the gateway and backend pod. \n If WellKnownCACerts is unspecified or empty (\"\"), then CACertRefs must be specified with at least one entry for a valid configuration. Only one of CACertRefs or WellKnownCACerts may be specified, not both. \n Support: Core for \"System\"" + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertRefs and WellKnownCACerts + rule: '!(has(self.caCertRefs) && size(self.caCertRefs) > 0 && has(self.wellKnownCACerts) && self.wellKnownCACerts != "")' + - message: must specify either CACertRefs or WellKnownCACerts + rule: (has(self.caCertRefs) && size(self.caCertRefs) > 0 || has(self.wellKnownCACerts) && self.wellKnownCACerts != "") + required: + - targetRef + - tls + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: "Ancestors is a list of ancestor resources (usually Gateways) that are associated with the policy, and the status of the policy with respect to each ancestor. When this policy attaches to a parent, the controller that manages the parent and the ancestors MUST add an entry to this list when the controller first sees the policy and SHOULD update the entry as appropriate when the relevant ancestor is modified. \n Note that choosing the relevant ancestor is left to the Policy designers; an important part of Policy design is designing the right object level at which to namespace this status. \n Note also that implementations MUST ONLY populate ancestor status for the Ancestor resources they are responsible for. Implementations MUST use the ControllerName field to uniquely identify the entries in this list that they are responsible for. \n Note that to achieve this, the list of PolicyAncestorStatus structs MUST be treated as a map with a composite key, made up of the AncestorRef and ControllerName fields combined. \n A maximum of 16 ancestors will be represented in this list. An empty list means the Policy is not relevant for any ancestors. \n If this slice is full, implementations MUST NOT add further entries. Instead they MUST consider the policy unimplementable and signal that on any related resources such as the ancestor that would be referenced here. For example, if this list was full on BackendTLSPolicy, no additional Gateways would be able to reference the Service targeted by the BackendTLSPolicy." + items: + description: "PolicyAncestorStatus describes the status of a route with respect to an associated Ancestor. \n Ancestors refer to objects that are either the Target of a policy or above it in terms of object hierarchy. For example, if a policy targets a Service, the Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most useful object to place Policy status on, so we recommend that implementations SHOULD use Gateway as the PolicyAncestorStatus object unless the designers have a _very_ good reason otherwise. \n In the context of policy attachment, the Ancestor is used to distinguish which resource results in a distinct application of this policy. For example, if a policy targets a Service, it may have a distinct result per attached Gateway. \n Policies targeting the same resource may have different effects depending on the ancestors of those resources. For example, different Gateways targeting the same Service may have different capabilities, especially if they have different underlying implementations. \n For example, in BackendTLSPolicy, the Policy attaches to a Service that is used as a backend in a HTTPRoute that is itself attached to a Gateway. In this case, the relevant object for status is the Gateway, and that is the ancestor object referred to in this status. \n Note that a parent is also an ancestor, so for objects where the parent is the relevant object for status, this struct SHOULD still be used. \n This struct is intended to be used in a slice that's effectively a map, with a composite key made up of the AncestorRef and the ControllerName." + properties: + ancestorRef: + description: AncestorRef corresponds with a ParentRef in the spec that this PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with respect to the given Ancestor. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_gatewayclasses.yaml b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_gatewayclasses.yaml index cac5b0ee7..7ebf7c706 100644 --- a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_gatewayclasses.yaml +++ b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_gatewayclasses.yaml @@ -1,226 +1,381 @@ - ---- +# +# config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: gatewayclasses.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io names: categories: - - gateway-api + - gateway-api kind: GatewayClass listKind: GatewayClassList plural: gatewayclasses shortNames: - - gc + - gc singular: gatewayclass scope: Cluster versions: - - additionalPrinterColumns: - - jsonPath: .spec.controller - name: Controller - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.description - name: Description - priority: 1 - type: string - name: v1alpha2 - schema: - openAPIV3Schema: - description: "GatewayClass describes a class of Gateways available to the - user for creating Gateway resources. \n It is recommended that this resource - be used as a template for Gateways. This means that a Gateway is based on - the state of the GatewayClass at the time it was created and changes to - the GatewayClass or associated parameters are not propagated down to existing - Gateways. This recommendation is intended to limit the blast radius of changes - to GatewayClass or associated parameters. If implementations choose to propagate - GatewayClass changes to existing Gateways, that MUST be clearly documented - by the implementation. \n Whenever one or more Gateways are using a GatewayClass, - implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` - finalizer on the associated GatewayClass. This ensures that a GatewayClass - associated with a Gateway is not deleted while in use. \n GatewayClass is - a Cluster level resource." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GatewayClass. - properties: - controllerName: - description: "ControllerName is the name of the controller that is - managing Gateways of this class. The value of this field MUST be - a domain prefixed path. \n Example: \"example.net/gateway-controller\". - \n This field is not mutable and cannot be empty. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - description: - description: Description helps describe a GatewayClass with more details. - maxLength: 64 - type: string - parametersRef: - description: "ParametersRef is a reference to a resource that contains - the configuration parameters corresponding to the GatewayClass. - This is optional if the controller does not require any additional - configuration. \n ParametersRef can reference a standard Kubernetes - resource, i.e. ConfigMap, or an implementation-specific custom resource. - The resource can be cluster-scoped or namespace-scoped. \n If the - referent cannot be found, the GatewayClass's \"InvalidParameters\" - status condition will be true. \n Support: Custom" - properties: - group: - description: Group is the group of the referent. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace is the namespace of the referent. This - field is required when referring to a Namespace-scoped resource - and MUST be unset when referring to a Cluster-scoped resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - group - - kind - - name - type: object - required: - - controllerName - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: Status defines the current state of GatewayClass. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Waiting - status: Unknown - type: Accepted - description: "Conditions is the current status from the controller - for this GatewayClass. \n Controllers should prefer to publish conditions - using values of GatewayClassConditionType for the type of each Condition." - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations SHOULD add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 + kind: + description: Kind is kind of the referent. + maxLength: 63 minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + namespace: + description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string required: - - lastTransitionTime - - message - - reason - - status - - type + - group + - kind + - name type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: "Status defines the current state of GatewayClass. \n Implementations MUST populate status on all GatewayClass resources which specify their controller name." + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: 'SupportedFeatures is the set of features the GatewayClass support. It MUST be sorted in ascending alphabetical order. ' + items: + description: SupportedFeature is used to describe distinct features that are covered by conformance tests. + enum: + - Gateway + - GatewayPort8080 + - GatewayStaticAddresses + - HTTPRoute + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - Mesh + - ReferenceGrant + - TLSRoute + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations SHOULD add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: "Status defines the current state of GatewayClass. \n Implementations MUST populate status on all GatewayClass resources which specify their controller name." + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: 'SupportedFeatures is the set of features the GatewayClass support. It MUST be sorted in ascending alphabetical order. ' + items: + description: SupportedFeature is used to describe distinct features that are covered by conformance tests. + enum: + - Gateway + - GatewayPort8080 + - GatewayStaticAddresses + - HTTPRoute + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - Mesh + - ReferenceGrant + - TLSRoute + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_gateways.yaml b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_gateways.yaml index 486471336..2a4b26f58 100644 --- a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_gateways.yaml +++ b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_gateways.yaml @@ -1,719 +1,1037 @@ - ---- +# +# config/crd/experimental/gateway.networking.k8s.io_gateways.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: gateways.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io names: categories: - - gateway-api + - gateway-api kind: Gateway listKind: GatewayList plural: gateways shortNames: - - gtw + - gtw singular: gateway scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.gatewayClassName - name: Class - type: string - - jsonPath: .status.addresses[*].value - name: Address - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: Gateway represents an instance of a service-traffic handling - infrastructure by binding Listeners to a set of IP addresses. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of Gateway. - properties: - addresses: - description: "Addresses requested for this Gateway. This is optional - and behavior can depend on the implementation. If a value is set - in the spec and the requested address is invalid or unavailable, - the implementation MUST indicate this in the associated entry in - GatewayStatus.Addresses. \n The Addresses field represents a request - for the address(es) on the \"outside of the Gateway\", that traffic - bound for this Gateway will use. This could be the IP address or - hostname of an external load balancer or other networking infrastructure, - or some other address that traffic will be sent to. \n The .listener.hostname - field is used to route traffic that has already arrived at the Gateway - to the correct in-cluster destination. \n If no Addresses are specified, - the implementation MAY schedule the Gateway in an implementation-specific - manner, assigning an appropriate set of Addresses. \n The implementation - MUST bind all Listeners to every GatewayAddress that it assigns - to the Gateway and add a corresponding entry in GatewayStatus.Addresses. - \n Support: Core" - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - enum: - - IPAddress - - Hostname - - NamedAddress - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - gatewayClassName: - description: GatewayClassName used for this Gateway. This is the name - of a GatewayClass resource. - maxLength: 253 - minLength: 1 - type: string - listeners: - description: "Listeners associated with this Gateway. Listeners define - logical endpoints that are bound on this Gateway's addresses. At - least one Listener MUST be specified. \n Each listener in a Gateway - must have a unique combination of Hostname, Port, and Protocol. - \n An implementation MAY group Listeners by Port and then collapse - each group of Listeners into a single Listener if the implementation - determines that the Listeners in the group are \"compatible\". An - implementation MAY also group together and collapse compatible Listeners - belonging to different Gateways. \n For example, an implementation - might consider Listeners to be compatible with each other if all - of the following conditions are met: \n 1. Either each Listener - within the group specifies the \"HTTP\" Protocol or each Listener - within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. - \n 2. Each Listener within the group specifies a Hostname that is - unique within the group. \n 3. As a special case, one Listener - within a group may omit Hostname, in which case this Listener - matches when no other Listener matches. \n If the implementation - does collapse compatible Listeners, the hostname provided in the - incoming client request MUST be matched to a Listener to find the - correct set of Routes. The incoming hostname MUST be matched using - the Hostname field for each Listener in order of most to least specific. - That is, exact matches must be processed before wildcard matches. - \n If this field specifies multiple Listeners that have the same - Port value but are not compatible, the implementation must raise - a \"Conflicted\" condition in the Listener status. \n Support: Core" - items: - description: Listener embodies the concept of a logical endpoint - where a Gateway accepts network connections. - properties: - allowedRoutes: - default: - namespaces: - from: Same - description: "AllowedRoutes defines the types of routes that - MAY be attached to a Listener and the trusted namespaces where - those Route resources MAY be present. \n Although a client - request may match multiple route rules, only one rule may - ultimately receive the request. Matching precedence MUST be - determined in order of the following criteria: \n * The most - specific match as defined by the Route type. * The oldest - Route based on creation timestamp. For example, a Route with - \ a creation timestamp of \"2020-09-08 01:02:03\" is given - precedence over a Route with a creation timestamp of \"2020-09-08 - 01:02:04\". * If everything else is equivalent, the Route - appearing first in alphabetical order (namespace/name) should - be given precedence. For example, foo/bar is given precedence - over foo/baz. \n All valid rules within a Route attached to - this Listener should be implemented. Invalid Route rules can - be ignored (sometimes that will mean the full Route). If a - Route rule transitions from valid to invalid, support for - that Route rule should be dropped to ensure consistency. For - example, even if a filter specified by a Route rule is invalid, - the rest of the rules within that Route should still be supported. - \n Support: Core" - properties: - kinds: - description: "Kinds specifies the groups and kinds of Routes - that are allowed to bind to this Gateway Listener. When - unspecified or empty, the kinds of Routes selected are - determined using the Listener protocol. \n A RouteGroupKind - MUST correspond to kinds of Routes that are compatible - with the application protocol specified in the Listener's - Protocol field. If an implementation does not support - or recognize this resource type, it MUST set the \"ResolvedRefs\" - condition to False for this Listener with the \"InvalidRoutesRef\" - reason. \n Support: Core" - items: - description: RouteGroupKind indicates the group and kind - of a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - namespaces: - default: - from: Same - description: "Namespaces indicates namespaces from which - Routes may be attached to this Listener. This is restricted - to the namespace of this Gateway by default. \n Support: - Core" - properties: - from: - default: Same - description: "From indicates where Routes will be selected - for this Gateway. Possible values are: * All: Routes - in all namespaces may be used by this Gateway. * Selector: - Routes in namespaces selected by the selector may - be used by this Gateway. * Same: Only Routes in - the same namespace may be used by this Gateway. \n - Support: Core" - enum: - - All - - Selector - - Same - type: string - selector: - description: "Selector must be specified when From is - set to \"Selector\". In that case, only Routes in - Namespaces matching this Selector will be selected - by this Gateway. This field is ignored for other values - of \"From\". \n Support: Core" - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - type: object - type: object - hostname: - description: "Hostname specifies the virtual hostname to match - for protocol types that define this concept. When unspecified, - all hostnames are matched. This field is ignored for protocols - that don't require hostname based matching. \n Implementations - MUST apply Hostname matching appropriately for each of the - following protocols: \n * TLS: The Listener Hostname MUST - match the SNI. * HTTP: The Listener Hostname MUST match the - Host header of the request. * HTTPS: The Listener Hostname - SHOULD match at both the TLS and HTTP protocol layers as - described above. If an implementation does not ensure that - both the SNI and Host header match the Listener hostname, - \ it MUST clearly document that. \n For HTTPRoute and TLSRoute - resources, there is an interaction with the `spec.hostnames` - array. When both listener and route specify hostnames, there - MUST be an intersection between the values for a Route to - be accepted. For more information, refer to the Route specific - Hostnames documentation. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - name: - description: "Name is the name of the Listener. \n Support: - Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - port: - description: "Port is the network port. Multiple listeners may - use the same port, subject to the Listener compatibility rules. - \n Support: Core" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - description: "Protocol specifies the network protocol this listener - expects to receive. \n Support: Core" - maxLength: 255 - minLength: 1 - pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ - type: string - tls: - description: "TLS is the TLS configuration for the Listener. - This field is required if the Protocol field is \"HTTPS\" - or \"TLS\". It is invalid to set this field if the Protocol - field is \"HTTP\", \"TCP\", or \"UDP\". \n The association - of SNIs to Certificate defined in GatewayTLSConfig is defined - based on the Hostname field for this listener. \n The GatewayClass - MUST use the longest matching SNI out of all available certificates - for any TLS handshake. \n Support: Core" - properties: - certificateRefs: - description: "CertificateRefs contains a series of references - to Kubernetes objects that contains TLS certificates and - private keys. These certificates are used to establish - a TLS handshake for requests that match the hostname of - the associated listener. \n A single CertificateRef to - a Kubernetes Secret has \"Core\" support. Implementations - MAY choose to support attaching multiple certificates - to a Listener, but this behavior is implementation-specific. - \n References to a resource in different namespace are - invalid UNLESS there is a ReferencePolicy in the target - namespace that allows the certificate to be attached. - If a ReferencePolicy does not allow this reference, the - \"ResolvedRefs\" condition MUST be set to False for this - listener with the \"InvalidCertificateRef\" reason. \n - This field is required to have at least one element when - the mode is set to \"Terminate\" (default) and is optional - otherwise. \n CertificateRefs can reference to standard - Kubernetes resources, i.e. Secret, or implementation-specific - custom resources. \n Support: Core - A single reference - to a Kubernetes Secret \n Support: Implementation-specific - (More than one reference or other resource types)" - items: - description: "SecretObjectReference identifies an API - object including its namespace, defaulting to Secret. - \n The API object must be valid in the cluster; the - Group and Kind must be registered in the cluster for - this reference to be valid. \n References to objects - with invalid Group and Kind are not valid, and must - be rejected by the implementation, with appropriate - Conditions set on the containing object." - properties: - group: - default: "" - description: Group is the group of the referent. For - example, "networking.k8s.io". When unspecified (empty - string), core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Secret - description: Kind is kind of the referent. For example - "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. - When unspecified, the local namespace is inferred. - \n Note that when a namespace is specified, a ReferencePolicy - object is required in the referent namespace to - allow that namespace's owner to accept the reference. - See the ReferencePolicy documentation for details. - \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - type: object - maxItems: 64 - type: array - mode: - default: Terminate - description: "Mode defines the TLS behavior for the TLS - session initiated by the client. There are two possible - modes: \n - Terminate: The TLS session between the downstream - client and the Gateway is terminated at the Gateway. - This mode requires certificateRefs to be set and contain - at least one element. - Passthrough: The TLS session is - NOT terminated by the Gateway. This implies that the - Gateway can't decipher the TLS stream except for the - ClientHello message of the TLS protocol. CertificateRefs - field is ignored in this mode. \n Support: Core" - enum: - - Terminate - - Passthrough - type: string - options: - additionalProperties: - description: AnnotationValue is the value of an annotation - in Gateway API. This is used for validation of maps - such as TLS options. This roughly matches Kubernetes - annotation validation, although the length validation - in that case is based on the entire size of the annotations - struct. - maxLength: 4096 - minLength: 0 - type: string - description: "Options are a list of key/value pairs to enable - extended TLS configuration for each implementation. For - example, configuring the minimum TLS version or supported - cipher suites. \n A set of common keys MAY be defined - by the API in the future. To avoid any ambiguity, implementation-specific - definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. - Un-prefixed names are reserved for key names defined by - Gateway API. \n Support: Implementation-specific" - maxProperties: 16 - type: object - type: object - required: - - name - - port - - protocol - type: object - maxItems: 64 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - gatewayClassName - - listeners - type: object - status: - default: - conditions: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: Status defines the current state of Gateway. - properties: - addresses: - description: Addresses lists the IP addresses that have actually been - bound to the Gateway. These addresses may differ from the addresses - in the Spec, e.g. if the Gateway automatically assigns an address - from a reserved pool. - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. - properties: - type: - default: IPAddress - description: Type of the address. - enum: - - IPAddress - - Hostname - - NamedAddress - type: string - value: - description: "Value of the address. The validity of the values - will depend on the type and support by the controller. \n - Examples: `1.2.3.4`, `128::1`, `my-ip-address`." - maxLength: 253 - minLength: 1 - type: string - required: - - value - type: object - maxItems: 16 - type: array - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: NotReconciled - status: Unknown - type: Scheduled - description: "Conditions describe the current conditions of the Gateway. - \n Implementations should prefer to express Gateway conditions using - the `GatewayConditionType` and `GatewayConditionReason` constants - so that operators and tools can converge on a common vocabulary - to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" - * \"Ready\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - listeners: - description: Listeners provide status for each unique listener port - defined in the Spec. - items: - description: ListenerStatus is the status associated with a Listener. - properties: - attachedRoutes: - description: AttachedRoutes represents the total number of Routes - that have been successfully attached to this Listener. - format: int32 - type: integer - conditions: - description: Conditions describe the current condition of this - listener. - items: - description: "Condition contains details for one aspect of - the current state of this API Resource. --- This struct - is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should - be when the underlying condition changed. If that is - not known, then using the time when the API field changed - is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, - if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the - current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier - indicating the reason for the condition's last transition. - Producers of specific condition types may define expected - values and meanings for this field, and whether the - values are considered a guaranteed API. The value should - be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended \n " + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + oneOf: + - properties: type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across - resources like Available, but because arbitrary conditions - can be useful (see .node.status.conditions), the ability - to deconflict is important. The regex it matches is - (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - name: - description: Name is the name of the Listener that this status - corresponds to. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - supportedKinds: - description: "SupportedKinds is the list indicating the Kinds - supported by this listener. This MUST represent the kinds - an implementation supports for that Listener configuration. - \n If kinds are specified in Spec that are not supported, - they MUST NOT appear in this list and an implementation MUST - set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" - reason. If both valid and invalid Route kinds are specified, - the implementation MUST reference the valid Route kinds that - have been specified." - items: - description: RouteGroupKind indicates the group and kind of - a Route resource. - properties: - group: - default: gateway.networking.k8s.io - description: Group is the group of the Route. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is the kind of the Route. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - required: - - kind - type: object - maxItems: 8 - type: array - required: - - attachedRoutes - - conditions - - name - - supportedKinds + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: "Infrastructure defines infrastructure level attributes about this Gateway instance. \n Support: Core \n " + properties: + annotations: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Annotations that SHOULD be applied to any resources created in response to this Gateway. \n For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. For other implementations, this refers to any relevant (implementation specific) \"annotations\" concepts. \n An implementation may chose to add additional implementation-specific annotations as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + labels: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Labels that SHOULD be applied to any resources created in response to this Gateway. \n For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. For other implementations, this refers to any relevant (implementation specific) \"labels\" concepts. \n An implementation may chose to add additional implementation-specific labels as they see fit. \n Support: Extended" + maxProperties: 8 + type: object type: object - maxItems: 64 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each Listener in a set of Listeners (for example, in a single Gateway) MUST be _distinct_, in that a traffic flow MUST be able to be assigned to exactly one listener. (This section uses \"set of Listeners\" rather than \"Listeners in a single Gateway\" because implementations MAY merge configuration from multiple Gateways onto a single data plane, and these rules _also_ apply in that case). \n Practically, this means that each listener in a set MUST have a unique combination of Port, Protocol, and, if supported by the protocol, Hostname. \n Some combinations of port, protocol, and TLS settings are considered Core support and MUST be supported by implementations based on their targeted conformance profile: \n HTTP Profile \n 1. HTTPRoute, Port: 80, Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided \n TLS Profile \n 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough \n \"Distinct\" Listeners have the following property: \n The implementation can match inbound requests to a single distinct Listener. When multiple Listeners share values for fields (for example, two Listeners with the same Port value), the implementation can match requests to only one of the Listeners using other Listener fields. \n For example, the following Listener scenarios are distinct: \n 1. Multiple Listeners with the same Port that all use the \"HTTP\" Protocol that all have unique Hostname values. 2. Multiple Listeners with the same Port that use either the \"HTTPS\" or \"TLS\" Protocol that all have unique Hostname values. 3. A mixture of \"TCP\" and \"UDP\" Protocol Listeners, where no Listener with the same Protocol has the same Port value. \n Some fields in the Listener struct have possible values that affect whether the Listener is distinct. Hostname is particularly relevant for HTTP or HTTPS protocols. \n When using the Hostname value to select between same-Port, same-Protocol Listeners, the Hostname value must be different on each Listener for the Listener to be distinct. \n When the Listeners are distinct based on Hostname, inbound request hostnames MUST match from the most specific to least specific Hostname values to choose the correct Listener and its associated set of Routes. \n Exact matches must be processed before wildcard matches, and wildcard matches must be processed before fallback (empty Hostname value) matches. For example, `\"foo.example.com\"` takes precedence over `\"*.example.com\"`, and `\"*.example.com\"` takes precedence over `\"\"`. \n Additionally, if there are multiple wildcard entries, more specific wildcard entries must be processed before less specific wildcard entries. For example, `\"*.foo.example.com\"` takes precedence over `\"*.example.com\"`. The precise definition here is that the higher the number of dots in the hostname to the right of the wildcard character, the higher the precedence. \n The wildcard character will match any number of characters _and dots_ to the left, however, so `\"*.example.com\"` will match both `\"foo.bar.example.com\"` _and_ `\"bar.example.com\"`. \n If a set of Listeners contains Listeners that are not distinct, then those Listeners are Conflicted, and the implementation MUST set the \"Conflicted\" condition in the Listener Status to \"True\". \n Implementations MAY choose to accept a Gateway with some Conflicted Listeners only if they only accept the partial Listener set that contains no Conflicted Listeners. To put this another way, implementations may accept a partial Listener set only if they throw out *all* the conflicting Listeners. No picking one of the conflicting listeners as the winner. This also means that the Gateway must have at least one non-conflicting Listener in this case, otherwise it violates the requirement that at least one Listener must be present. \n The implementation MUST set a \"ListenersNotValid\" condition on the Gateway Status when the Gateway contains Conflicted Listeners whether or not they accept the Gateway. That Condition SHOULD clearly indicate in the Message which Listeners are conflicted, and which are Accepted. Additionally, the Listener status for those listeners SHOULD indicate which Listeners are conflicted and not Accepted. \n A Gateway's Listeners are considered \"compatible\" if: \n 1. They are distinct. 2. The implementation can serve them in compliance with the Addresses requirement that all Listeners are available on all assigned addresses. \n Compatible combinations in Extended support are expected to vary across implementations. A combination that is compatible for one implementation may not be compatible for another. \n For example, an implementation that cannot serve both TCP and UDP listeners on the same address, or cannot mix HTTPS and generic TLS listens on the same port would not consider those cases compatible, even though they are distinct. \n Note that requests SHOULD match at most one Listener. For example, if Listeners are defined for \"foo.example.com\" and \"*.example.com\", a request to \"foo.example.com\" SHOULD only be routed using routes attached to the \"foo.example.com\" Listener (and not the \"*.example.com\" Listener). This concept is known as \"Listener Isolation\". Implementations that do not support Listener Isolation MUST clearly document this. \n Implementations MAY merge separate Gateways onto a single set of Addresses if all Listeners across all Gateways are compatible. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected for this Gateway. Possible values are: \n * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" + items: + description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referenced object. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs must be specified when TLSModeType is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must be specified for protocols ['HTTPS', 'TLS'] + rule: 'self.all(l, l.protocol in [''HTTPS'', ''TLS''] ? has(l.tls) : true)' + - message: tls must not be specified for protocols ['HTTP', 'TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? !has(l.tls) : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: "Addresses lists the network addresses that have been bound to the Gateway. \n This list may differ from the addresses provided in the spec under some conditions: \n * no addresses are specified, all addresses are dynamically assigned * a combination of specified and dynamic addresses are assigned * a specified address was unusable (e.g. already in use) \n " + items: + description: GatewayStatusAddress describes a network address that is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Programmed\" * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: "AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. \n Successful attachment of a Route to a Listener is based solely on the combination of the AllowedRoutes field on the corresponding Listener and the Route's ParentRefs field. A Route is successfully attached to a Listener when it is selected by the Listener's AllowedRoutes field AND the Route has a valid ParentRef selecting the whole Gateway resource or a specific Listener as a parent resource (more detail on attachment semantics can be found in the documentation on the various Route kinds ParentRefs fields). Listener or Route status does not impact successful attachment, i.e. the AttachedRoutes field count MUST be set for Listeners with condition Accepted: false and MUST count successfully attached Routes that may themselves have Accepted: false conditions. \n Uses for this field include troubleshooting Route attachment and measuring blast radius/impact of changes to a Listener." + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this listener. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended \n " + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: "Infrastructure defines infrastructure level attributes about this Gateway instance. \n Support: Core \n " + properties: + annotations: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Annotations that SHOULD be applied to any resources created in response to this Gateway. \n For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. For other implementations, this refers to any relevant (implementation specific) \"annotations\" concepts. \n An implementation may chose to add additional implementation-specific annotations as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + labels: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Labels that SHOULD be applied to any resources created in response to this Gateway. \n For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. For other implementations, this refers to any relevant (implementation specific) \"labels\" concepts. \n An implementation may chose to add additional implementation-specific labels as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + type: object + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each Listener in a set of Listeners (for example, in a single Gateway) MUST be _distinct_, in that a traffic flow MUST be able to be assigned to exactly one listener. (This section uses \"set of Listeners\" rather than \"Listeners in a single Gateway\" because implementations MAY merge configuration from multiple Gateways onto a single data plane, and these rules _also_ apply in that case). \n Practically, this means that each listener in a set MUST have a unique combination of Port, Protocol, and, if supported by the protocol, Hostname. \n Some combinations of port, protocol, and TLS settings are considered Core support and MUST be supported by implementations based on their targeted conformance profile: \n HTTP Profile \n 1. HTTPRoute, Port: 80, Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided \n TLS Profile \n 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough \n \"Distinct\" Listeners have the following property: \n The implementation can match inbound requests to a single distinct Listener. When multiple Listeners share values for fields (for example, two Listeners with the same Port value), the implementation can match requests to only one of the Listeners using other Listener fields. \n For example, the following Listener scenarios are distinct: \n 1. Multiple Listeners with the same Port that all use the \"HTTP\" Protocol that all have unique Hostname values. 2. Multiple Listeners with the same Port that use either the \"HTTPS\" or \"TLS\" Protocol that all have unique Hostname values. 3. A mixture of \"TCP\" and \"UDP\" Protocol Listeners, where no Listener with the same Protocol has the same Port value. \n Some fields in the Listener struct have possible values that affect whether the Listener is distinct. Hostname is particularly relevant for HTTP or HTTPS protocols. \n When using the Hostname value to select between same-Port, same-Protocol Listeners, the Hostname value must be different on each Listener for the Listener to be distinct. \n When the Listeners are distinct based on Hostname, inbound request hostnames MUST match from the most specific to least specific Hostname values to choose the correct Listener and its associated set of Routes. \n Exact matches must be processed before wildcard matches, and wildcard matches must be processed before fallback (empty Hostname value) matches. For example, `\"foo.example.com\"` takes precedence over `\"*.example.com\"`, and `\"*.example.com\"` takes precedence over `\"\"`. \n Additionally, if there are multiple wildcard entries, more specific wildcard entries must be processed before less specific wildcard entries. For example, `\"*.foo.example.com\"` takes precedence over `\"*.example.com\"`. The precise definition here is that the higher the number of dots in the hostname to the right of the wildcard character, the higher the precedence. \n The wildcard character will match any number of characters _and dots_ to the left, however, so `\"*.example.com\"` will match both `\"foo.bar.example.com\"` _and_ `\"bar.example.com\"`. \n If a set of Listeners contains Listeners that are not distinct, then those Listeners are Conflicted, and the implementation MUST set the \"Conflicted\" condition in the Listener Status to \"True\". \n Implementations MAY choose to accept a Gateway with some Conflicted Listeners only if they only accept the partial Listener set that contains no Conflicted Listeners. To put this another way, implementations may accept a partial Listener set only if they throw out *all* the conflicting Listeners. No picking one of the conflicting listeners as the winner. This also means that the Gateway must have at least one non-conflicting Listener in this case, otherwise it violates the requirement that at least one Listener must be present. \n The implementation MUST set a \"ListenersNotValid\" condition on the Gateway Status when the Gateway contains Conflicted Listeners whether or not they accept the Gateway. That Condition SHOULD clearly indicate in the Message which Listeners are conflicted, and which are Accepted. Additionally, the Listener status for those listeners SHOULD indicate which Listeners are conflicted and not Accepted. \n A Gateway's Listeners are considered \"compatible\" if: \n 1. They are distinct. 2. The implementation can serve them in compliance with the Addresses requirement that all Listeners are available on all assigned addresses. \n Compatible combinations in Extended support are expected to vary across implementations. A combination that is compatible for one implementation may not be compatible for another. \n For example, an implementation that cannot serve both TCP and UDP listeners on the same address, or cannot mix HTTPS and generic TLS listens on the same port would not consider those cases compatible, even though they are distinct. \n Note that requests SHOULD match at most one Listener. For example, if Listeners are defined for \"foo.example.com\" and \"*.example.com\", a request to \"foo.example.com\" SHOULD only be routed using routes attached to the \"foo.example.com\" Listener (and not the \"*.example.com\" Listener). This concept is known as \"Listener Isolation\". Implementations that do not support Listener Isolation MUST clearly document this. \n Implementations MAY merge separate Gateways onto a single set of Addresses if all Listeners across all Gateways are compatible. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected for this Gateway. Possible values are: \n * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" + items: + description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referenced object. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs must be specified when TLSModeType is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must be specified for protocols ['HTTPS', 'TLS'] + rule: 'self.all(l, l.protocol in [''HTTPS'', ''TLS''] ? has(l.tls) : true)' + - message: tls must not be specified for protocols ['HTTP', 'TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? !has(l.tls) : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: "Addresses lists the network addresses that have been bound to the Gateway. \n This list may differ from the addresses provided in the spec under some conditions: \n * no addresses are specified, all addresses are dynamically assigned * a combination of specified and dynamic addresses are assigned * a specified address was unusable (e.g. already in use) \n " + items: + description: GatewayStatusAddress describes a network address that is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Programmed\" * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: "AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. \n Successful attachment of a Route to a Listener is based solely on the combination of the AllowedRoutes field on the corresponding Listener and the Route's ParentRefs field. A Route is successfully attached to a Listener when it is selected by the Listener's AllowedRoutes field AND the Route has a valid ParentRef selecting the whole Gateway resource or a specific Listener as a parent resource (more detail on attachment semantics can be found in the documentation on the various Route kinds ParentRefs fields). Listener or Route status does not impact successful attachment, i.e. the AttachedRoutes field count MUST be set for Listeners with condition Accepted: false and MUST count successfully attached Routes that may themselves have Accepted: false conditions. \n Uses for this field include troubleshooting Route attachment and measuring blast radius/impact of changes to a Listener." + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this listener. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_grpcroutes.yaml b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_grpcroutes.yaml new file mode 100644 index 000000000..1d2964156 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_grpcroutes.yaml @@ -0,0 +1,819 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: GRPCRouteRule defines the semantics for matching a gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: "GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n " + properties: + filters: + description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific \n This filter can be used multiple times within the same rule." + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying the same filter multiple times is not supported unless explicitly indicated in the filter. \n If an implementation can not support a combination of filters, it must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify this configuration error. \n Support: Core" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific \n This filter can be used multiple times within the same rule." + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + matches: + description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." + items: + description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. + items: + description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. + properties: + name: + description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. + properties: + method: + description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string." + maxLength: 1024 + type: string + service: + description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string." + maxLength: 1024 + type: string + type: + default: Exact + description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - RegularExpression + type: string + type: object + x-kubernetes-validations: + - message: One or both of 'service' or 'method' must be specified + rule: 'has(self.type) ? has(self.service) || has(self.method) : true' + - message: service must only contain valid characters (matching ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$) + rule: '(!has(self.type) || self.type == ''Exact'') && has(self.service) ? self.service.matches(r"""^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$"""): true' + - message: method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$) + rule: '(!has(self.type) || self.type == ''Exact'') && has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): true' + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_httproutes.yaml b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_httproutes.yaml index fc574151a..5ce7697b4 100644 --- a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_httproutes.yaml +++ b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_httproutes.yaml @@ -1,240 +1,621 @@ - ---- +# +# config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: httproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io names: categories: - - gateway-api + - gateway-api kind: HTTPRoute listKind: HTTPRouteList plural: httproutes singular: httproute scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes - the capability to match requests by hostname, path, header, or query param. - Filters can be used to specify additional processing steps. Backends specify - where matching requests should be routed. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of HTTPRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostname that should match - against the HTTP Host header to select a HTTPRoute to process the - request. This matches the RFC 1123 definition of a hostname with - 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may - be prefixed with a wildcard label (`*.`). The wildcard label - must appear by itself as the first label. \n If a hostname is specified - by both the Listener and HTTPRoute, there must be at least one intersecting - hostname for the HTTPRoute to be attached to the Listener. For example: - \n * A Listener with `test.example.com` as the hostname matches - HTTPRoutes that have either not specified any hostnames, or have - specified at least one of `test.example.com` or `*.example.com`. - * A Listener with `*.example.com` as the hostname matches HTTPRoutes - \ that have either not specified any hostnames or have specified - at least one hostname that matches the Listener hostname. For - example, `test.example.com` and `*.example.com` would both match. - On the other hand, `example.com` and `test.example.net` would - not match. \n If both the Listener and HTTPRoute have specified - hostnames, any HTTPRoute hostnames that do not match the Listener - hostname MUST be ignored. For example, if a Listener specified `*.example.com`, - and the HTTPRoute specified `test.example.com` and `test.example.net`, - `test.example.net` must not be considered for a match. \n If both - the Listener and HTTPRoute have specified hostnames, and none match - with the criteria above, then the HTTPRoute is not accepted. The - implementation must raise an 'Accepted' Condition with a status - of `False` in the corresponding RouteParentStatus. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network - host. This matches the RFC 1123 definition of a hostname with - 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname - may be prefixed with a wildcard label (`*.`). The wildcard label - must appear by itself as the first label. \n Hostname can be \"precise\" - which is a domain name without the terminating dot of a network - host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain - name prefixed with a single wildcard label (e.g. `*.example.com`). - \n Note that as per RFC1035 and RFC1123, a *label* must consist - of lower case alphanumeric characters or '-', and must start and - end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) - that a Route wants to be attached to. Note that the referenced parent - resource needs to allow this for the attachment to be complete. - For Gateways, that means the Gateway needs to allow attachment from - Routes of this kind and namespace. \n The only kind of parent resource - with \"Core\" support is Gateway. This API may be extended in the - future to support additional kinds of parent resources such as one - of the route kinds. \n It is invalid to reference an identical parent - more than once. It is valid to reference multiple distinct sections - within the same parent resource, such as 2 Listeners within a Gateway. - \n It is possible to separately reference multiple distinct objects - that may be collapsed by an implementation. For example, some implementations - may choose to merge compatible Gateway Listeners together. If that - is the case, the list of routes attached to those resources should - also be merged." - items: - description: "ParentRef identifies an API object (usually a Gateway) - that can be considered a parent of this resource (usually a route). - The only kind of parent resource with \"Core\" support is Gateway. - This API may be extended in the future to support additional kinds - of parent resources, such as HTTPRoute. \n The API object must - be valid in the cluster; the Group and Kind must be registered - in the cluster for this reference to be valid. \n References to - objects with invalid Group and Kind are not valid, and must be - rejected by the implementation, with appropriate Conditions set - on the containing object." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: - Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core - (Gateway) Support: Custom (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: - Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When - unspecified (or empty string), this refers to the local namespace - of the Route. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - sectionName: - description: "SectionName is the name of a section within the - target resource. In the following resources, SectionName is - interpreted as the following: \n * Gateway: Listener Name - \n Implementations MAY choose to support attaching Routes - to other resources. If that is the case, they MUST clearly - document how SectionName is interpreted. \n When unspecified - (empty string), this will reference the entire resource. For - the purpose of status, an attachment is considered successful - if at least one section in the parent resource accepts it. - For example, Gateway listeners can restrict which Routes can - attach to them by Route kind, namespace, or hostname. If 1 - of 2 Gateway listeners accept attachment from the referencing - Route, the Route MUST be considered successfully attached. - If no Gateway listeners accept attachment from this Route, - the Route MUST be considered detached from the Gateway. \n - Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. - items: - description: HTTPRouteRule defines semantics for matching an HTTP - request based on conditions (matches), processing it (filters), - and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "If unspecified or invalid (refers to a non-existent - resource or a Service with no endpoints), the rule performs - no forwarding. If there are also no filters specified that - would result in a response being sent, a HTTP 503 status code - is returned. 503 responses must be sent so that the overall - weight is respected; if an invalid backend is requested to - have 80% of requests, then 80% of requests must get a 503 - instead. \n Support: Core for Kubernetes Service Support: - Custom for any other resource \n Support for weight: Core" - items: - description: HTTPBackendRef defines how a HTTPRoute should - forward an HTTP request. - properties: - filters: - description: "Filters defined at this level should be - executed if and only if the request is being forwarded - to the backend defined here. \n Support: Custom (For - broader support of filters, use the Filters field in - HTTPRouteRule.)" - items: - description: HTTPRouteFilter defines processing steps - that must be completed during the request or response - lifecycle. HTTPRouteFilters are meant as an extension - point to express processing that may be done in Gateway - implementations. Some examples include request or - response modification, implementing authentication - strategies, rate-limiting, and traffic shaping. API - guarantee/conformance is defined based on the type - of the filter. + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames that should match against the HTTP Host header to select a HTTPRoute used to process the request. Implementations MUST ignore any port value specified in the HTTP Host header while performing a match and (absent of any applicable header modification configuration) MUST forward this header unmodified to the backend. \n Valid values for Hostnames are determined by RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: "HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n " + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n This filter can be used multiple times within the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname in the `Host` header of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in the value of the `Location` header in the response. \n If no port is specified, the redirect port MUST be derived using the following rules: \n * If redirect scheme is not-empty, the redirect port MUST be the well-known port associated with the redirect scheme. Specifically \"http\" to port 80 and \"https\" to port 443. If the redirect scheme does not have a well-known port, the listener port of the Gateway SHOULD be used. * If redirect scheme is empty, the redirect port MUST be the Gateway Listener port. \n Implementations SHOULD NOT add the port number in the 'Location' header in the following cases: \n * A Location header that will use HTTP (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 80. * A Location header that will use HTTPS (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Scheme redirects can affect the port of the redirect, for more information, refer to the documentation for the port field of this filter. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying the same filter multiple times is not supported unless explicitly indicated in the filter. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify this configuration error. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n This filter can be used multiple times within the same rule. \n Support: Implementation-specific" properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific - extension to the \"filter\" behavior. For example, - resource \"myroutefilter\" in group \"networking.example.net\"). - ExtensionRef MUST NOT be used for core and extended - filters. \n Support: Implementation-specific" + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" properties: group: - description: Group is the group of the referent. - For example, "networking.k8s.io". When unspecified - (empty string), core API group is inferred. + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: - description: Kind is kind of the referent. For - example "HTTPRoute" or "Service". + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -244,1041 +625,1639 @@ spec: maxLength: 253 minLength: 1 type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema - for a filter that modifies request headers. \n - Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. \n Input: GET /foo - HTTP/1.1 my-header: foo \n Config: add: - \ - name: \"my-header\" value: \"bar\" - \n Output: GET /foo HTTP/1.1 my-header: - foo my-header: bar" - items: - description: HTTPHeader represents an HTTP - Header name and value as defined by RFC - 7230. - properties: - name: - description: "Name is the name of the - HTTP Header to be matched. Name matching - MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). - \n If multiple entries specify equivalent - header names, the first entry with an - equivalent name MUST be considered for - a match. Subsequent entries with an - equivalent header name MUST be ignored. - Due to the case-insensitivity of header - names, \"foo\" and \"Foo\" are considered - equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP - Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - \n Input: GET /foo HTTP/1.1 my-header1: - foo my-header2: bar my-header3: baz \n - Config: remove: [\"my-header1\", \"my-header3\"] - \n Output: GET /foo HTTP/1.1 my-header2: - bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with - the given header (name, value) before the - action. \n Input: GET /foo HTTP/1.1 my-header: - foo \n Config: set: - name: \"my-header\" - \ value: \"bar\" \n Output: GET /foo - HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP - Header name and value as defined by RFC - 7230. - properties: - name: - description: "Name is the name of the - HTTP Header to be matched. Name matching - MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). - \n If multiple entries specify equivalent - header names, the first entry with an - equivalent name MUST be considered for - a match. Subsequent entries with an - equivalent header name MUST be ignored. - Due to the case-insensitivity of header - names, \"foo\" and \"Foo\" are considered - equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP - Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for - a filter that mirrors requests. Requests are sent - to the specified destination, but responses from - that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource - where mirrored requests are sent. \n If the - referent cannot be found, this BackendRef - is invalid and must be dropped from the Gateway. - The controller must ensure the \"ResolvedRefs\" - condition on the Route status is set to `status: - False` and not configure this backend in the - underlying implementation. \n If there is - a cross-namespace reference to an *existing* - object that is not allowed by a ReferencePolicy, - the controller must ensure the \"ResolvedRefs\" - \ condition on the Route is set to `status: - False`, with the \"RefNotPermitted\" reason - and not configure this backend in the underlying - implementation. \n In either error case, the - Message of the `ResolvedRefs` Condition should - be used to provide more detail about the problem. - \n Support: Extended for Kubernetes Service - Support: Custom for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. - For example, "networking.k8s.io". When - unspecified (empty string), core API group - is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. - For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace - of the backend. When unspecified, the - local namespace is inferred. \n Note that - when a namespace is specified, a ReferencePolicy - object is required in the referent namespace - to allow that namespace's owner to accept - the reference. See the ReferencePolicy - documentation for details. \n Support: - Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination - port number to use for this resource. - Port is required when the referent is - a Kubernetes Service. For other resources, - destination port might be derived from - the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for - a filter that responds to the request with an - HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be - used in the value of the `Location` header - in the response. When empty, the hostname - of the request is used. \n Support: Core" - maxLength: 253 + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string port: - description: "Port is the port to be used in - the value of the `Location` header in the - response. When empty, port (if specified) - of the request is used. \n Support: Extended" + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. format: int32 maximum: 65535 minimum: 1 type: integer - scheme: - description: "Scheme is the scheme to be used - in the value of the `Location` header in the - response. When empty, the scheme of the request - is used. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status - code to be used in response. \n Support: Core" - enum: - - 301 - - 302 - type: integer + required: + - name type: object - type: - description: "Type identifies the type of filter - to apply. As with other API fields, types are - classified into three conformance levels: \n - - Core: Filter types and their corresponding configuration - defined by \"Support: Core\" in this package, - e.g. \"RequestHeaderModifier\". All implementations - must support core filters. \n - Extended: Filter - types and their corresponding configuration defined - by \"Support: Extended\" in this package, e.g. - \"RequestMirror\". Implementers are encouraged - to support extended filters. \n - Custom: Filters - that are defined and supported by specific vendors. - \ In the future, filters showing convergence - in behavior across multiple implementations - will be considered for inclusion in extended or - core conformance levels. Filter-specific configuration - for such filters is specified using the ExtensionRef - field. `Type` should be set to \"ExtensionRef\" - for custom filters. \n Implementers are encouraged - to define custom implementation types to extend - the core API with implementation-specific behavior. - \n If a reference to a custom filter type cannot - be resolved, the filter MUST NOT be skipped. Instead, - requests that would have been processed by that - filter MUST receive a HTTP error response." - enum: - - RequestHeaderModifier - - RequestMirror - - RequestRedirect - - ExtensionRef - type: string + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' required: - - type + - backendRef type: object - maxItems: 16 - type: array + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname in the `Host` header of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in the value of the `Location` header in the response. \n If no port is specified, the redirect port MUST be derived using the following rules: \n * If redirect scheme is not-empty, the redirect port MUST be the well-known port associated with the redirect scheme. Specifically \"http\" to port 80 and \"https\" to port 443. If the redirect scheme does not have a well-known port, the listener port of the Gateway SHOULD be used. * If redirect scheme is empty, the redirect port MUST be the Gateway Listener port. \n Implementations SHOULD NOT add the port number in the 'Location' header in the following cases: \n * A Location header that will use HTTP (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 80. * A Location header that will use HTTPS (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Scheme redirects can affect the port of the redirect, for more information, refer to the documentation for the port field of this filter. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match having: \n * \"Exact\" path match. * \"Prefix\" path match with largest number of characters. * Method match. * Largest number of header matches. * Largest number of query param matches. \n Note: The precedence of RegularExpression path matches are implementation-specific. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n path: value: \"/foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') : true' + - message: must not contain '//' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') : true' + - message: must not contain '/./' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') : true' + - message: must not contain '/../' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') : true' + - message: must not contain '%2f' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') : true' + - message: must not contain '%2F' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') : true' + - message: must not contain '#' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') : true' + - message: must not end with '/..' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') : true' + - message: must not end with '/.' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') : true' + - message: type must be one of ['Exact', 'PathPrefix', 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type == 'RegularExpression' + - message: must only contain valid characters (matching ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") : true' + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + timeouts: + description: "Timeouts defines the timeouts that can be configured for an HTTP request. \n Support: Extended \n " + properties: + backendRequest: + description: "BackendRequest specifies a timeout for an individual request from the gateway to a backend. This covers the time from when the request first starts being sent from the gateway to when the full response has been received from the backend. \n An entire client HTTP transaction with a gateway, covered by the Request timeout, may result in more than one call from the gateway to the destination backend, for example, if automatic retries are supported. \n Because the Request timeout encompasses the BackendRequest timeout, the value of BackendRequest must be <= the value of Request timeout. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: "Request specifies the maximum duration for a gateway to respond to an HTTP request. If the gateway has not been able to respond before this deadline is met, the gateway MUST return a timeout error. \n For example, setting the `rules.timeouts.request` field to the value `10s` in an `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds to complete. \n This timeout is intended to cover as close to the whole request-response transaction as possible although an implementation MAY choose to start the timeout after the entire request stream has been received instead of immediately after the transaction is initiated by the client. \n When this field is unspecified, request timeout behavior is implementation-specific. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request timeout + rule: '!(has(self.request) && has(self.backendRequest) && duration(self.request) != duration(''0s'') && duration(self.backendRequest) > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) && has(f.requestRedirect.path) && f.requestRedirect.path.type == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) && has(f.requestRedirect.path) && f.requestRedirect.path.type == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, When using URLRewrite filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: group: - default: "" - description: Group is the group of the referent. For example, - "networking.k8s.io". When unspecified (empty string), - core API group is inferred. + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: - default: Service - description: Kind is kind of the referent. For example - "HTTPRoute" or "Service". + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string name: - description: Name is the name of the referent. + description: "Name is the name of the referent. \n Support: Core" maxLength: 253 minLength: 1 type: string namespace: - description: "Namespace is the namespace of the backend. - When unspecified, the local namespace is inferred. \n - Note that when a namespace is specified, a ReferencePolicy - object is required in the referent namespace to allow - that namespace's owner to accept the reference. See - the ReferencePolicy documentation for details. \n Support: - Core" + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string port: - description: Port specifies the destination port number - to use for this resource. Port is required when the - referent is a Kubernetes Service. For other resources, - destination port might be derived from the referent - resource or this field. + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " format: int32 maximum: 65535 minimum: 1 type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1. \n Support for this field varies based - on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to - requests that match this rule. \n The effects of ordering - of multiple behaviors are currently unspecified. This can - change in the future based on feedback during the alpha stage. - \n Conformance-levels at this level are defined based on the - type of filter: \n - ALL core filters MUST be supported by - all implementations. - Implementers are encouraged to support - extended filters. - Implementation-specific custom filters - have no API guarantees across implementations. \n Specifying - a core filter multiple times has unspecified or custom conformance. - \n Support: Core" - items: - description: HTTPRouteFilter defines processing steps that - must be completed during the request or response lifecycle. - HTTPRouteFilters are meant as an extension point to express - processing that may be done in Gateway implementations. - Some examples include request or response modification, - implementing authentication strategies, rate-limiting, and - traffic shaping. API guarantee/conformance is defined based - on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific - extension to the \"filter\" behavior. For example, - resource \"myroutefilter\" in group \"networking.example.net\"). - ExtensionRef MUST NOT be used for core and extended - filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For - example, "networking.k8s.io". When unspecified (empty - string), core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example - "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for - a filter that modifies request headers. \n Support: - Core" - properties: - add: - description: "Add adds the given header(s) (name, - value) to the request before the action. It appends - to any existing values associated with the header - name. \n Input: GET /foo HTTP/1.1 my-header: - foo \n Config: add: - name: \"my-header\" value: - \"bar\" \n Output: GET /foo HTTP/1.1 my-header: - foo my-header: bar" - items: - description: HTTPHeader represents an HTTP Header - name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header - to be matched. Name matching MUST be case - insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). - \n If multiple entries specify equivalent - header names, the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent header name MUST - be ignored. Due to the case-insensitivity - of header names, \"foo\" and \"Foo\" are considered - equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header - to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the - HTTP request before the action. The value of Remove - is a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - \n Input: GET /foo HTTP/1.1 my-header1: foo - \ my-header2: bar my-header3: baz \n Config: - \ remove: [\"my-header1\", \"my-header3\"] \n Output: - \ GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the - given header (name, value) before the action. \n - Input: GET /foo HTTP/1.1 my-header: foo \n Config: - \ set: - name: \"my-header\" value: \"bar\" - \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header - name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header - to be matched. Name matching MUST be case - insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). - \n If multiple entries specify equivalent - header names, the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent header name MUST - be ignored. Due to the case-insensitivity - of header names, \"foo\" and \"Foo\" are considered - equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header - to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter - that mirrors requests. Requests are sent to the specified - destination, but responses from that destination are - ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where - mirrored requests are sent. \n If the referent cannot - be found, this BackendRef is invalid and must be - dropped from the Gateway. The controller must ensure - the \"ResolvedRefs\" condition on the Route status - is set to `status: False` and not configure this - backend in the underlying implementation. \n If - there is a cross-namespace reference to an *existing* - object that is not allowed by a ReferencePolicy, - the controller must ensure the \"ResolvedRefs\" - \ condition on the Route is set to `status: False`, - with the \"RefNotPermitted\" reason and not configure - this backend in the underlying implementation. \n - In either error case, the Message of the `ResolvedRefs` - Condition should be used to provide more detail - about the problem. \n Support: Extended for Kubernetes - Service Support: Custom for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. - For example, "networking.k8s.io". When unspecified - (empty string), core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For - example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the - backend. When unspecified, the local namespace - is inferred. \n Note that when a namespace is - specified, a ReferencePolicy object is required - in the referent namespace to allow that namespace's - owner to accept the reference. See the ReferencePolicy - documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port - number to use for this resource. Port is required - when the referent is a Kubernetes Service. For - other resources, destination port might be derived - from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter - that responds to the request with an HTTP redirection. - \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used - in the value of the `Location` header in the response. - When empty, the hostname of the request is used. - \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - port: - description: "Port is the port to be used in the value - of the `Location` header in the response. When empty, - port (if specified) of the request is used. \n Support: - Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the - value of the `Location` header in the response. - When empty, the scheme of the request is used. \n - Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to - be used in response. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - type: - description: "Type identifies the type of filter to apply. - As with other API fields, types are classified into - three conformance levels: \n - Core: Filter types and - their corresponding configuration defined by \"Support: - Core\" in this package, e.g. \"RequestHeaderModifier\". - All implementations must support core filters. \n - - Extended: Filter types and their corresponding configuration - defined by \"Support: Extended\" in this package, - e.g. \"RequestMirror\". Implementers are encouraged - to support extended filters. \n - Custom: Filters that - are defined and supported by specific vendors. In - the future, filters showing convergence in behavior - across multiple implementations will be considered - for inclusion in extended or core conformance levels. - Filter-specific configuration for such filters is - specified using the ExtensionRef field. `Type` should - be set to \"ExtensionRef\" for custom filters. \n - Implementers are encouraged to define custom implementation - types to extend the core API with implementation-specific - behavior. \n If a reference to a custom filter type - cannot be resolved, the filter MUST NOT be skipped. - Instead, requests that would have been processed by - that filter MUST receive a HTTP error response." - enum: - - RequestHeaderModifier - - RequestMirror - - RequestRedirect - - ExtensionRef + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - - type + - name type: object - maxItems: 16 - type: array - matches: - default: - - path: - type: PathPrefix - value: / - description: "Matches define conditions used for matching the - rule against incoming HTTP requests. Each match is independent, - i.e. this rule will be matched if **any** one of the matches - is satisfied. \n For example, take the following matches configuration: - \n ``` matches: - path: value: \"/foo\" headers: - - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" - ``` \n For a request to match against this rule, a request - must satisfy EITHER of the two conditions: \n - path prefixed - with `/foo` AND contains the header `version: v2` - path prefix - of `/v2/foo` \n See the documentation for HTTPRouteMatch on - how to specify multiple match conditions that should be ANDed - together. \n If no matches are specified, the default is a - prefix path match on \"/\", which has the effect of matching - every HTTP request. \n Proxy or Load Balancer routing configuration - generated from HTTPRoutes MUST prioritize rules based on the - following criteria, continuing on ties. Precedence must be - given to the the Rule with the largest number of: \n * Characters - in a matching non-wildcard hostname. * Characters in a matching - hostname. * Characters in a matching path. * Header matches. - * Query param matches. \n If ties still exist across multiple - Routes, matching precedence MUST be determined in order of - the following criteria, continuing on ties: \n * The oldest - Route based on creation timestamp. * The Route appearing first - in alphabetical order by \"/\". \n If ties - still exist within the Route that has been given precedence, - matching precedence MUST be granted to the first matching - rule meeting the above criteria." - items: - description: "HTTPRouteMatch defines the predicate used to - match requests to a given action. Multiple match types are - ANDed together, i.e. the match will evaluate to true only - if all conditions are satisfied. \n For example, the match - below will match a HTTP request only if its path starts - with `/foo` AND it contains the `version: v1` header: \n - ``` match: path: value: \"/foo\" headers: - name: - \"version\" value \"v1\" ```" - properties: - headers: - description: Headers specifies HTTP request header matchers. - Multiple match values are ANDed together, meaning, a - request must match all the specified headers to select - the route. - items: - description: HTTPHeaderMatch describes how to select - a HTTP route by matching HTTP request headers. + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames that should match against the HTTP Host header to select a HTTPRoute used to process the request. Implementations MUST ignore any port value specified in the HTTP Host header while performing a match and (absent of any applicable header modification configuration) MUST forward this header unmodified to the backend. \n Valid values for Hostnames are determined by RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: "HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n " + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n This filter can be used multiple times within the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname in the `Host` header of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in the value of the `Location` header in the response. \n If no port is specified, the redirect port MUST be derived using the following rules: \n * If redirect scheme is not-empty, the redirect port MUST be the well-known port associated with the redirect scheme. Specifically \"http\" to port 80 and \"https\" to port 443. If the redirect scheme does not have a well-known port, the listener port of the Gateway SHOULD be used. * If redirect scheme is empty, the redirect port MUST be the Gateway Listener port. \n Implementations SHOULD NOT add the port number in the 'Location' header in the following cases: \n * A Location header that will use HTTP (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 80. * A Location header that will use HTTPS (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Scheme redirects can affect the port of the redirect, for more information, refer to the documentation for the port field of this filter. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying the same filter multiple times is not supported unless explicitly indicated in the filter. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify this configuration error. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n This filter can be used multiple times within the same rule. \n Support: Implementation-specific" properties: - name: - description: "Name is the name of the HTTP Header - to be matched. Name matching MUST be case insensitive. - (See https://tools.ietf.org/html/rfc7230#section-3.2). - \n If multiple entries specify equivalent header - names, only the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent header name MUST be - ignored. Due to the case-insensitivity of header - names, \"foo\" and \"Foo\" are considered equivalent. - \n When a header is repeated in an HTTP request, - it is implementation-specific behavior as to how - this is represented. Generally, proxies should - follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 - regarding processing a repeated header, with special - handling for \"Set-Cookie\"." - maxLength: 256 + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - type: - default: Exact - description: "Type specifies how to match against - the value of the header. \n Support: Core (Exact) - \n Support: Custom (RegularExpression) \n Since - RegularExpression HeaderMatchType has custom conformance, - implementations can support POSIX, PCRE or any - other dialects of regular expressions. Please - read the implementation's documentation to determine - the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP Header to - be matched. - maxLength: 4096 + name: + description: Name is the name of the referent. + maxLength: 253 minLength: 1 type: string required: - - name - - value + - group + - kind + - name type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - description: "Method specifies HTTP method matcher. When - specified, this route will be matched only if the request - has the specified method. \n Support: Extended" - enum: - - GET - - HEAD - - POST - - PUT - - DELETE - - CONNECT - - OPTIONS - - TRACE - - PATCH - type: string - path: - default: + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname in the `Host` header of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in the value of the `Location` header in the response. \n If no port is specified, the redirect port MUST be derived using the following rules: \n * If redirect scheme is not-empty, the redirect port MUST be the well-known port associated with the redirect scheme. Specifically \"http\" to port 80 and \"https\" to port 443. If the redirect scheme does not have a well-known port, the listener port of the Gateway SHOULD be used. * If redirect scheme is empty, the redirect port MUST be the Gateway Listener port. \n Implementations SHOULD NOT add the port number in the 'Location' header in the following cases: \n * A Location header that will use HTTP (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 80. * A Location header that will use HTTPS (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Scheme redirects can affect the port of the redirect, for more information, refer to the documentation for the port field of this filter. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: type: PathPrefix value: / - description: Path specifies a HTTP request path matcher. - If this field is not specified, a default prefix match - on the "/" path is provided. - properties: - type: - default: PathPrefix - description: "Type specifies how to match against - the path Value. \n Support: Core (Exact, PathPrefix) - \n Support: Custom (RegularExpression)" - enum: - - Exact - - PathPrefix - - RegularExpression - type: string - value: - default: / - description: Value of the HTTP path to match against. - maxLength: 1024 - type: string - type: object - queryParams: - description: QueryParams specifies HTTP query parameter - matchers. Multiple match values are ANDed together, - meaning, a request must match all the specified query - parameters to select the route. - items: - description: HTTPQueryParamMatch describes how to select - a HTTP route by matching HTTP query parameters. + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match having: \n * \"Exact\" path match. * \"Prefix\" path match with largest number of characters. * Method match. * Largest number of header matches. * Largest number of query param matches. \n Note: The precedence of RegularExpression path matches are implementation-specific. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n path: value: \"/foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. properties: - name: - description: Name is the name of the HTTP query - param to be matched. This must be an exact string - match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). - maxLength: 256 - minLength: 1 - type: string type: - default: Exact - description: "Type specifies how to match against - the value of the query parameter. \n Support: - Extended (Exact) \n Support: Custom (RegularExpression) - \n Since RegularExpression QueryParamMatchType - has custom conformance, implementations can support - POSIX, PCRE or any other dialects of regular expressions. - Please read the implementation's documentation - to determine the supported dialect." + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" enum: - - Exact - - RegularExpression + - Exact + - PathPrefix + - RegularExpression type: string value: - description: Value is the value of HTTP query param - to be matched. + default: / + description: Value of the HTTP path to match against. maxLength: 1024 - minLength: 1 type: string - required: - - name - - value type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of HTTPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) - that are associated with the route, and the status of the route - with respect to each parent. When this route attaches to a parent, - the controller that manages the parent must add an entry to this - list when the controller first sees the route and should update - the entry as appropriate when the route or gateway is modified. - \n Note that parent references that cannot be resolved by an implementation - of this API will not be added to this list. Implementations of this - API can only populate Route status for the Gateways/parent resources - they are responsible for. \n A maximum of 32 Gateways will be represented - in this list. An empty list means the route has not been attached - to any Gateway." - items: - description: RouteParentStatus describes the status of a route with - respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with - respect to the Gateway. Note that the route's availability - is also subject to the Gateway's own status conditions and - listener status. \n If the Route's ParentRef specifies an - existing Gateway that supports Routes of this kind AND that - Gateway's controller has sufficient access, then that Gateway's - controller MUST set the \"Accepted\" condition on the Route, - to indicate whether the route has been accepted or rejected - by the Gateway, and why. \n A Route MUST be considered \"Accepted\" - if at least one of the Route's rules is implemented by the - Gateway. \n There are a number of cases where the \"Accepted\" - condition may not be set due to lack of controller visibility, - that includes when: \n * The Route refers to a non-existent - parent. * The Route is of a type that the controller does - not support. * The Route is in a namespace the the controller - does not have access to." - items: - description: "Condition contains details for one aspect of - the current state of this API Resource. --- This struct - is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + x-kubernetes-validations: + - message: value must be an absolute path and start with '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') : true' + - message: must not contain '//' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') : true' + - message: must not contain '/./' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') : true' + - message: must not contain '/../' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') : true' + - message: must not contain '%2f' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') : true' + - message: must not contain '%2F' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') : true' + - message: must not contain '#' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') : true' + - message: must not end with '/..' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') : true' + - message: must not end with '/.' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') : true' + - message: type must be one of ['Exact', 'PathPrefix', 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type == 'RegularExpression' + - message: must only contain valid characters (matching ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") : true' + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + timeouts: + description: "Timeouts defines the timeouts that can be configured for an HTTP request. \n Support: Extended \n " properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should - be when the underlying condition changed. If that is - not known, then using the time when the API field changed - is acceptable. - format: date-time + backendRequest: + description: "BackendRequest specifies a timeout for an individual request from the gateway to a backend. This covers the time from when the request first starts being sent from the gateway to when the full response has been received from the backend. \n An entire client HTTP transaction with a gateway, covered by the Request timeout, may result in more than one call from the gateway to the destination backend, for example, if automatic retries are supported. \n Because the Request timeout encompasses the BackendRequest timeout, the value of BackendRequest must be <= the value of Request timeout. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 + request: + description: "Request specifies the maximum duration for a gateway to respond to an HTTP request. If the gateway has not been able to respond before this deadline is met, the gateway MUST return a timeout error. \n For example, setting the `rules.timeouts.request` field to the value `10s` in an `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds to complete. \n This timeout is intended to cover as close to the whole request-response transaction as possible although an implementation MAY choose to start the timeout after the entire request stream has been received instead of immediately after the transaction is initiated by the client. \n When this field is unspecified, request timeout behavior is implementation-specific. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, - if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the - current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier - indicating the reason for the condition's last transition. - Producers of specific condition types may define expected - values and meanings for this field, and whether the - values are considered a guaranteed API. The value should - be a CamelCase string. This field may not be empty. - maxLength: 1024 + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request timeout + rule: '!(has(self.request) && has(self.backendRequest) && duration(self.request) != duration(''0s'') && duration(self.backendRequest) > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) && has(f.requestRedirect.path) && f.requestRedirect.path.type == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) && has(f.requestRedirect.path) && f.requestRedirect.path.type == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, When using URLRewrite filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across - resources like Available, but because arbitrary conditions - can be useful (see .node.status.conditions), the ability - to deconflict is important. The regex it matches is - (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - - lastTransitionTime - - message - - reason - - status - - type + - name type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates - the name of the controller that wrote this status. This corresponds - with the controllerName field on GatewayClass. \n Example: - \"example.net/gateway-controller\". \n The format of this - field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid - Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names)." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec - that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: - Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: - Core (Gateway) Support: Custom (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: - Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. - When unspecified (or empty string), this refers to the - local namespace of the Route. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - sectionName: - description: "SectionName is the name of a section within - the target resource. In the following resources, SectionName - is interpreted as the following: \n * Gateway: Listener - Name \n Implementations MAY choose to support attaching - Routes to other resources. If that is the case, they MUST - clearly document how SectionName is interpreted. \n When - unspecified (empty string), this will reference the entire - resource. For the purpose of status, an attachment is - considered successful if at least one section in the parent - resource accepts it. For example, Gateway listeners can - restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept - attachment from the referencing Route, the Route MUST - be considered successfully attached. If no Gateway listeners - accept attachment from this Route, the Route MUST be considered - detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_referencegrants.yaml b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_referencegrants.yaml new file mode 100644 index 000000000..fb9a6dc1e --- /dev/null +++ b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_referencegrants.yaml @@ -0,0 +1,205 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: The v1alpha2 version of ReferenceGrant has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n A ReferenceGrant is required for all cross-namespace references in Gateway API (with the exception of cross-namespace Route-Gateway attachment, which is governed by the AllowedRoutes configuration on the Gateway, and cross-namespace Service ParentRefs on a \"consumer\" mesh Route, which defines routing rules applicable only to workloads in the Route namespace). ReferenceGrants allowing a reference from a Route to a Service are only applicable to BackendRefs. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and kinds. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: false + subresources: {} + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and kinds. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_tcproutes.yaml b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_tcproutes.yaml index b7bf7ff16..b2284973c 100644 --- a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_tcproutes.yaml +++ b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_tcproutes.yaml @@ -1,431 +1,284 @@ - ---- +# +# config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: tcproutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io names: categories: - - gateway-api + - gateway-api kind: TCPRoute listKind: TCPRouteList plural: tcproutes singular: tcproute scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: TCPRoute provides a way to route TCP requests. When combined - with a Gateway listener, it can be used to forward connections on the port - specified by the listener to a set of backends specified by the TCPRoute. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of TCPRoute. - properties: - parentRefs: - description: "ParentRefs references the resources (usually Gateways) - that a Route wants to be attached to. Note that the referenced parent - resource needs to allow this for the attachment to be complete. - For Gateways, that means the Gateway needs to allow attachment from - Routes of this kind and namespace. \n The only kind of parent resource - with \"Core\" support is Gateway. This API may be extended in the - future to support additional kinds of parent resources such as one - of the route kinds. \n It is invalid to reference an identical parent - more than once. It is valid to reference multiple distinct sections - within the same parent resource, such as 2 Listeners within a Gateway. - \n It is possible to separately reference multiple distinct objects - that may be collapsed by an implementation. For example, some implementations - may choose to merge compatible Gateway Listeners together. If that - is the case, the list of routes attached to those resources should - also be merged." - items: - description: "ParentRef identifies an API object (usually a Gateway) - that can be considered a parent of this resource (usually a route). - The only kind of parent resource with \"Core\" support is Gateway. - This API may be extended in the future to support additional kinds - of parent resources, such as HTTPRoute. \n The API object must - be valid in the cluster; the Group and Kind must be registered - in the cluster for this reference to be valid. \n References to - objects with invalid Group and Kind are not valid, and must be - rejected by the implementation, with appropriate Conditions set - on the containing object." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: - Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core - (Gateway) Support: Custom (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: - Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When - unspecified (or empty string), this refers to the local namespace - of the Route. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - sectionName: - description: "SectionName is the name of a section within the - target resource. In the following resources, SectionName is - interpreted as the following: \n * Gateway: Listener Name - \n Implementations MAY choose to support attaching Routes - to other resources. If that is the case, they MUST clearly - document how SectionName is interpreted. \n When unspecified - (empty string), this will reference the entire resource. For - the purpose of status, an attachment is considered successful - if at least one section in the parent resource accepts it. - For example, Gateway listeners can restrict which Routes can - attach to them by Route kind, namespace, or hostname. If 1 - of 2 Gateway listeners accept attachment from the referencing - Route, the Route MUST be considered successfully attached. - If no Gateway listeners accept attachment from this Route, - the Route MUST be considered detached from the Gateway. \n - Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of TCP matchers and actions. - items: - description: TCPRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. If unspecified or invalid (refers - to a non-existent resource or a Service with no endpoints), - the underlying implementation MUST actively reject connection - attempts to this backend. Connection rejections must respect - weight; if an invalid backend is requested to have 80% of - connections, then 80% of connections must be rejected instead. - \n Support: Core for Kubernetes Service Support: Custom for - any other resource \n Support for weight: Extended" - items: - description: "BackendRef defines how a Route should forward - a request to a Kubernetes resource. \n Note that when a - namespace is specified, a ReferencePolicy object is required - in the referent namespace to allow that namespace's owner - to accept the reference. See the ReferencePolicy documentation - for details." + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n \n Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields where this struct is used for more information about the exact behavior." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. properties: group: - default: "" - description: Group is the group of the referent. For example, - "networking.k8s.io". When unspecified (empty string), - core API group is inferred. + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: - default: Service - description: Kind is kind of the referent. For example - "HTTPRoute" or "Service". + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string name: - description: Name is the name of the referent. + description: "Name is the name of the referent. \n Support: Core" maxLength: 253 minLength: 1 type: string namespace: - description: "Namespace is the namespace of the backend. - When unspecified, the local namespace is inferred. \n - Note that when a namespace is specified, a ReferencePolicy - object is required in the referent namespace to allow - that namespace's owner to accept the reference. See - the ReferencePolicy documentation for details. \n Support: - Core" + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string port: - description: Port specifies the destination port number - to use for this resource. Port is required when the - referent is a Kubernetes Service. For other resources, - destination port might be derived from the referent - resource or this field. + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " format: int32 maximum: 65535 minimum: 1 type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1. \n Support for this field varies based - on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of TCPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) - that are associated with the route, and the status of the route - with respect to each parent. When this route attaches to a parent, - the controller that manages the parent must add an entry to this - list when the controller first sees the route and should update - the entry as appropriate when the route or gateway is modified. - \n Note that parent references that cannot be resolved by an implementation - of this API will not be added to this list. Implementations of this - API can only populate Route status for the Gateways/parent resources - they are responsible for. \n A maximum of 32 Gateways will be represented - in this list. An empty list means the route has not been attached - to any Gateway." - items: - description: RouteParentStatus describes the status of a route with - respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with - respect to the Gateway. Note that the route's availability - is also subject to the Gateway's own status conditions and - listener status. \n If the Route's ParentRef specifies an - existing Gateway that supports Routes of this kind AND that - Gateway's controller has sufficient access, then that Gateway's - controller MUST set the \"Accepted\" condition on the Route, - to indicate whether the route has been accepted or rejected - by the Gateway, and why. \n A Route MUST be considered \"Accepted\" - if at least one of the Route's rules is implemented by the - Gateway. \n There are a number of cases where the \"Accepted\" - condition may not be set due to lack of controller visibility, - that includes when: \n * The Route refers to a non-existent - parent. * The Route is of a type that the controller does - not support. * The Route is in a namespace the the controller - does not have access to." - items: - description: "Condition contains details for one aspect of - the current state of this API Resource. --- This struct - is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should - be when the underlying condition changed. If that is - not known, then using the time when the API field changed - is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, - if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the - current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier - indicating the reason for the condition's last transition. - Producers of specific condition types may define expected - values and meanings for this field, and whether the - values are considered a guaranteed API. The value should - be a CamelCase string. This field may not be empty. - maxLength: 1024 + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across - resources like Available, but because arbitrary conditions - can be useful (see .node.status.conditions), the ability - to deconflict is important. The regex it matches is - (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - - lastTransitionTime - - message - - reason - - status - - type + - name type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates - the name of the controller that wrote this status. This corresponds - with the controllerName field on GatewayClass. \n Example: - \"example.net/gateway-controller\". \n The format of this - field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid - Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names)." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec - that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: - Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: - Core (Gateway) Support: Custom (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: - Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. - When unspecified (or empty string), this refers to the - local namespace of the Route. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - sectionName: - description: "SectionName is the name of a section within - the target resource. In the following resources, SectionName - is interpreted as the following: \n * Gateway: Listener - Name \n Implementations MAY choose to support attaching - Routes to other resources. If that is the case, they MUST - clearly document how SectionName is interpreted. \n When - unspecified (empty string), this will reference the entire - resource. For the purpose of status, an attachment is - considered successful if at least one section in the parent - resource accepts it. For example, Gateway listeners can - restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept - attachment from the referencing Route, the Route MUST - be considered successfully attached. If no Gateway listeners - accept attachment from this Route, the Route MUST be considered - detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_tlsroutes.yaml b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_tlsroutes.yaml index ab3b49990..fa097ea7f 100644 --- a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_tlsroutes.yaml +++ b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_tlsroutes.yaml @@ -1,480 +1,294 @@ - ---- +# +# config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml +# apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: tlsroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io names: categories: - - gateway-api + - gateway-api kind: TLSRoute listKind: TLSRouteList plural: tlsroutes singular: tlsroute scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: "The TLSRoute resource is similar to TCPRoute, but can be configured - to match against TLS-specific metadata. This allows more flexibility in - matching streams for a given TLS listener. \n If you need to forward traffic - to a single target for a TLS listener, you could choose to use a TCPRoute - with a TLS listener." - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of TLSRoute. - properties: - hostnames: - description: "Hostnames defines a set of SNI names that should match - against the SNI attribute of TLS ClientHello message in TLS handshake. - This matches the RFC 1123 definition of a hostname with 2 notable - exceptions: \n 1. IPs are not allowed in SNI names per RFC 6066. - 2. A hostname may be prefixed with a wildcard label (`*.`). The - wildcard label must appear by itself as the first label. \n If - a hostname is specified by both the Listener and TLSRoute, there - must be at least one intersecting hostname for the TLSRoute to be - attached to the Listener. For example: \n * A Listener with `test.example.com` - as the hostname matches TLSRoutes that have either not specified - any hostnames, or have specified at least one of `test.example.com` - or `*.example.com`. * A Listener with `*.example.com` as the hostname - matches TLSRoutes that have either not specified any hostnames - or have specified at least one hostname that matches the Listener - hostname. For example, `test.example.com` and `*.example.com` - would both match. On the other hand, `example.com` and `test.example.net` - would not match. \n If both the Listener and TLSRoute have specified - hostnames, any TLSRoute hostnames that do not match the Listener - hostname MUST be ignored. For example, if a Listener specified `*.example.com`, - and the TLSRoute specified `test.example.com` and `test.example.net`, - `test.example.net` must not be considered for a match. \n If both - the Listener and TLSRoute have specified hostnames, and none match - with the criteria above, then the TLSRoute is not accepted. The - implementation must raise an 'Accepted' Condition with a status - of `False` in the corresponding RouteParentStatus. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network - host. This matches the RFC 1123 definition of a hostname with - 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname - may be prefixed with a wildcard label (`*.`). The wildcard label - must appear by itself as the first label. \n Hostname can be \"precise\" - which is a domain name without the terminating dot of a network - host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain - name prefixed with a single wildcard label (e.g. `*.example.com`). - \n Note that as per RFC1035 and RFC1123, a *label* must consist - of lower case alphanumeric characters or '-', and must start and - end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) - that a Route wants to be attached to. Note that the referenced parent - resource needs to allow this for the attachment to be complete. - For Gateways, that means the Gateway needs to allow attachment from - Routes of this kind and namespace. \n The only kind of parent resource - with \"Core\" support is Gateway. This API may be extended in the - future to support additional kinds of parent resources such as one - of the route kinds. \n It is invalid to reference an identical parent - more than once. It is valid to reference multiple distinct sections - within the same parent resource, such as 2 Listeners within a Gateway. - \n It is possible to separately reference multiple distinct objects - that may be collapsed by an implementation. For example, some implementations - may choose to merge compatible Gateway Listeners together. If that - is the case, the list of routes attached to those resources should - also be merged." - items: - description: "ParentRef identifies an API object (usually a Gateway) - that can be considered a parent of this resource (usually a route). - The only kind of parent resource with \"Core\" support is Gateway. - This API may be extended in the future to support additional kinds - of parent resources, such as HTTPRoute. \n The API object must - be valid in the cluster; the Group and Kind must be registered - in the cluster for this reference to be valid. \n References to - objects with invalid Group and Kind are not valid, and must be - rejected by the implementation, with appropriate Conditions set - on the containing object." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: - Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core - (Gateway) Support: Custom (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: - Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When - unspecified (or empty string), this refers to the local namespace - of the Route. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - sectionName: - description: "SectionName is the name of a section within the - target resource. In the following resources, SectionName is - interpreted as the following: \n * Gateway: Listener Name - \n Implementations MAY choose to support attaching Routes - to other resources. If that is the case, they MUST clearly - document how SectionName is interpreted. \n When unspecified - (empty string), this will reference the entire resource. For - the purpose of status, an attachment is considered successful - if at least one section in the parent resource accepts it. - For example, Gateway listeners can restrict which Routes can - attach to them by Route kind, namespace, or hostname. If 1 - of 2 Gateway listeners accept attachment from the referencing - Route, the Route MUST be considered successfully attached. - If no Gateway listeners accept attachment from this Route, - the Route MUST be considered detached from the Gateway. \n - Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of TLS matchers and actions. - items: - description: TLSRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. If unspecified or invalid (refers - to a non-existent resource or a Service with no endpoints), - the rule performs no forwarding; if no filters are specified - that would result in a response being sent, the underlying - implementation must actively reject request attempts to this - backend, by rejecting the connection or returning a 503 status - code. Request rejections must respect weight; if an invalid - backend is requested to have 80% of requests, then 80% of - requests must be rejected instead. \n Support: Core for Kubernetes - Service Support: Custom for any other resource \n Support - for weight: Extended" - items: - description: "BackendRef defines how a Route should forward - a request to a Kubernetes resource. \n Note that when a - namespace is specified, a ReferencePolicy object is required - in the referent namespace to allow that namespace's owner - to accept the reference. See the ReferencePolicy documentation - for details." + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "The TLSRoute resource is similar to TCPRoute, but can be configured to match against TLS-specific metadata. This allows more flexibility in matching streams for a given TLS listener. \n If you need to forward traffic to a single target for a TLS listener, you could choose to use a TCPRoute with a TLS listener." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + hostnames: + description: "Hostnames defines a set of SNI names that should match against the SNI attribute of TLS ClientHello message in TLS handshake. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed in SNI names per RFC 6066. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and TLSRoute, there must be at least one intersecting hostname for the TLSRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n If both the Listener and TLSRoute have specified hostnames, any TLSRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the TLSRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and TLSRoute have specified hostnames, and none match with the criteria above, then the TLSRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the rule performs no forwarding; if no filters are specified that would result in a response being sent, the underlying implementation must actively reject request attempts to this backend, by rejecting the connection or returning a 500 status code. Request rejections must respect weight; if an invalid backend is requested to have 80% of requests, then 80% of requests must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n \n Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields where this struct is used for more information about the exact behavior." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. properties: group: - default: "" - description: Group is the group of the referent. For example, - "networking.k8s.io". When unspecified (empty string), - core API group is inferred. + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: - default: Service - description: Kind is kind of the referent. For example - "HTTPRoute" or "Service". + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string name: - description: Name is the name of the referent. + description: "Name is the name of the referent. \n Support: Core" maxLength: 253 minLength: 1 type: string namespace: - description: "Namespace is the namespace of the backend. - When unspecified, the local namespace is inferred. \n - Note that when a namespace is specified, a ReferencePolicy - object is required in the referent namespace to allow - that namespace's owner to accept the reference. See - the ReferencePolicy documentation for details. \n Support: - Core" + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string port: - description: Port specifies the destination port number - to use for this resource. Port is required when the - referent is a Kubernetes Service. For other resources, - destination port might be derived from the referent - resource or this field. + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " format: int32 maximum: 65535 minimum: 1 type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1. \n Support for this field varies based - on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of TLSRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) - that are associated with the route, and the status of the route - with respect to each parent. When this route attaches to a parent, - the controller that manages the parent must add an entry to this - list when the controller first sees the route and should update - the entry as appropriate when the route or gateway is modified. - \n Note that parent references that cannot be resolved by an implementation - of this API will not be added to this list. Implementations of this - API can only populate Route status for the Gateways/parent resources - they are responsible for. \n A maximum of 32 Gateways will be represented - in this list. An empty list means the route has not been attached - to any Gateway." - items: - description: RouteParentStatus describes the status of a route with - respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with - respect to the Gateway. Note that the route's availability - is also subject to the Gateway's own status conditions and - listener status. \n If the Route's ParentRef specifies an - existing Gateway that supports Routes of this kind AND that - Gateway's controller has sufficient access, then that Gateway's - controller MUST set the \"Accepted\" condition on the Route, - to indicate whether the route has been accepted or rejected - by the Gateway, and why. \n A Route MUST be considered \"Accepted\" - if at least one of the Route's rules is implemented by the - Gateway. \n There are a number of cases where the \"Accepted\" - condition may not be set due to lack of controller visibility, - that includes when: \n * The Route refers to a non-existent - parent. * The Route is of a type that the controller does - not support. * The Route is in a namespace the the controller - does not have access to." - items: - description: "Condition contains details for one aspect of - the current state of this API Resource. --- This struct - is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should - be when the underlying condition changed. If that is - not known, then using the time when the API field changed - is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, - if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the - current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier - indicating the reason for the condition's last transition. - Producers of specific condition types may define expected - values and meanings for this field, and whether the - values are considered a guaranteed API. The value should - be a CamelCase string. This field may not be empty. - maxLength: 1024 + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across - resources like Available, but because arbitrary conditions - can be useful (see .node.status.conditions), the ability - to deconflict is important. The regex it matches is - (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string required: - - lastTransitionTime - - message - - reason - - status - - type + - name type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates - the name of the controller that wrote this status. This corresponds - with the controllerName field on GatewayClass. \n Example: - \"example.net/gateway-controller\". \n The format of this - field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid - Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names)." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec - that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: - Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: - Core (Gateway) Support: Custom (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: - Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. - When unspecified (or empty string), this refers to the - local namespace of the Route. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - sectionName: - description: "SectionName is the name of a section within - the target resource. In the following resources, SectionName - is interpreted as the following: \n * Gateway: Listener - Name \n Implementations MAY choose to support attaching - Routes to other resources. If that is the case, they MUST - clearly document how SectionName is interpreted. \n When - unspecified (empty string), this will reference the entire - resource. For the purpose of status, an attachment is - considered successful if at least one section in the parent - resource accepts it. For example, Gateway listeners can - restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept - attachment from the referencing Route, the Route MUST - be considered successfully attached. If no Gateway listeners - accept attachment from this Route, the Route MUST be considered - detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_udproutes.yaml b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_udproutes.yaml new file mode 100644 index 000000000..bb48563b9 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/gateway.networking.k8s.io_udproutes.yaml @@ -0,0 +1,284 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: udproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: UDPRoute + listKind: UDPRouteList + plural: udproutes + singular: udproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: UDPRoute provides a way to route UDP traffic. When combined with a Gateway listener, it can be used to forward traffic on the port specified by the listener to a set of backends specified by the UDPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of UDPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + description: Rules are a list of UDP matchers and actions. + items: + description: UDPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Packet drops must respect weight; if an invalid backend is requested to have 80% of the packets, then 80% of packets must be dropped instead. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n \n Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields where this struct is used for more information about the exact behavior." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of UDPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 37849dedc..5a4c63c3c 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: ingressroutes.traefik.io spec: group: traefik.io @@ -162,6 +160,12 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When + set to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -267,20 +271,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: ingressroutetcps.traefik.io spec: group: traefik.io @@ -486,20 +482,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: ingressrouteudps.traefik.io spec: group: traefik.io @@ -591,20 +579,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: middlewares.traefik.io spec: group: traefik.io @@ -776,6 +756,13 @@ spec: items: type: string type: array + includedContentTypes: + description: IncludedContentTypes defines the list of content + types to compare the Content-Type header of the responses before + compressing. + items: + type: string + type: array minResponseBodyBytes: description: 'MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed. Default: @@ -896,6 +883,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -938,6 +931,12 @@ spec: This middleware delegates the request authentication to a Service. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/' properties: + addAuthCookiesToResponse: + description: AddAuthCookiesToResponse defines the list of cookies + to copy from the authentication server response to the response. + items: + type: string + type: array address: description: Address defines the authentication server address. type: string @@ -1190,6 +1189,36 @@ spec: description: 'IPAllowList holds the IP allowlist middleware configuration. This middleware accepts / refuses requests based on the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/' + properties: + ipStrategy: + description: 'IPStrategy holds the IP strategy configuration used + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position (starting + from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the X-Forwarded-For + header and select the first IP not in the list. + items: + type: string + type: array + type: object + rejectStatusCode: + description: RejectStatusCode defines the HTTP status code used + for refused requests. If not set, the default is 403 (Forbidden). + type: integer + sourceRange: + description: SourceRange defines the set of allowed IPs (or ranges + of allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object + ipWhiteList: + description: 'Deprecated: please use IPAllowList instead.' properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration used @@ -1493,20 +1522,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: middlewaretcps.traefik.io spec: group: traefik.io @@ -1558,6 +1579,17 @@ spec: type: string type: array type: object + ipWhiteList: + description: 'IPWhiteList defines the IPWhiteList middleware configuration. + Deprecated: please use IPAllowList instead.' + properties: + sourceRange: + description: SourceRange defines the allowed IPs (or ranges of + allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object type: object required: - metadata @@ -1565,20 +1597,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: serverstransports.traefik.io spec: group: traefik.io @@ -1706,20 +1730,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: serverstransporttcps.traefik.io spec: group: traefik.io @@ -1828,20 +1844,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: tlsoptions.traefik.io spec: group: traefik.io @@ -1935,20 +1943,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: tlsstores.traefik.io spec: group: traefik.io @@ -2034,20 +2034,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: traefikservices.traefik.io spec: group: traefik.io @@ -2178,6 +2170,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2269,6 +2267,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2376,6 +2380,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2415,6 +2425,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2436,9 +2452,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd.md b/docs/content/reference/dynamic-configuration/kubernetes-crd.md index ba7c6e56e..fb6ec6f72 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd.md +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd.md @@ -26,4 +26,4 @@ Dynamic configuration with Kubernetes Custom Resource --8<-- "content/reference/dynamic-configuration/kubernetes-crd-rbac.yml" ``` -{!traefik-api-management-kubernetes.md!} +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml index a22858a69..d3b1bef20 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-resource.yml @@ -1,5 +1,5 @@ --- -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: my-gateway-class @@ -7,7 +7,7 @@ spec: controllerName: traefik.io/gateway-controller --- -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: my-gateway @@ -44,7 +44,7 @@ spec: name: mysecret --- -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: http-app diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-https.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-https.yml index c16a5dc2f..1f15ae429 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-https.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-simple-https.yml @@ -1,5 +1,5 @@ --- -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: my-gateway-class @@ -8,7 +8,7 @@ spec: controllerName: traefik.io/gateway-controller --- -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: my-gateway @@ -25,7 +25,7 @@ spec: name: mysecret --- -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: http-app diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway.md b/docs/content/reference/dynamic-configuration/kubernetes-gateway.md index 6700a218d..1abbcf2b5 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway.md +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway.md @@ -11,11 +11,15 @@ Dynamic configuration with Kubernetes Gateway provider. ## Definitions ```yaml +--8<-- "content/reference/dynamic-configuration/gateway.networking.k8s.io_backendtlspolicies.yaml" --8<-- "content/reference/dynamic-configuration/gateway.networking.k8s.io_gatewayclasses.yaml" --8<-- "content/reference/dynamic-configuration/gateway.networking.k8s.io_gateways.yaml" +--8<-- "content/reference/dynamic-configuration/gateway.networking.k8s.io_grpcroutes.yaml" --8<-- "content/reference/dynamic-configuration/gateway.networking.k8s.io_httproutes.yaml" +--8<-- "content/reference/dynamic-configuration/gateway.networking.k8s.io_referencegrants.yaml" --8<-- "content/reference/dynamic-configuration/gateway.networking.k8s.io_tcproutes.yaml" --8<-- "content/reference/dynamic-configuration/gateway.networking.k8s.io_tlsroutes.yaml" +--8<-- "content/reference/dynamic-configuration/gateway.networking.k8s.io_udproutes.yaml" ``` ## Resources @@ -30,4 +34,4 @@ Dynamic configuration with Kubernetes Gateway provider. --8<-- "content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml" ``` -{!traefik-api-management-kubernetes.md!} +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index ce15fb1f2..f078b166a 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -1,140 +1,157 @@ -| `traefik/http/middlewares/Middleware00/addPrefix/prefix` | `foobar` | -| `traefik/http/middlewares/Middleware01/basicAuth/headerField` | `foobar` | -| `traefik/http/middlewares/Middleware01/basicAuth/realm` | `foobar` | -| `traefik/http/middlewares/Middleware01/basicAuth/removeHeader` | `true` | -| `traefik/http/middlewares/Middleware01/basicAuth/users/0` | `foobar` | -| `traefik/http/middlewares/Middleware01/basicAuth/users/1` | `foobar` | -| `traefik/http/middlewares/Middleware01/basicAuth/usersFile` | `foobar` | -| `traefik/http/middlewares/Middleware02/buffering/maxRequestBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware02/buffering/maxResponseBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware02/buffering/memRequestBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware02/buffering/memResponseBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware02/buffering/retryExpression` | `foobar` | -| `traefik/http/middlewares/Middleware03/chain/middlewares/0` | `foobar` | -| `traefik/http/middlewares/Middleware03/chain/middlewares/1` | `foobar` | -| `traefik/http/middlewares/Middleware04/circuitBreaker/checkPeriod` | `42s` | -| `traefik/http/middlewares/Middleware04/circuitBreaker/expression` | `foobar` | -| `traefik/http/middlewares/Middleware04/circuitBreaker/fallbackDuration` | `42s` | -| `traefik/http/middlewares/Middleware04/circuitBreaker/recoveryDuration` | `42s` | -| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/0` | `foobar` | -| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/1` | `foobar` | -| `traefik/http/middlewares/Middleware05/compress/minResponseBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware06/contentType` | `` | -| `traefik/http/middlewares/Middleware07/digestAuth/headerField` | `foobar` | -| `traefik/http/middlewares/Middleware07/digestAuth/realm` | `foobar` | -| `traefik/http/middlewares/Middleware07/digestAuth/removeHeader` | `true` | -| `traefik/http/middlewares/Middleware07/digestAuth/users/0` | `foobar` | -| `traefik/http/middlewares/Middleware07/digestAuth/users/1` | `foobar` | -| `traefik/http/middlewares/Middleware07/digestAuth/usersFile` | `foobar` | -| `traefik/http/middlewares/Middleware08/errors/query` | `foobar` | -| `traefik/http/middlewares/Middleware08/errors/service` | `foobar` | -| `traefik/http/middlewares/Middleware08/errors/status/0` | `foobar` | -| `traefik/http/middlewares/Middleware08/errors/status/1` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/address` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/authRequestHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/authRequestHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/authResponseHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/authResponseHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/authResponseHeadersRegex` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/tls/ca` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/tls/cert` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/tls/insecureSkipVerify` | `true` | -| `traefik/http/middlewares/Middleware09/forwardAuth/tls/key` | `foobar` | -| `traefik/http/middlewares/Middleware09/forwardAuth/trustForwardHeader` | `true` | -| `traefik/http/middlewares/Middleware10/headers/accessControlAllowCredentials` | `true` | -| `traefik/http/middlewares/Middleware10/headers/accessControlAllowHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlAllowHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlAllowMethods/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlAllowMethods/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlAllowOriginList/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlAllowOriginList/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlAllowOriginListRegex/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlAllowOriginListRegex/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlExposeHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlExposeHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/accessControlMaxAge` | `42` | -| `traefik/http/middlewares/Middleware10/headers/addVaryHeader` | `true` | -| `traefik/http/middlewares/Middleware10/headers/allowedHosts/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/allowedHosts/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/browserXssFilter` | `true` | -| `traefik/http/middlewares/Middleware10/headers/contentSecurityPolicy` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/contentTypeNosniff` | `true` | -| `traefik/http/middlewares/Middleware10/headers/customBrowserXSSValue` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/customFrameOptionsValue` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/customRequestHeaders/name0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/customRequestHeaders/name1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/customResponseHeaders/name0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/customResponseHeaders/name1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/forceSTSHeader` | `true` | -| `traefik/http/middlewares/Middleware10/headers/frameDeny` | `true` | -| `traefik/http/middlewares/Middleware10/headers/hostsProxyHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/hostsProxyHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/isDevelopment` | `true` | -| `traefik/http/middlewares/Middleware10/headers/permissionsPolicy` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/publicKey` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/referrerPolicy` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/sslProxyHeaders/name0` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/sslProxyHeaders/name1` | `foobar` | -| `traefik/http/middlewares/Middleware10/headers/stsIncludeSubdomains` | `true` | -| `traefik/http/middlewares/Middleware10/headers/stsPreload` | `true` | -| `traefik/http/middlewares/Middleware10/headers/stsSeconds` | `42` | -| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware11/ipAllowList/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware11/ipAllowList/sourceRange/0` | `foobar` | -| `traefik/http/middlewares/Middleware11/ipAllowList/sourceRange/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/inFlightReq/amount` | `42` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` | -| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/requestHost` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/commonName` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/country` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/domainComponent` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/locality` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/organization` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/province` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/serialNumber` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/notAfter` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/notBefore` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/sans` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/serialNumber` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/commonName` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/country` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/domainComponent` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/locality` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/organization` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/organizationalUnit` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/province` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/serialNumber` | `true` | -| `traefik/http/middlewares/Middleware13/passTLSClientCert/pem` | `true` | -| `traefik/http/middlewares/Middleware14/plugin/PluginConf/foo` | `bar` | -| `traefik/http/middlewares/Middleware15/rateLimit/average` | `42` | -| `traefik/http/middlewares/Middleware15/rateLimit/burst` | `42` | -| `traefik/http/middlewares/Middleware15/rateLimit/period` | `42s` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/requestHeaderName` | `foobar` | -| `traefik/http/middlewares/Middleware15/rateLimit/sourceCriterion/requestHost` | `true` | -| `traefik/http/middlewares/Middleware16/redirectRegex/permanent` | `true` | -| `traefik/http/middlewares/Middleware16/redirectRegex/regex` | `foobar` | -| `traefik/http/middlewares/Middleware16/redirectRegex/replacement` | `foobar` | -| `traefik/http/middlewares/Middleware17/redirectScheme/permanent` | `true` | -| `traefik/http/middlewares/Middleware17/redirectScheme/port` | `foobar` | -| `traefik/http/middlewares/Middleware17/redirectScheme/scheme` | `foobar` | -| `traefik/http/middlewares/Middleware18/replacePath/path` | `foobar` | -| `traefik/http/middlewares/Middleware19/replacePathRegex/regex` | `foobar` | -| `traefik/http/middlewares/Middleware19/replacePathRegex/replacement` | `foobar` | -| `traefik/http/middlewares/Middleware20/retry/attempts` | `42` | -| `traefik/http/middlewares/Middleware20/retry/initialInterval` | `42s` | -| `traefik/http/middlewares/Middleware21/stripPrefix/prefixes/0` | `foobar` | -| `traefik/http/middlewares/Middleware21/stripPrefix/prefixes/1` | `foobar` | -| `traefik/http/middlewares/Middleware22/stripPrefixRegex/regex/0` | `foobar` | -| `traefik/http/middlewares/Middleware22/stripPrefixRegex/regex/1` | `foobar` | -| `traefik/http/middlewares/Middleware23/grpcWeb/allowOrigins/0` | `foobar` | -| `traefik/http/middlewares/Middleware23/grpcWeb/allowOrigins/1` | `foobar` | + +| `traefik/http/middlewares/Middleware01/addPrefix/prefix` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/headerField` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/realm` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/removeHeader` | `true` | +| `traefik/http/middlewares/Middleware02/basicAuth/users/0` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/users/1` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/usersFile` | `foobar` | +| `traefik/http/middlewares/Middleware03/buffering/maxRequestBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware03/buffering/maxResponseBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware03/buffering/memRequestBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware03/buffering/memResponseBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware03/buffering/retryExpression` | `foobar` | +| `traefik/http/middlewares/Middleware04/chain/middlewares/0` | `foobar` | +| `traefik/http/middlewares/Middleware04/chain/middlewares/1` | `foobar` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/checkPeriod` | `42s` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/expression` | `foobar` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/fallbackDuration` | `42s` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` | +| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/1` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/minResponseBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware07/contentType` | `` | +| `traefik/http/middlewares/Middleware08/digestAuth/headerField` | `foobar` | +| `traefik/http/middlewares/Middleware08/digestAuth/realm` | `foobar` | +| `traefik/http/middlewares/Middleware08/digestAuth/removeHeader` | `true` | +| `traefik/http/middlewares/Middleware08/digestAuth/users/0` | `foobar` | +| `traefik/http/middlewares/Middleware08/digestAuth/users/1` | `foobar` | +| `traefik/http/middlewares/Middleware08/digestAuth/usersFile` | `foobar` | +| `traefik/http/middlewares/Middleware09/errors/query` | `foobar` | +| `traefik/http/middlewares/Middleware09/errors/service` | `foobar` | +| `traefik/http/middlewares/Middleware09/errors/status/0` | `foobar` | +| `traefik/http/middlewares/Middleware09/errors/status/1` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/addAuthCookiesToResponse/0` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/addAuthCookiesToResponse/1` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/address` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authRequestHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authRequestHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeadersRegex` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/ca` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/cert` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/insecureSkipVerify` | `true` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/key` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/trustForwardHeader` | `true` | +| `traefik/http/middlewares/Middleware11/grpcWeb/allowOrigins/0` | `foobar` | +| `traefik/http/middlewares/Middleware11/grpcWeb/allowOrigins/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowCredentials` | `true` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowMethods/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowMethods/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginList/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginList/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginListRegex/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginListRegex/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlExposeHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlExposeHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlMaxAge` | `42` | +| `traefik/http/middlewares/Middleware12/headers/addVaryHeader` | `true` | +| `traefik/http/middlewares/Middleware12/headers/allowedHosts/0` | `foobar` | +| `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/contentTypeNosniff` | `true` | +| `traefik/http/middlewares/Middleware12/headers/customBrowserXSSValue` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customFrameOptionsValue` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customRequestHeaders/name0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customRequestHeaders/name1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/forceSTSHeader` | `true` | +| `traefik/http/middlewares/Middleware12/headers/frameDeny` | `true` | +| `traefik/http/middlewares/Middleware12/headers/hostsProxyHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/hostsProxyHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/isDevelopment` | `true` | +| `traefik/http/middlewares/Middleware12/headers/permissionsPolicy` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/publicKey` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/referrerPolicy` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/stsIncludeSubdomains` | `true` | +| `traefik/http/middlewares/Middleware12/headers/stsPreload` | `true` | +| `traefik/http/middlewares/Middleware12/headers/stsSeconds` | `42` | +| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware13/ipAllowList/rejectStatusCode` | `42` | +| `traefik/http/middlewares/Middleware13/ipAllowList/sourceRange/0` | `foobar` | +| `traefik/http/middlewares/Middleware13/ipAllowList/sourceRange/1` | `foobar` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/sourceRange/0` | `foobar` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/sourceRange/1` | `foobar` | +| `traefik/http/middlewares/Middleware15/inFlightReq/amount` | `42` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/requestHost` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/commonName` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/country` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/domainComponent` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/locality` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/organization` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/province` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/serialNumber` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/notAfter` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/notBefore` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/sans` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/serialNumber` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/commonName` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/country` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/domainComponent` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/locality` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/organization` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/organizationalUnit` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/province` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/serialNumber` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/pem` | `true` | +| `traefik/http/middlewares/Middleware17/plugin/PluginConf0/name0` | `foobar` | +| `traefik/http/middlewares/Middleware17/plugin/PluginConf0/name1` | `foobar` | +| `traefik/http/middlewares/Middleware17/plugin/PluginConf1/name0` | `foobar` | +| `traefik/http/middlewares/Middleware17/plugin/PluginConf1/name1` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/average` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/burst` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/period` | `42s` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/requestHeaderName` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/requestHost` | `true` | +| `traefik/http/middlewares/Middleware19/redirectRegex/permanent` | `true` | +| `traefik/http/middlewares/Middleware19/redirectRegex/regex` | `foobar` | +| `traefik/http/middlewares/Middleware19/redirectRegex/replacement` | `foobar` | +| `traefik/http/middlewares/Middleware20/redirectScheme/permanent` | `true` | +| `traefik/http/middlewares/Middleware20/redirectScheme/port` | `foobar` | +| `traefik/http/middlewares/Middleware20/redirectScheme/scheme` | `foobar` | +| `traefik/http/middlewares/Middleware21/replacePath/path` | `foobar` | +| `traefik/http/middlewares/Middleware22/replacePathRegex/regex` | `foobar` | +| `traefik/http/middlewares/Middleware22/replacePathRegex/replacement` | `foobar` | +| `traefik/http/middlewares/Middleware23/retry/attempts` | `42` | +| `traefik/http/middlewares/Middleware23/retry/initialInterval` | `42s` | +| `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/0` | `foobar` | +| `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/1` | `foobar` | +| `traefik/http/middlewares/Middleware25/stripPrefixRegex/regex/0` | `foobar` | +| `traefik/http/middlewares/Middleware25/stripPrefixRegex/regex/1` | `foobar` | | `traefik/http/routers/Router0/entryPoints/0` | `foobar` | | `traefik/http/routers/Router0/entryPoints/1` | `foobar` | | `traefik/http/routers/Router0/middlewares/0` | `foobar` | @@ -203,49 +220,53 @@ | `traefik/http/serversTransports/ServersTransport1/spiffe/ids/0` | `foobar` | | `traefik/http/serversTransports/ServersTransport1/spiffe/ids/1` | `foobar` | | `traefik/http/serversTransports/ServersTransport1/spiffe/trustDomain` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/followRedirects` | `true` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/hostname` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/interval` | `42s` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/method` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/mode` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/path` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/port` | `42` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/scheme` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/status` | `42` | -| `traefik/http/services/Service01/loadBalancer/healthCheck/timeout` | `42s` | -| `traefik/http/services/Service01/loadBalancer/passHostHeader` | `true` | -| `traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval` | `42s` | -| `traefik/http/services/Service01/loadBalancer/servers/0/url` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/servers/1/url` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/serversTransport` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/sticky/cookie/httpOnly` | `true` | -| `traefik/http/services/Service01/loadBalancer/sticky/cookie/name` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/sticky/cookie/sameSite` | `foobar` | -| `traefik/http/services/Service01/loadBalancer/sticky/cookie/secure` | `true` | -| `traefik/http/services/Service02/mirroring/healthCheck` | `` | -| `traefik/http/services/Service02/mirroring/maxBodySize` | `42` | -| `traefik/http/services/Service02/mirroring/mirrors/0/name` | `foobar` | -| `traefik/http/services/Service02/mirroring/mirrors/0/percent` | `42` | -| `traefik/http/services/Service02/mirroring/mirrors/1/name` | `foobar` | -| `traefik/http/services/Service02/mirroring/mirrors/1/percent` | `42` | -| `traefik/http/services/Service02/mirroring/service` | `foobar` | -| `traefik/http/services/Service03/weighted/healthCheck` | `` | -| `traefik/http/services/Service03/weighted/services/0/name` | `foobar` | -| `traefik/http/services/Service03/weighted/services/0/weight` | `42` | -| `traefik/http/services/Service03/weighted/services/1/name` | `foobar` | -| `traefik/http/services/Service03/weighted/services/1/weight` | `42` | -| `traefik/http/services/Service03/weighted/sticky/cookie/httpOnly` | `true` | -| `traefik/http/services/Service03/weighted/sticky/cookie/name` | `foobar` | -| `traefik/http/services/Service03/weighted/sticky/cookie/sameSite` | `foobar` | -| `traefik/http/services/Service03/weighted/sticky/cookie/secure` | `true` | -| `traefik/http/services/Service04/failover/fallback` | `foobar` | -| `traefik/http/services/Service04/failover/healthCheck` | `` | -| `traefik/http/services/Service04/failover/service` | `foobar` | -| `traefik/tcp/middlewares/TCPMiddleware00/ipAllowList/sourceRange/0` | `foobar` | -| `traefik/tcp/middlewares/TCPMiddleware00/ipAllowList/sourceRange/1` | `foobar` | -| `traefik/tcp/middlewares/TCPMiddleware01/inFlightConn/amount` | `42` | +| `traefik/http/services/Service01/failover/fallback` | `foobar` | +| `traefik/http/services/Service01/failover/healthCheck` | `` | +| `traefik/http/services/Service01/failover/service` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/followRedirects` | `true` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/headers/name0` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/headers/name1` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/hostname` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/interval` | `42s` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/method` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/mode` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/path` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/port` | `42` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/scheme` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/status` | `42` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/timeout` | `42s` | +| `traefik/http/services/Service02/loadBalancer/passHostHeader` | `true` | +| `traefik/http/services/Service02/loadBalancer/responseForwarding/flushInterval` | `42s` | +| `traefik/http/services/Service02/loadBalancer/servers/0/url` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/servers/1/url` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/serversTransport` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/httpOnly` | `true` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/maxAge` | `42` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/name` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/sameSite` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` | +| `traefik/http/services/Service03/mirroring/healthCheck` | `` | +| `traefik/http/services/Service03/mirroring/maxBodySize` | `42` | +| `traefik/http/services/Service03/mirroring/mirrors/0/name` | `foobar` | +| `traefik/http/services/Service03/mirroring/mirrors/0/percent` | `42` | +| `traefik/http/services/Service03/mirroring/mirrors/1/name` | `foobar` | +| `traefik/http/services/Service03/mirroring/mirrors/1/percent` | `42` | +| `traefik/http/services/Service03/mirroring/service` | `foobar` | +| `traefik/http/services/Service04/weighted/healthCheck` | `` | +| `traefik/http/services/Service04/weighted/services/0/name` | `foobar` | +| `traefik/http/services/Service04/weighted/services/0/weight` | `42` | +| `traefik/http/services/Service04/weighted/services/1/name` | `foobar` | +| `traefik/http/services/Service04/weighted/services/1/weight` | `42` | +| `traefik/http/services/Service04/weighted/sticky/cookie/httpOnly` | `true` | +| `traefik/http/services/Service04/weighted/sticky/cookie/maxAge` | `42` | +| `traefik/http/services/Service04/weighted/sticky/cookie/name` | `foobar` | +| `traefik/http/services/Service04/weighted/sticky/cookie/sameSite` | `foobar` | +| `traefik/http/services/Service04/weighted/sticky/cookie/secure` | `true` | +| `traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/0` | `foobar` | +| `traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/1` | `foobar` | +| `traefik/tcp/middlewares/TCPMiddleware02/ipWhiteList/sourceRange/0` | `foobar` | +| `traefik/tcp/middlewares/TCPMiddleware02/ipWhiteList/sourceRange/1` | `foobar` | +| `traefik/tcp/middlewares/TCPMiddleware03/inFlightConn/amount` | `42` | | `traefik/tcp/routers/TCPRouter0/entryPoints/0` | `foobar` | | `traefik/tcp/routers/TCPRouter0/entryPoints/1` | `foobar` | | `traefik/tcp/routers/TCPRouter0/middlewares/0` | `foobar` | @@ -280,9 +301,6 @@ | `traefik/tcp/routers/TCPRouter1/tls/passthrough` | `true` | | `traefik/tcp/serversTransports/TCPServersTransport0/dialKeepAlive` | `42s` | | `traefik/tcp/serversTransports/TCPServersTransport0/dialTimeout` | `42s` | -| `traefik/tcp/serversTransports/TCPServersTransport0/spiffe/ids/0` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/spiffe/ids/1` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/spiffe/trustDomain` | `foobar` | | `traefik/tcp/serversTransports/TCPServersTransport0/terminationDelay` | `42s` | | `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/0/certFile` | `foobar` | | `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/0/keyFile` | `foobar` | @@ -293,11 +311,11 @@ | `traefik/tcp/serversTransports/TCPServersTransport0/tls/rootCAs/0` | `foobar` | | `traefik/tcp/serversTransports/TCPServersTransport0/tls/rootCAs/1` | `foobar` | | `traefik/tcp/serversTransports/TCPServersTransport0/tls/serverName` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/spiffe/ids/0` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/spiffe/ids/1` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/spiffe/trustDomain` | `foobar` | | `traefik/tcp/serversTransports/TCPServersTransport1/dialKeepAlive` | `42s` | | `traefik/tcp/serversTransports/TCPServersTransport1/dialTimeout` | `42s` | -| `traefik/tcp/serversTransports/TCPServersTransport1/spiffe/ids/0` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/spiffe/ids/1` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/spiffe/trustDomain` | `foobar` | | `traefik/tcp/serversTransports/TCPServersTransport1/terminationDelay` | `42s` | | `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/0/certFile` | `foobar` | | `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/0/keyFile` | `foobar` | @@ -308,6 +326,9 @@ | `traefik/tcp/serversTransports/TCPServersTransport1/tls/rootCAs/0` | `foobar` | | `traefik/tcp/serversTransports/TCPServersTransport1/tls/rootCAs/1` | `foobar` | | `traefik/tcp/serversTransports/TCPServersTransport1/tls/serverName` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/spiffe/ids/0` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/spiffe/ids/1` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/spiffe/trustDomain` | `foobar` | | `traefik/tcp/services/TCPService01/loadBalancer/proxyProtocol/version` | `42` | | `traefik/tcp/services/TCPService01/loadBalancer/servers/0/address` | `foobar` | | `traefik/tcp/services/TCPService01/loadBalancer/servers/0/tls` | `true` | diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index 8ea5b28c2..41628b58a 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: ingressroutes.traefik.io spec: group: traefik.io @@ -162,6 +160,12 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When + set to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -267,9 +271,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml index 7acb7cd5f..94226e14c 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: ingressroutetcps.traefik.io spec: group: traefik.io @@ -211,9 +209,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml index 59585a453..76ead3b92 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: ingressrouteudps.traefik.io spec: group: traefik.io @@ -97,9 +95,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 67b9e0b5d..b7d8c6b52 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: middlewares.traefik.io spec: group: traefik.io @@ -177,6 +175,13 @@ spec: items: type: string type: array + includedContentTypes: + description: IncludedContentTypes defines the list of content + types to compare the Content-Type header of the responses before + compressing. + items: + type: string + type: array minResponseBodyBytes: description: 'MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed. Default: @@ -297,6 +302,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -339,6 +350,12 @@ spec: This middleware delegates the request authentication to a Service. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/' properties: + addAuthCookiesToResponse: + description: AddAuthCookiesToResponse defines the list of cookies + to copy from the authentication server response to the response. + items: + type: string + type: array address: description: Address defines the authentication server address. type: string @@ -591,6 +608,36 @@ spec: description: 'IPAllowList holds the IP allowlist middleware configuration. This middleware accepts / refuses requests based on the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/' + properties: + ipStrategy: + description: 'IPStrategy holds the IP strategy configuration used + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position (starting + from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the X-Forwarded-For + header and select the first IP not in the list. + items: + type: string + type: array + type: object + rejectStatusCode: + description: RejectStatusCode defines the HTTP status code used + for refused requests. If not set, the default is 403 (Forbidden). + type: integer + sourceRange: + description: SourceRange defines the set of allowed IPs (or ranges + of allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object + ipWhiteList: + description: 'Deprecated: please use IPAllowList instead.' properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration used @@ -894,9 +941,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml index ec5ee7e68..616e48b9c 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: middlewaretcps.traefik.io spec: group: traefik.io @@ -57,6 +55,17 @@ spec: type: string type: array type: object + ipWhiteList: + description: 'IPWhiteList defines the IPWhiteList middleware configuration. + Deprecated: please use IPAllowList instead.' + properties: + sourceRange: + description: SourceRange defines the allowed IPs (or ranges of + allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object type: object required: - metadata @@ -64,9 +73,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml index fff9f7006..3ac912f6f 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: serverstransports.traefik.io spec: group: traefik.io @@ -133,9 +131,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml index 10e0a3f0e..22b76caa2 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: serverstransporttcps.traefik.io spec: group: traefik.io @@ -114,9 +112,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml index ddf525ceb..925e4c025 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: tlsoptions.traefik.io spec: group: traefik.io @@ -99,9 +97,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml b/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml index 61d7063c0..efafd398c 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: tlsstores.traefik.io spec: group: traefik.io @@ -91,9 +89,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index 870dfdf53..8620b4d74 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: traefikservices.traefik.io spec: group: traefik.io @@ -136,6 +134,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -227,6 +231,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -334,6 +344,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -373,6 +389,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -394,9 +416,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 030d8fa2c..aabed9139 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -177,6 +177,12 @@ Trust all. (Default: ```false```) `--entrypoints..proxyprotocol.trustedips`: Trust only selected IPs. +`--entrypoints..transport.keepalivemaxrequests`: +Maximum number of requests before closing a keep-alive connection. (Default: ```0```) + +`--entrypoints..transport.keepalivemaxtime`: +Maximum duration before closing a keep-alive connection. (Default: ```0```) + `--entrypoints..transport.lifecycle.gracetimeout`: Duration to give active requests a chance to finish before Traefik stops. (Default: ```10```) @@ -195,9 +201,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I `--entrypoints..udp.timeout`: Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) -`--experimental.http3`: -Enable HTTP3. (Default: ```false```) - `--experimental.kubernetesgateway`: Allow the Kubernetes gateway api provider usage. (Default: ```false```) @@ -217,7 +220,7 @@ plugin's version. Periodically check if a new version has been released. (Default: ```true```) `--global.sendanonymoususage`: -Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default. (Default: ```false```) +Periodically send anonymous usage statistics. If the option is not specified, it will be disabled by default. (Default: ```false```) `--hostresolver`: Enable CNAME Flattening. (Default: ```false```) @@ -688,7 +691,7 @@ Kubernetes namespaces. Ingress refresh throttle duration (Default: ```0```) `--providers.kubernetescrd.token`: -Kubernetes bearer token (not needed for in-cluster client). +Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. `--providers.kubernetesgateway`: Enable Kubernetes gateway api provider with default settings. (Default: ```false```) @@ -709,7 +712,7 @@ Kubernetes namespaces. Kubernetes refresh throttle duration (Default: ```0```) `--providers.kubernetesgateway.token`: -Kubernetes bearer token (not needed for in-cluster client). +Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. `--providers.kubernetesingress`: Enable Kubernetes backend with default settings. (Default: ```false```) @@ -751,7 +754,7 @@ Kubernetes namespaces. Ingress refresh throttle duration (Default: ```0```) `--providers.kubernetesingress.token`: -Kubernetes bearer token (not needed for in-cluster client). +Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. `--providers.nomad`: Enable Nomad backend with default settings. (Default: ```false```) @@ -822,6 +825,27 @@ Password for authentication. `--providers.redis.rootkey`: Root key used for KV store. (Default: ```traefik```) +`--providers.redis.sentinel.latencystrategy`: +Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy). (Default: ```false```) + +`--providers.redis.sentinel.mastername`: +Name of the master. + +`--providers.redis.sentinel.password`: +Password for Sentinel authentication. + +`--providers.redis.sentinel.randomstrategy`: +Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy). (Default: ```false```) + +`--providers.redis.sentinel.replicastrategy`: +Defines whether to route all commands to replica nodes (mutually exclusive with LatencyStrategy and RandomStrategy). (Default: ```false```) + +`--providers.redis.sentinel.usedisconnectedreplicas`: +Use replicas disconnected with master when cannot get connected replicas. (Default: ```false```) + +`--providers.redis.sentinel.username`: +Username for Sentinel authentication. + `--providers.redis.tls.ca`: TLS CA @@ -963,170 +987,50 @@ Defines the allowed SPIFFE trust domain. `--tracing`: OpenTracing configuration. (Default: ```false```) -`--tracing.datadog`: -Settings for Datadog. (Default: ```false```) +`--tracing.globalattributes.`: +Defines additional attributes (key:value) on all spans. -`--tracing.datadog.bagageprefixheadername`: -Sets the header name prefix used to store baggage items in a map. - -`--tracing.datadog.debug`: -Enables Datadog debug. (Default: ```false```) - -`--tracing.datadog.globaltags.`: -Sets a list of key:value tags on all spans. - -`--tracing.datadog.localagenthostport`: -Sets the Datadog Agent host:port. (Default: ```localhost:8126```) - -`--tracing.datadog.localagentsocket`: -Sets the socket for the Datadog Agent. - -`--tracing.datadog.parentidheadername`: -Sets the header name used to store the parent ID. - -`--tracing.datadog.prioritysampling`: -Enables priority sampling. When using distributed tracing, this option must be enabled in order to get all the parts of a distributed trace sampled. (Default: ```false```) - -`--tracing.datadog.samplingpriorityheadername`: -Sets the header name used to store the sampling priority. - -`--tracing.datadog.traceidheadername`: -Sets the header name used to store the trace ID. - -`--tracing.elastic`: -Settings for Elastic. (Default: ```false```) - -`--tracing.elastic.secrettoken`: -Sets the token used to connect to Elastic APM Server. - -`--tracing.elastic.serverurl`: -Sets the URL of the Elastic APM server. - -`--tracing.elastic.serviceenvironment`: -Sets the name of the environment Traefik is deployed in, e.g. 'production' or 'staging'. - -`--tracing.haystack`: -Settings for Haystack. (Default: ```false```) - -`--tracing.haystack.baggageprefixheadername`: -Sets the header name prefix used to store baggage items in a map. - -`--tracing.haystack.globaltag`: -Sets a key:value tag on all spans. - -`--tracing.haystack.localagenthost`: -Sets the Haystack Agent host. (Default: ```127.0.0.1```) - -`--tracing.haystack.localagentport`: -Sets the Haystack Agent port. (Default: ```35000```) - -`--tracing.haystack.parentidheadername`: -Sets the header name used to store the parent ID. - -`--tracing.haystack.spanidheadername`: -Sets the header name used to store the span ID. - -`--tracing.haystack.traceidheadername`: -Sets the header name used to store the trace ID. - -`--tracing.instana`: -Settings for Instana. (Default: ```false```) - -`--tracing.instana.enableautoprofile`: -Enables automatic profiling for the Traefik process. (Default: ```false```) - -`--tracing.instana.localagenthost`: -Sets the Instana Agent host. - -`--tracing.instana.localagentport`: -Sets the Instana Agent port. (Default: ```42699```) - -`--tracing.instana.loglevel`: -Sets the log level for the Instana tracer. ('error','warn','info','debug') (Default: ```info```) - -`--tracing.jaeger`: -Settings for Jaeger. (Default: ```false```) - -`--tracing.jaeger.collector.endpoint`: -Instructs reporter to send spans to jaeger-collector at this URL. - -`--tracing.jaeger.collector.password`: -Password for basic http authentication when sending spans to jaeger-collector. - -`--tracing.jaeger.collector.user`: -User for basic http authentication when sending spans to jaeger-collector. - -`--tracing.jaeger.disableattemptreconnecting`: -Disables the periodic re-resolution of the agent's hostname and reconnection if there was a change. (Default: ```true```) - -`--tracing.jaeger.gen128bit`: -Generates 128 bits span IDs. (Default: ```false```) - -`--tracing.jaeger.localagenthostport`: -Sets the Jaeger Agent host:port. (Default: ```127.0.0.1:6831```) - -`--tracing.jaeger.propagation`: -Sets the propagation format (jaeger/b3). (Default: ```jaeger```) - -`--tracing.jaeger.samplingparam`: -Sets the sampling parameter. (Default: ```1.000000```) - -`--tracing.jaeger.samplingserverurl`: -Sets the sampling server URL. (Default: ```http://localhost:5778/sampling```) - -`--tracing.jaeger.samplingtype`: -Sets the sampling type. (Default: ```const```) - -`--tracing.jaeger.tracecontextheadername`: -Sets the header name used to store the trace ID. (Default: ```uber-trace-id```) - -`--tracing.opentelemetry`: -Settings for OpenTelemetry. (Default: ```false```) - -`--tracing.opentelemetry.address`: -Sets the address (host:port) of the collector endpoint. (Default: ```localhost:4318```) - -`--tracing.opentelemetry.grpc`: -gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```) - -`--tracing.opentelemetry.headers.`: +`--tracing.headers.`: Defines additional headers to be sent with the payloads. -`--tracing.opentelemetry.insecure`: +`--tracing.otlp`: +Settings for OpenTelemetry. (Default: ```false```) + +`--tracing.otlp.grpc.endpoint`: +Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) + +`--tracing.otlp.grpc.insecure`: Disables client transport security for the exporter. (Default: ```false```) -`--tracing.opentelemetry.path`: -Sets the URL path of the collector endpoint. - -`--tracing.opentelemetry.tls.ca`: +`--tracing.otlp.grpc.tls.ca`: TLS CA -`--tracing.opentelemetry.tls.cert`: +`--tracing.otlp.grpc.tls.cert`: TLS cert -`--tracing.opentelemetry.tls.insecureskipverify`: +`--tracing.otlp.grpc.tls.insecureskipverify`: TLS insecure skip verify (Default: ```false```) -`--tracing.opentelemetry.tls.key`: +`--tracing.otlp.grpc.tls.key`: TLS key +`--tracing.otlp.http.endpoint`: +Sets the HTTP endpoint (scheme://host:port/v1/traces) of the collector. (Default: ```localhost:4318```) + +`--tracing.otlp.http.tls.ca`: +TLS CA + +`--tracing.otlp.http.tls.cert`: +TLS cert + +`--tracing.otlp.http.tls.insecureskipverify`: +TLS insecure skip verify (Default: ```false```) + +`--tracing.otlp.http.tls.key`: +TLS key + +`--tracing.samplerate`: +Sets the rate between 0.0 and 1.0 of requests to trace. (Default: ```1.000000```) + `--tracing.servicename`: Set the name for this service. (Default: ```traefik```) - -`--tracing.spannamelimit`: -Set the maximum character limit for Span names (default 0 = no limit). (Default: ```0```) - -`--tracing.zipkin`: -Settings for Zipkin. (Default: ```false```) - -`--tracing.zipkin.httpendpoint`: -Sets the HTTP Endpoint to report traces to. (Default: ```http://localhost:9411/api/v2/spans```) - -`--tracing.zipkin.id128bit`: -Uses 128 bits root span IDs. (Default: ```true```) - -`--tracing.zipkin.samespan`: -Uses SameSpan RPC style traces. (Default: ```false```) - -`--tracing.zipkin.samplerate`: -Sets the rate between 0.0 and 1.0 of requests to trace. (Default: ```1.000000```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index cec5bd2a4..892fb2369 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -177,6 +177,12 @@ Trust all. (Default: ```false```) `TRAEFIK_ENTRYPOINTS__PROXYPROTOCOL_TRUSTEDIPS`: Trust only selected IPs. +`TRAEFIK_ENTRYPOINTS__TRANSPORT_KEEPALIVEMAXREQUESTS`: +Maximum number of requests before closing a keep-alive connection. (Default: ```0```) + +`TRAEFIK_ENTRYPOINTS__TRANSPORT_KEEPALIVEMAXTIME`: +Maximum duration before closing a keep-alive connection. (Default: ```0```) + `TRAEFIK_ENTRYPOINTS__TRANSPORT_LIFECYCLE_GRACETIMEOUT`: Duration to give active requests a chance to finish before Traefik stops. (Default: ```10```) @@ -195,9 +201,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I `TRAEFIK_ENTRYPOINTS__UDP_TIMEOUT`: Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) -`TRAEFIK_EXPERIMENTAL_HTTP3`: -Enable HTTP3. (Default: ```false```) - `TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`: Allow the Kubernetes gateway api provider usage. (Default: ```false```) @@ -217,7 +220,7 @@ plugin's version. Periodically check if a new version has been released. (Default: ```true```) `TRAEFIK_GLOBAL_SENDANONYMOUSUSAGE`: -Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default. (Default: ```false```) +Periodically send anonymous usage statistics. If the option is not specified, it will be disabled by default. (Default: ```false```) `TRAEFIK_HOSTRESOLVER`: Enable CNAME Flattening. (Default: ```false```) @@ -688,7 +691,7 @@ Kubernetes namespaces. Ingress refresh throttle duration (Default: ```0```) `TRAEFIK_PROVIDERS_KUBERNETESCRD_TOKEN`: -Kubernetes bearer token (not needed for in-cluster client). +Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. `TRAEFIK_PROVIDERS_KUBERNETESGATEWAY`: Enable Kubernetes gateway api provider with default settings. (Default: ```false```) @@ -709,7 +712,7 @@ Kubernetes namespaces. Kubernetes refresh throttle duration (Default: ```0```) `TRAEFIK_PROVIDERS_KUBERNETESGATEWAY_TOKEN`: -Kubernetes bearer token (not needed for in-cluster client). +Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. `TRAEFIK_PROVIDERS_KUBERNETESINGRESS`: Enable Kubernetes backend with default settings. (Default: ```false```) @@ -751,7 +754,7 @@ Kubernetes namespaces. Ingress refresh throttle duration (Default: ```0```) `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_TOKEN`: -Kubernetes bearer token (not needed for in-cluster client). +Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. `TRAEFIK_PROVIDERS_NOMAD`: Enable Nomad backend with default settings. (Default: ```false```) @@ -822,6 +825,27 @@ Password for authentication. `TRAEFIK_PROVIDERS_REDIS_ROOTKEY`: Root key used for KV store. (Default: ```traefik```) +`TRAEFIK_PROVIDERS_REDIS_SENTINEL_LATENCYSTRATEGY`: +Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy). (Default: ```false```) + +`TRAEFIK_PROVIDERS_REDIS_SENTINEL_MASTERNAME`: +Name of the master. + +`TRAEFIK_PROVIDERS_REDIS_SENTINEL_PASSWORD`: +Password for Sentinel authentication. + +`TRAEFIK_PROVIDERS_REDIS_SENTINEL_RANDOMSTRATEGY`: +Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy). (Default: ```false```) + +`TRAEFIK_PROVIDERS_REDIS_SENTINEL_REPLICASTRATEGY`: +Defines whether to route all commands to replica nodes (mutually exclusive with LatencyStrategy and RandomStrategy). (Default: ```false```) + +`TRAEFIK_PROVIDERS_REDIS_SENTINEL_USEDISCONNECTEDREPLICAS`: +Use replicas disconnected with master when cannot get connected replicas. (Default: ```false```) + +`TRAEFIK_PROVIDERS_REDIS_SENTINEL_USERNAME`: +Username for Sentinel authentication. + `TRAEFIK_PROVIDERS_REDIS_TLS_CA`: TLS CA @@ -963,170 +987,50 @@ Defines the allowed SPIFFE trust domain. `TRAEFIK_TRACING`: OpenTracing configuration. (Default: ```false```) -`TRAEFIK_TRACING_DATADOG`: -Settings for Datadog. (Default: ```false```) +`TRAEFIK_TRACING_GLOBALATTRIBUTES_`: +Defines additional attributes (key:value) on all spans. -`TRAEFIK_TRACING_DATADOG_BAGAGEPREFIXHEADERNAME`: -Sets the header name prefix used to store baggage items in a map. - -`TRAEFIK_TRACING_DATADOG_DEBUG`: -Enables Datadog debug. (Default: ```false```) - -`TRAEFIK_TRACING_DATADOG_GLOBALTAGS_`: -Sets a list of key:value tags on all spans. - -`TRAEFIK_TRACING_DATADOG_LOCALAGENTHOSTPORT`: -Sets the Datadog Agent host:port. (Default: ```localhost:8126```) - -`TRAEFIK_TRACING_DATADOG_LOCALAGENTSOCKET`: -Sets the socket for the Datadog Agent. - -`TRAEFIK_TRACING_DATADOG_PARENTIDHEADERNAME`: -Sets the header name used to store the parent ID. - -`TRAEFIK_TRACING_DATADOG_PRIORITYSAMPLING`: -Enables priority sampling. When using distributed tracing, this option must be enabled in order to get all the parts of a distributed trace sampled. (Default: ```false```) - -`TRAEFIK_TRACING_DATADOG_SAMPLINGPRIORITYHEADERNAME`: -Sets the header name used to store the sampling priority. - -`TRAEFIK_TRACING_DATADOG_TRACEIDHEADERNAME`: -Sets the header name used to store the trace ID. - -`TRAEFIK_TRACING_ELASTIC`: -Settings for Elastic. (Default: ```false```) - -`TRAEFIK_TRACING_ELASTIC_SECRETTOKEN`: -Sets the token used to connect to Elastic APM Server. - -`TRAEFIK_TRACING_ELASTIC_SERVERURL`: -Sets the URL of the Elastic APM server. - -`TRAEFIK_TRACING_ELASTIC_SERVICEENVIRONMENT`: -Sets the name of the environment Traefik is deployed in, e.g. 'production' or 'staging'. - -`TRAEFIK_TRACING_HAYSTACK`: -Settings for Haystack. (Default: ```false```) - -`TRAEFIK_TRACING_HAYSTACK_BAGGAGEPREFIXHEADERNAME`: -Sets the header name prefix used to store baggage items in a map. - -`TRAEFIK_TRACING_HAYSTACK_GLOBALTAG`: -Sets a key:value tag on all spans. - -`TRAEFIK_TRACING_HAYSTACK_LOCALAGENTHOST`: -Sets the Haystack Agent host. (Default: ```127.0.0.1```) - -`TRAEFIK_TRACING_HAYSTACK_LOCALAGENTPORT`: -Sets the Haystack Agent port. (Default: ```35000```) - -`TRAEFIK_TRACING_HAYSTACK_PARENTIDHEADERNAME`: -Sets the header name used to store the parent ID. - -`TRAEFIK_TRACING_HAYSTACK_SPANIDHEADERNAME`: -Sets the header name used to store the span ID. - -`TRAEFIK_TRACING_HAYSTACK_TRACEIDHEADERNAME`: -Sets the header name used to store the trace ID. - -`TRAEFIK_TRACING_INSTANA`: -Settings for Instana. (Default: ```false```) - -`TRAEFIK_TRACING_INSTANA_ENABLEAUTOPROFILE`: -Enables automatic profiling for the Traefik process. (Default: ```false```) - -`TRAEFIK_TRACING_INSTANA_LOCALAGENTHOST`: -Sets the Instana Agent host. - -`TRAEFIK_TRACING_INSTANA_LOCALAGENTPORT`: -Sets the Instana Agent port. (Default: ```42699```) - -`TRAEFIK_TRACING_INSTANA_LOGLEVEL`: -Sets the log level for the Instana tracer. ('error','warn','info','debug') (Default: ```info```) - -`TRAEFIK_TRACING_JAEGER`: -Settings for Jaeger. (Default: ```false```) - -`TRAEFIK_TRACING_JAEGER_COLLECTOR_ENDPOINT`: -Instructs reporter to send spans to jaeger-collector at this URL. - -`TRAEFIK_TRACING_JAEGER_COLLECTOR_PASSWORD`: -Password for basic http authentication when sending spans to jaeger-collector. - -`TRAEFIK_TRACING_JAEGER_COLLECTOR_USER`: -User for basic http authentication when sending spans to jaeger-collector. - -`TRAEFIK_TRACING_JAEGER_DISABLEATTEMPTRECONNECTING`: -Disables the periodic re-resolution of the agent's hostname and reconnection if there was a change. (Default: ```true```) - -`TRAEFIK_TRACING_JAEGER_GEN128BIT`: -Generates 128 bits span IDs. (Default: ```false```) - -`TRAEFIK_TRACING_JAEGER_LOCALAGENTHOSTPORT`: -Sets the Jaeger Agent host:port. (Default: ```127.0.0.1:6831```) - -`TRAEFIK_TRACING_JAEGER_PROPAGATION`: -Sets the propagation format (jaeger/b3). (Default: ```jaeger```) - -`TRAEFIK_TRACING_JAEGER_SAMPLINGPARAM`: -Sets the sampling parameter. (Default: ```1.000000```) - -`TRAEFIK_TRACING_JAEGER_SAMPLINGSERVERURL`: -Sets the sampling server URL. (Default: ```http://localhost:5778/sampling```) - -`TRAEFIK_TRACING_JAEGER_SAMPLINGTYPE`: -Sets the sampling type. (Default: ```const```) - -`TRAEFIK_TRACING_JAEGER_TRACECONTEXTHEADERNAME`: -Sets the header name used to store the trace ID. (Default: ```uber-trace-id```) - -`TRAEFIK_TRACING_OPENTELEMETRY`: -Settings for OpenTelemetry. (Default: ```false```) - -`TRAEFIK_TRACING_OPENTELEMETRY_ADDRESS`: -Sets the address (host:port) of the collector endpoint. (Default: ```localhost:4318```) - -`TRAEFIK_TRACING_OPENTELEMETRY_GRPC`: -gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```) - -`TRAEFIK_TRACING_OPENTELEMETRY_HEADERS_`: +`TRAEFIK_TRACING_HEADERS_`: Defines additional headers to be sent with the payloads. -`TRAEFIK_TRACING_OPENTELEMETRY_INSECURE`: +`TRAEFIK_TRACING_OTLP`: +Settings for OpenTelemetry. (Default: ```false```) + +`TRAEFIK_TRACING_OTLP_GRPC_ENDPOINT`: +Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) + +`TRAEFIK_TRACING_OTLP_GRPC_INSECURE`: Disables client transport security for the exporter. (Default: ```false```) -`TRAEFIK_TRACING_OPENTELEMETRY_PATH`: -Sets the URL path of the collector endpoint. - -`TRAEFIK_TRACING_OPENTELEMETRY_TLS_CA`: +`TRAEFIK_TRACING_OTLP_GRPC_TLS_CA`: TLS CA -`TRAEFIK_TRACING_OPENTELEMETRY_TLS_CERT`: +`TRAEFIK_TRACING_OTLP_GRPC_TLS_CERT`: TLS cert -`TRAEFIK_TRACING_OPENTELEMETRY_TLS_INSECURESKIPVERIFY`: +`TRAEFIK_TRACING_OTLP_GRPC_TLS_INSECURESKIPVERIFY`: TLS insecure skip verify (Default: ```false```) -`TRAEFIK_TRACING_OPENTELEMETRY_TLS_KEY`: +`TRAEFIK_TRACING_OTLP_GRPC_TLS_KEY`: TLS key +`TRAEFIK_TRACING_OTLP_HTTP_ENDPOINT`: +Sets the HTTP endpoint (scheme://host:port/v1/traces) of the collector. (Default: ```localhost:4318```) + +`TRAEFIK_TRACING_OTLP_HTTP_TLS_CA`: +TLS CA + +`TRAEFIK_TRACING_OTLP_HTTP_TLS_CERT`: +TLS cert + +`TRAEFIK_TRACING_OTLP_HTTP_TLS_INSECURESKIPVERIFY`: +TLS insecure skip verify (Default: ```false```) + +`TRAEFIK_TRACING_OTLP_HTTP_TLS_KEY`: +TLS key + +`TRAEFIK_TRACING_SAMPLERATE`: +Sets the rate between 0.0 and 1.0 of requests to trace. (Default: ```1.000000```) + `TRAEFIK_TRACING_SERVICENAME`: Set the name for this service. (Default: ```traefik```) - -`TRAEFIK_TRACING_SPANNAMELIMIT`: -Set the maximum character limit for Span names (default 0 = no limit). (Default: ```0```) - -`TRAEFIK_TRACING_ZIPKIN`: -Settings for Zipkin. (Default: ```false```) - -`TRAEFIK_TRACING_ZIPKIN_HTTPENDPOINT`: -Sets the HTTP Endpoint to report traces to. (Default: ```http://localhost:9411/api/v2/spans```) - -`TRAEFIK_TRACING_ZIPKIN_ID128BIT`: -Uses 128 bits root span IDs. (Default: ```true```) - -`TRAEFIK_TRACING_ZIPKIN_SAMESPAN`: -Uses SameSpan RPC style traces. (Default: ```false```) - -`TRAEFIK_TRACING_ZIPKIN_SAMPLERATE`: -Sets the rate between 0.0 and 1.0 of requests to trace. (Default: ```1.000000```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index a6b0f0bc0..3edf7ebc9 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -1,3 +1,5 @@ +## CODE GENERATED AUTOMATICALLY +## THIS FILE MUST NOT BE EDITED BY HAND [global] checkNewVersion = true sendAnonymousUsage = true @@ -6,26 +8,21 @@ insecureSkipVerify = true rootCAs = ["foobar", "foobar"] maxIdleConnsPerHost = 42 - [serversTransport.forwardingTimeouts] dialTimeout = "42s" responseHeaderTimeout = "42s" idleConnTimeout = "42s" - readIdleTimeout = "42s" - pingTimeout = "42s" - [serversTransport.spiffe] ids = ["foobar", "foobar"] trustDomain = "foobar" [tcpServersTransport] - dialTimeout = "42s" dialKeepAlive = "42s" - + dialTimeout = "42s" + terminationDelay = "42s" [tcpServersTransport.tls] insecureSkipVerify = true rootCAs = ["foobar", "foobar"] - [tcpServersTransport.tls.spiffe] ids = ["foobar", "foobar"] trustDomain = "foobar" @@ -35,6 +32,8 @@ address = "foobar" asDefault = true [entryPoints.EntryPoint0.transport] + keepAliveMaxTime = "42s" + keepAliveMaxRequests = 42 [entryPoints.EntryPoint0.transport.lifeCycle] requestAcceptGraceTimeout = "42s" graceTimeOut = "42s" @@ -78,31 +77,31 @@ [providers] providersThrottleDuration = "42s" [providers.docker] - constraints = "foobar" - watch = true - endpoint = "foobar" - defaultRule = "foobar" exposedByDefault = true - useBindPortIP = true - network = "foobar" - httpClientTimeout = "42s" + constraints = "foobar" allowEmptyServices = true + network = "foobar" + useBindPortIP = true + watch = true + defaultRule = "foobar" + endpoint = "foobar" + httpClientTimeout = "42s" [providers.docker.tls] ca = "foobar" cert = "foobar" key = "foobar" insecureSkipVerify = true [providers.swarm] - constraints = "foobar" - watch = true - endpoint = "foobar" - defaultRule = "foobar" exposedByDefault = true - useBindPortIP = true - network = "foobar" - refreshSeconds = "42s" - httpClientTimeout = "42s" + constraints = "foobar" allowEmptyServices = true + network = "foobar" + useBindPortIP = true + watch = true + defaultRule = "foobar" + endpoint = "foobar" + httpClientTimeout = "42s" + refreshSeconds = "42s" [providers.swarm.tls] ca = "foobar" cert = "foobar" @@ -181,9 +180,9 @@ constraints = "foobar" prefix = "foobar" stale = true - namespaces = ["foobar", "foobar"] exposedByDefault = true refreshInterval = "42s" + namespaces = ["foobar", "foobar"] [providers.nomad.endpoint] address = "foobar" region = "foobar" @@ -201,11 +200,11 @@ defaultRule = "foobar" clusters = ["foobar", "foobar"] autoDiscoverClusters = true + healthyTasksOnly = true + ecsAnywhere = true region = "foobar" accessKeyID = "foobar" secretAccessKey = "foobar" - ecsAnywhere = true - healthyTasksOnly = true [providers.consul] rootKey = "foobar" endpoints = ["foobar", "foobar"] @@ -242,6 +241,14 @@ cert = "foobar" key = "foobar" insecureSkipVerify = true + [providers.redis.sentinel] + masterName = "foobar" + username = "foobar" + password = "foobar" + latencyStrategy = true + randomStrategy = true + replicaStrategy = true + useDisconnectedReplicas = true [providers.http] endpoint = "foobar" pollInterval = "42s" @@ -255,14 +262,18 @@ key = "foobar" insecureSkipVerify = true [providers.plugin] - [providers.plugin.Descriptor0] - [providers.plugin.Descriptor1] + [providers.plugin.PluginConf0] + name0 = "foobar" + name1 = "foobar" + [providers.plugin.PluginConf1] + name0 = "foobar" + name1 = "foobar" [api] insecure = true dashboard = true debug = true - disabledashboardad = false + disableDashboardAd = true [metrics] [metrics.prometheus] @@ -273,8 +284,8 @@ entryPoint = "foobar" manualRouting = true [metrics.prometheus.headerLabels] - label1 = "foobar" - label2 = "foobar" + name0 = "foobar" + name1 = "foobar" [metrics.datadog] address = "foobar" pushInterval = "42s" @@ -306,20 +317,19 @@ addEntryPointsLabels = true addRoutersLabels = true addServicesLabels = true - pushInterval = "42s" - path = "foobar" - explicitBoundaries = [42.0, 42.0] + explicitBoundaries = [42.0, 42.0] insecure = true + path = "foobar" + pushInterval = "42s" + [metrics.openTelemetry.grpc] [metrics.openTelemetry.headers] name0 = "foobar" name1 = "foobar" [metrics.openTelemetry.tls] ca = "foobar" - caOptional = true cert = "foobar" - insecureSkipVerify = true key = "foobar" - [metrics.openTelemetry.grpc] + insecureSkipVerify = true [ping] entryPoint = "foobar" @@ -332,8 +342,8 @@ noColor = true filePath = "foobar" maxSize = 42 - maxBackups = 42 maxAge = 42 + maxBackups = 42 compress = true [accessLog] @@ -357,68 +367,29 @@ [tracing] serviceName = "foobar" - spanNameLimit = 42 - [tracing.jaeger] - samplingServerURL = "foobar" - samplingType = "foobar" - samplingParam = 42.0 - localAgentHostPort = "foobar" - gen128Bit = true - propagation = "foobar" - traceContextHeaderName = "foobar" - disableAttemptReconnecting = true - [tracing.jaeger.collector] + sampleRate = 42.0 + [tracing.headers] + name0 = "foobar" + name1 = "foobar" + [tracing.globalAttributes] + name0 = "foobar" + name1 = "foobar" + [tracing.otlp] + [tracing.otlp.grpc] endpoint = "foobar" - user = "foobar" - password = "foobar" - [tracing.zipkin] - httpEndpoint = "foobar" - sameSpan = true - id128Bit = true - sampleRate = 42.0 - [tracing.datadog] - localAgentHostPort = "foobar" - localAgentSocket = "foobar" - [tracing.datadog.globalTags] - tag1 = "foobar" - tag2 = "foobar" - debug = true - prioritySampling = true - traceIDHeaderName = "foobar" - parentIDHeaderName = "foobar" - samplingPriorityHeaderName = "foobar" - bagagePrefixHeaderName = "foobar" - [tracing.instana] - localAgentHost = "foobar" - localAgentPort = 42 - logLevel = "foobar" - enableAutoProfile = true - [tracing.haystack] - localAgentHost = "foobar" - localAgentPort = 42 - globalTag = "foobar" - traceIDHeaderName = "foobar" - parentIDHeaderName = "foobar" - spanIDHeaderName = "foobar" - baggagePrefixHeaderName = "foobar" - [tracing.elastic] - serverURL = "foobar" - secretToken = "foobar" - serviceEnvironment = "foobar" - [tracing.openTelemetry] - address = "foobar" - insecure = true - path = "foobar" - [tracing.openTelemetry.headers] - name0 = "foobar" - name1 = "foobar" - [tracing.openTelemetry.tls] - ca = "foobar" - caOptional = true - cert = "foobar" - key = "foobar" - insecureSkipVerify = true - [tracing.openTelemetry.grpc] + insecure = true + [tracing.otlp.grpc.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [tracing.otlp.http] + endpoint = "foobar" + [tracing.otlp.http.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true [hostResolver] cnameFlattening = true @@ -445,11 +416,30 @@ [certificatesResolvers.CertificateResolver0.acme.httpChallenge] entryPoint = "foobar" [certificatesResolvers.CertificateResolver0.acme.tlsChallenge] - [certificatesResolvers.CertificateResolver1.tailscale] + [certificatesResolvers.CertificateResolver0.tailscale] + [certificatesResolvers.CertificateResolver1] + [certificatesResolvers.CertificateResolver1.acme] + email = "foobar" + caServer = "foobar" + preferredChain = "foobar" + storage = "foobar" + keyType = "foobar" + certificatesDuration = 42 + [certificatesResolvers.CertificateResolver1.acme.eab] + kid = "foobar" + hmacEncoded = "foobar" + [certificatesResolvers.CertificateResolver1.acme.dnsChallenge] + provider = "foobar" + delayBeforeCheck = "42s" + resolvers = ["foobar", "foobar"] + disablePropagationCheck = true + [certificatesResolvers.CertificateResolver1.acme.httpChallenge] + entryPoint = "foobar" + [certificatesResolvers.CertificateResolver1.acme.tlsChallenge] + [certificatesResolvers.CertificateResolver1.tailscale] [experimental] kubernetesGateway = true - http3 = true [experimental.plugins] [experimental.plugins.Descriptor0] moduleName = "foobar" @@ -458,7 +448,10 @@ moduleName = "foobar" version = "foobar" [experimental.localPlugins] - [experimental.localPlugins.Descriptor0] + [experimental.localPlugins.LocalDescriptor0] moduleName = "foobar" - [experimental.localPlugins.Descriptor1] + [experimental.localPlugins.LocalDescriptor1] moduleName = "foobar" + +[spiffe] + workloadAPIAddr = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 7372f9a2a..a9ccffe9a 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -1,7 +1,9 @@ +## CODE GENERATED AUTOMATICALLY +## THIS FILE MUST NOT BE EDITED BY HAND global: checkNewVersion: true sendAnonymousUsage: true -serversTransports: +serversTransport: insecureSkipVerify: true rootCAs: - foobar @@ -11,16 +13,15 @@ serversTransports: dialTimeout: 42s responseHeaderTimeout: 42s idleConnTimeout: 42s - readIdleTimeout: 42s - pingTimeout: 42s spiffe: ids: - foobar - foobar trustDomain: foobar tcpServersTransport: - dialTimeout: 42s dialKeepAlive: 42s + dialTimeout: 42s + terminationDelay: 42s tls: insecureSkipVerify: true rootCAs: @@ -43,6 +44,8 @@ entryPoints: readTimeout: 42s writeTimeout: 42s idleTimeout: 42s + keepAliveMaxTime: 42s + keepAliveMaxRequests: 42 proxyProtocol: insecure: true trustedIPs: @@ -54,7 +57,6 @@ entryPoints: - foobar - foobar http: - encodeQuerySemicolons: true redirections: entryPoint: to: foobar @@ -76,6 +78,7 @@ entryPoints: sans: - foobar - foobar + encodeQuerySemicolons: true http2: maxConcurrentStreams: 42 http3: @@ -85,36 +88,36 @@ entryPoints: providers: providersThrottleDuration: 42s docker: + exposedByDefault: true constraints: foobar + allowEmptyServices: true + network: foobar + useBindPortIP: true watch: true - endpoint: foobar defaultRule: foobar + endpoint: foobar tls: ca: foobar cert: foobar key: foobar insecureSkipVerify: true - exposedByDefault: true - useBindPortIP: true - network: foobar httpClientTimeout: 42s - allowEmptyServices: true swarm: + exposedByDefault: true constraints: foobar + allowEmptyServices: true + network: foobar + useBindPortIP: true watch: true - endpoint: foobar defaultRule: foobar + endpoint: foobar tls: ca: foobar cert: foobar key: foobar insecureSkipVerify: true - exposedByDefault: true - useBindPortIP: true - network: foobar - refreshSeconds: 42s httpClientTimeout: 42s - allowEmptyServices: true + refreshSeconds: 42s file: directory: foobar watch: true @@ -129,14 +132,14 @@ providers: - foobar labelSelector: foobar ingressClass: foobar - throttleDuration: 42s - allowEmptyServices: true - allowExternalNameServices: true - disableIngressClassLookup: true ingressEndpoint: ip: foobar hostname: foobar publishedService: foobar + throttleDuration: 42s + allowEmptyServices: true + allowExternalNameServices: true + disableIngressClassLookup: true kubernetesCRD: endpoint: foobar token: foobar @@ -163,6 +166,20 @@ providers: insecure: true consulCatalog: constraints: foobar + endpoint: + address: foobar + scheme: foobar + datacenter: foobar + token: foobar + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + httpAuth: + username: foobar + password: foobar + endpointWaitTime: 42s prefix: foobar refreshInterval: 42s requireConsistent: true @@ -177,40 +194,26 @@ providers: namespaces: - foobar - foobar - endpoint: - address: foobar - scheme: foobar - datacenter: foobar - token: foobar - endpointWaitTime: 42s - tls: - ca: foobar - cert: foobar - key: foobar - insecureSkipVerify: true - httpAuth: - username: foobar - password: foobar nomad: defaultRule: foobar constraints: foobar - prefix: foobar - stale: true - namespaces: - - foobar - - foobar - exposedByDefault: true - refreshInterval: 42s endpoint: address: foobar region: foobar token: foobar - endpointWaitTime: 42s tls: ca: foobar cert: foobar key: foobar insecureSkipVerify: true + endpointWaitTime: 42s + prefix: foobar + stale: true + exposedByDefault: true + refreshInterval: 42s + namespaces: + - foobar + - foobar ecs: constraints: foobar exposedByDefault: true @@ -220,37 +223,37 @@ providers: - foobar - foobar autoDiscoverClusters: true + healthyTasksOnly: true + ecsAnywhere: true region: foobar accessKeyID: foobar secretAccessKey: foobar - ecsAnywhere: true - healthyTasksOnly: true consul: rootKey: foobar endpoints: - foobar - foobar token: foobar - namespaces: - - foobar - - foobar tls: ca: foobar cert: foobar key: foobar insecureSkipVerify: true + namespaces: + - foobar + - foobar etcd: rootKey: foobar endpoints: - foobar - foobar - username: foobar - password: foobar tls: ca: foobar cert: foobar key: foobar insecureSkipVerify: true + username: foobar + password: foobar zooKeeper: rootKey: foobar endpoints: @@ -263,14 +266,22 @@ providers: endpoints: - foobar - foobar - username: foobar - password: foobar - db: 42 tls: ca: foobar cert: foobar key: foobar insecureSkipVerify: true + username: foobar + password: foobar + db: 42 + sentinel: + masterName: foobar + username: foobar + password: foobar + latencyStrategy: true + randomStrategy: true + replicaStrategy: true + useDisconnectedReplicas: true http: endpoint: foobar pollInterval: 42s @@ -284,13 +295,17 @@ providers: key: foobar insecureSkipVerify: true plugin: - Descriptor0: {} - Descriptor1: {} + PluginConf0: + name0: foobar + name1: foobar + PluginConf1: + name0: foobar + name1: foobar api: insecure: true dashboard: true debug: true - disabledashboardad: false + disableDashboardAd: true metrics: prometheus: buckets: @@ -302,8 +317,8 @@ metrics: entryPoint: foobar manualRouting: true headerLabels: - label1: foobar - label2: foobar + name0: foobar + name1: foobar datadog: address: foobar pushInterval: 42s @@ -331,6 +346,7 @@ metrics: name0: foobar name1: foobar openTelemetry: + grpc: {} address: foobar addEntryPointsLabels: true addRoutersLabels: true @@ -346,12 +362,9 @@ metrics: pushInterval: 42s tls: ca: foobar - caOptional: true cert: foobar - insecureSkipVerify: true key: foobar - grpc: {} - + insecureSkipVerify: true ping: entryPoint: foobar manualRouting: true @@ -362,8 +375,8 @@ log: noColor: true filePath: foobar maxSize: 42 - maxBackups: 42 maxAge: 42 + maxBackups: 42 compress: true accessLog: filePath: foobar @@ -387,68 +400,29 @@ accessLog: bufferingSize: 42 tracing: serviceName: foobar - spanNameLimit: 42 - jaeger: - samplingServerURL: foobar - samplingType: foobar - samplingParam: 42 - localAgentHostPort: foobar - gen128Bit: true - propagation: foobar - traceContextHeaderName: foobar - disableAttemptReconnecting: true - collector: + headers: + name0: foobar + name1: foobar + globalAttributes: + name0: foobar + name1: foobar + sampleRate: 42 + otlp: + grpc: endpoint: foobar - user: foobar - password: foobar - zipkin: - httpEndpoint: foobar - sameSpan: true - id128Bit: true - sampleRate: 42 - datadog: - localAgentHostPort: foobar - localAgentSocket: foobar - globalTags: - tag1: foobar - tag2: foobar - debug: true - prioritySampling: true - traceIDHeaderName: foobar - parentIDHeaderName: foobar - samplingPriorityHeaderName: foobar - bagagePrefixHeaderName: foobar - instana: - localAgentHost: foobar - localAgentPort: 42 - logLevel: foobar - enableAutoProfile: true - haystack: - localAgentHost: foobar - localAgentPort: 42 - globalTag: foobar - traceIDHeaderName: foobar - parentIDHeaderName: foobar - spanIDHeaderName: foobar - baggagePrefixHeaderName: foobar - elastic: - serverURL: foobar - secretToken: foobar - serviceEnvironment: foobar - openTelemetry: - address: foobar - headers: - name0: foobar - name1: foobar - insecure: true - path: foobar - tls: - ca: foobar - caOptional: true - cert: foobar - key: foobar - insecureSkipVerify: true - grpc: {} + insecure: true + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + http: + endpoint: foobar + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true hostResolver: cnameFlattening: true resolvConfig: foobar @@ -458,13 +432,13 @@ certificatesResolvers: acme: email: foobar caServer: foobar - certificatesDuration: 42 preferredChain: foobar storage: foobar keyType: foobar eab: kid: foobar hmacEncoded: foobar + certificatesDuration: 42 dnsChallenge: provider: foobar delayBeforeCheck: 42s @@ -475,11 +449,30 @@ certificatesResolvers: httpChallenge: entryPoint: foobar tlsChallenge: {} + tailscale: {} CertificateResolver1: + acme: + email: foobar + caServer: foobar + preferredChain: foobar + storage: foobar + keyType: foobar + eab: + kid: foobar + hmacEncoded: foobar + certificatesDuration: 42 + dnsChallenge: + provider: foobar + delayBeforeCheck: 42s + resolvers: + - foobar + - foobar + disablePropagationCheck: true + httpChallenge: + entryPoint: foobar + tlsChallenge: {} tailscale: {} experimental: - kubernetesGateway: true - http3: true plugins: Descriptor0: moduleName: foobar @@ -488,7 +481,10 @@ experimental: moduleName: foobar version: foobar localPlugins: - Descriptor0: + LocalDescriptor0: moduleName: foobar - Descriptor1: + LocalDescriptor1: moduleName: foobar + kubernetesGateway: true +spiffe: + workloadAPIAddr: foobar diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index c544440c9..cd79c3cfd 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -248,7 +248,7 @@ EntryPoints in this list are used (by default) on HTTP and TCP routers that do n If at least one EntryPoint has the `AsDefault` option set to `true`, then the list of default EntryPoints includes only EntryPoints that have the `AsDefault` option set to `true`. - Some built-in EntryPoints are always excluded from the list, namely: `traefik`, `traefikhub-api`, and `traefikhub-tunl`. + Some built-in EntryPoints are always excluded from the list, namely: `traefik`. !!! warning "Only TCP and HTTP" @@ -623,17 +623,77 @@ Controls the behavior of Traefik during the shutdown phase. --entryPoints.name.transport.lifeCycle.graceTimeOut=42 ``` +#### `keepAliveMaxRequests` + +_Optional, Default=0_ + +The maximum number of requests Traefik can handle before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY). Zero means no limit. + + ```yaml tab="File (YAML)" + ## Static configuration + entryPoints: + name: + address: ":8888" + transport: + keepAliveMaxRequests: 42 + ``` + + ```toml tab="File (TOML)" + ## Static configuration + [entryPoints] + [entryPoints.name] + address = ":8888" + [entryPoints.name.transport] + keepAliveMaxRequests = 42 + ``` + + ```bash tab="CLI" + ## Static configuration + --entryPoints.name.address=:8888 + --entryPoints.name.transport.keepAliveRequests=42 + ``` + +#### `keepAliveMaxTime` + +_Optional, Default=0s_ + +The maximum duration Traefik can handle requests before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY). Zero means no limit. + + ```yaml tab="File (YAML)" + ## Static configuration + entryPoints: + name: + address: ":8888" + transport: + keepAliveMaxTime: 42s + ``` + + ```toml tab="File (TOML)" + ## Static configuration + [entryPoints] + [entryPoints.name] + address = ":8888" + [entryPoints.name.transport] + keepAliveMaxTime = 42s + ``` + + ```bash tab="CLI" + ## Static configuration + --entryPoints.name.address=:8888 + --entryPoints.name.transport.keepAliveTime=42s + ``` + ### ProxyProtocol -Traefik supports [ProxyProtocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. +Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. -If Proxy Protocol header parsing is enabled for the entry point, this entry point can accept connections with or without Proxy Protocol headers. +If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers. -If the Proxy Protocol header is passed, then the version is determined automatically. +If the PROXY protocol header is passed, then the version is determined automatically. ??? info "`proxyProtocol.trustedIPs`" - Enabling Proxy Protocol with Trusted IPs. + Enabling PROXY protocol with Trusted IPs. ```yaml tab="File (YAML)" ## Static configuration @@ -696,7 +756,7 @@ If the Proxy Protocol header is passed, then the version is determined automatic !!! warning "Queuing Traefik behind Another Load Balancer" - When queuing Traefik behind another load-balancer, make sure to configure Proxy Protocol on both sides. + When queuing Traefik behind another load-balancer, make sure to configure PROXY protocol on both sides. Not doing so could introduce a security risk in your system (enabling request forgery). ## HTTP Options diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index 8eb985043..678e23b92 100644 --- a/docs/content/routing/providers/consul-catalog.md +++ b/docs/content/routing/providers/consul-catalog.md @@ -273,6 +273,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none ``` +??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42 + ``` + ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index 4a2b32436..0f6df9a9d 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -376,6 +376,14 @@ you'd add the label `traefik.http.services..loadbalancer.pa - "traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none" ``` +??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + - "traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42" + ``` + ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. diff --git a/docs/content/routing/providers/ecs.md b/docs/content/routing/providers/ecs.md index 718303c0b..e5c66a8c3 100644 --- a/docs/content/routing/providers/ecs.md +++ b/docs/content/routing/providers/ecs.md @@ -275,6 +275,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none ``` +??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42 + ``` + ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index e2f5574f9..55f25494a 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -348,6 +348,7 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne name: cookie secure: true sameSite: none + maxAge: 42 strategy: RoundRobin weight: 10 nativeLB: true # [11] @@ -2011,4 +2012,4 @@ If the ServersTransportTCP CRD is defined in another provider the cross-provider Also see the [full example](../../user-guides/crd-acme/index.md) with Let's Encrypt. -{!traefik-api-management-kubernetes.md!} +{!traefik-for-business-applications.md!} diff --git a/docs/content/routing/providers/kubernetes-gateway.md b/docs/content/routing/providers/kubernetes-gateway.md index 9ca76c4db..e8d11c426 100644 --- a/docs/content/routing/providers/kubernetes-gateway.md +++ b/docs/content/routing/providers/kubernetes-gateway.md @@ -39,19 +39,19 @@ The Kubernetes Gateway API, The Experimental Way. {: .subtitle } You can find an excerpt of the supported Kubernetes Gateway API resources in the table below: -| Kind | Purpose | Concept Behind | -|------------------------------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------| -| [GatewayClass](#kind-gatewayclass) | Defines a set of Gateways that share a common configuration and behaviour | [GatewayClass](https://gateway-api.sigs.k8s.io/v1alpha2/api-types/gatewayclass) | -| [Gateway](#kind-gateway) | Describes how traffic can be translated to Services within the cluster | [Gateway](https://gateway-api.sigs.k8s.io/v1alpha2/api-types/gateway) | -| [HTTPRoute](#kind-httproute) | HTTP rules for mapping requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute) | -| [TCPRoute](#kind-tcproute) | Allows mapping TCP requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/v1alpha2/guides/tcp/) | -| [TLSRoute](#kind-tlsroute) | Allows mapping TLS requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/v1alpha2/guides/tls/) | +| Kind | Purpose | Concept Behind | +|------------------------------------|---------------------------------------------------------------------------|------------------------------------------------------------------------| +| [GatewayClass](#kind-gatewayclass) | Defines a set of Gateways that share a common configuration and behaviour | [GatewayClass](https://gateway-api.sigs.k8s.io/api-types/gatewayclass) | +| [Gateway](#kind-gateway) | Describes how traffic can be translated to Services within the cluster | [Gateway](https://gateway-api.sigs.k8s.io/api-types/gateway) | +| [HTTPRoute](#kind-httproute) | HTTP rules for mapping requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/api-types/httproute) | +| [TCPRoute](#kind-tcproute) | Allows mapping TCP requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/guides/tcp/) | +| [TLSRoute](#kind-tlsroute) | Allows mapping TLS requests from a Gateway to Kubernetes Services | [Route](https://gateway-api.sigs.k8s.io/guides/tls/) | ### Kind: `GatewayClass` `GatewayClass` is cluster-scoped resource defined by the infrastructure provider. This resource represents a class of Gateways that can be instantiated. More details on the -GatewayClass [official documentation](https://gateway-api.sigs.k8s.io/v1alpha2/api-types/gatewayclass/). +GatewayClass [official documentation](https://gateway-api.sigs.k8s.io/api-types/gatewayclass/). The `GatewayClass` should be declared by the infrastructure provider, otherwise please register the `GatewayClass` [definition](../../reference/dynamic-configuration/kubernetes-gateway.md#definitions) in the Kubernetes cluster before @@ -60,7 +60,7 @@ creating `GatewayClass` objects. !!! info "Declaring GatewayClass" ```yaml - apiVersion: gateway.networking.k8s.io/v1alpha2 + apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: my-gateway-class @@ -92,7 +92,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte !!! info "Declaring Gateway" ```yaml tab="HTTP Listener" - apiVersion: gateway.networking.k8s.io/v1alpha2 + apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: my-http-gateway @@ -114,7 +114,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte ``` ```yaml tab="HTTPS Listener" - apiVersion: gateway.networking.k8s.io/v1alpha2 + apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: my-https-gateway @@ -140,7 +140,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte ``` ```yaml tab="TCP Listener" - apiVersion: gateway.networking.k8s.io/v1alpha2 + apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: my-tcp-gateway @@ -162,7 +162,7 @@ Depending on the Listener Protocol, different modes and Route types are supporte ``` ```yaml tab="TLS Listener" - apiVersion: gateway.networking.k8s.io/v1alpha2 + apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: my-tls-gateway @@ -213,7 +213,7 @@ Kubernetes cluster before creating `HTTPRoute` objects. !!! info "Declaring HTTPRoute" ```yaml - apiVersion: gateway.networking.k8s.io/v1alpha2 + apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: http-app @@ -274,7 +274,7 @@ Kubernetes cluster before creating `TCPRoute` objects. !!! info "Declaring TCPRoute" ```yaml - apiVersion: gateway.networking.k8s.io/v1alpha2 + apiVersion: gateway.networking.k8s.io/v1 kind: TCPRoute metadata: name: tcp-app @@ -318,7 +318,7 @@ Kubernetes cluster before creating `TLSRoute` objects. !!! info "Declaring TLSRoute" ```yaml - apiVersion: gateway.networking.k8s.io/v1alpha2 + apiVersion: gateway.networking.k8s.io/v1 kind: TLSRoute metadata: name: tls-app @@ -355,4 +355,4 @@ Kubernetes cluster before creating `TLSRoute` objects. | [11] | `group` | Group is the group of the referent. Only `traefik.io` and `gateway.networking.k8s.io` values are supported. | | [12] | `kind` | Kind is kind of the referent. Only `TraefikService` and `Service` values are supported. | -{!traefik-api-management-kubernetes.md!} +{!traefik-for-business-applications.md!} diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index 3d1eacabb..01f1d5166 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -351,6 +351,14 @@ which in turn will create the resulting routers, services, handlers, etc. traefik.ingress.kubernetes.io/service.sticky.cookie.httponly: "true" ``` +??? info "`traefik.ingress.kubernetes.io/service.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.ingress.kubernetes.io/service.sticky.cookie.maxage: 42 + ``` + ## Path Types on Kubernetes 1.18+ If the Kubernetes cluster version is 1.18+, @@ -864,4 +872,4 @@ This will allow users to create a "default router" that will match all unmatched To do this, use the `traefik.ingress.kubernetes.io/router.priority` annotation (as seen in [Annotations on Ingress](#on-ingress)) on your ingresses accordingly. -{!traefik-api-management-kubernetes.md!} +{!traefik-for-business-applications.md!} diff --git a/docs/content/routing/providers/kv.md b/docs/content/routing/providers/kv.md index a54ef1c61..3a6cc7744 100644 --- a/docs/content/routing/providers/kv.md +++ b/docs/content/routing/providers/kv.md @@ -244,6 +244,14 @@ A Story of key & values |-----------------------------------------------------------------------|--------| | `traefik/http/services/myservice/loadbalancer/sticky/cookie/samesite` | `none` | +??? info "`traefik/http/services//loadbalancer/sticky/cookie/maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + | Key (Path) | Value | + |---------------------------------------------------------------------|-------| + | `traefik/http/services/myservice/loadbalancer/sticky/cookie/maxage` | `42` | + ??? info "`traefik/http/services//loadbalancer/responseforwarding/flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. @@ -306,6 +314,12 @@ A Story of key & values |------------------------------------------------------------------------|--------| | `traefik/http/services//weighted/sticky/cookie/httpOnly` | `true` | +??? info "`traefik/http/services//weighted/sticky/cookie/maxage`" + + | Key (Path) | Value | + |----------------------------------------------------------------------|-------| + | `traefik/http/services//weighted/sticky/cookie/maxage` | `42` | + ### Middleware More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md). diff --git a/docs/content/routing/providers/nomad.md b/docs/content/routing/providers/nomad.md index b0143d094..e1176c51d 100644 --- a/docs/content/routing/providers/nomad.md +++ b/docs/content/routing/providers/nomad.md @@ -265,6 +265,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none ``` +??? info "`traefik.http.services..loadbalancer.sticky.cookie.maxage`" + + See [sticky sessions](../services/index.md#sticky-sessions) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.sticky.cookie.maxage=42 + ``` + ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" See [response forwarding](../services/index.md#response-forwarding) for more information. diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 598778296..55b46dc9d 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -968,12 +968,12 @@ If the rule is verified, the router becomes active, calls middlewares, and then The table below lists all the available matchers: -| Rule | Description | -|------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------| -| [```HostSNI(`domain`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication is equal to `domain`. | -| [```HostSNIRegexp(`regexp`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication matches `regexp`. | -| [```ClientIP(`ip`)```](#clientip_1) | Checks if the connection's client IP correspond to `ip`. It accepts IPv4, IPv6 and CIDR formats. | -| [```ALPN(`protocol`)```](#alpn) | Checks if the connection's ALPN protocol equals `protocol`. | +| Rule | Description | +|-------------------------------------------------------------|:-------------------------------------------------------------------------------------------------| +| [```HostSNI(`domain`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication is equal to `domain`. | +| [```HostSNIRegexp(`regexp`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication matches `regexp`. | +| [```ClientIP(`ip`)```](#clientip_1) | Checks if the connection's client IP correspond to `ip`. It accepts IPv4, IPv6 and CIDR formats. | +| [```ALPN(`protocol`)```](#alpn) | Checks if the connection's ALPN protocol equals `protocol`. | !!! tip "Backticks or Quotes?" diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index c809392f4..7658195e1 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -187,6 +187,13 @@ On subsequent requests, to keep the session alive with the same server, the clie The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`). +!!! info "MaxAge" + + By default, the affinity cookie will never expire as the `MaxAge` option is set to zero. + + This option indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + !!! info "Secure & HTTPOnly & SameSite flags" By default, the affinity cookie is created without those flags. @@ -338,7 +345,6 @@ Below are the available options for the health check mechanism: !!! info "Interval & Timeout Format" Interval and timeout are to be given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). - The interval must be greater than the timeout. If configuration doesn't reflect this, the interval will be set to timeout + 1 second. !!! info "Recovering Servers" diff --git a/docs/content/user-guides/crd-acme/index.md b/docs/content/user-guides/crd-acme/index.md index 7eac2b9f8..0921d892b 100644 --- a/docs/content/user-guides/crd-acme/index.md +++ b/docs/content/user-guides/crd-acme/index.md @@ -91,7 +91,7 @@ Therefore, for the whole thing to work, we must delay applying the ingressRoute kubectl port-forward --address 0.0.0.0 service/traefik 8000:8000 8080:8080 443:4443 -n default ``` -Also, and this is out of the scope if this guide, please note that because of the privileged ports limitation on Linux, the above command might fail to listen on port 443. +Also, and this is out of the scope of this guide, please note that because of the privileged ports limitation on Linux, the above command might fail to listen on port 443. In which case you can use tricks such as elevating caps of `kubectl` with `setcaps`, or using `authbind`, or setting up a NAT between your host and the WAN. Look it up. diff --git a/docs/content/user-guides/docker-compose/acme-dns/index.md b/docs/content/user-guides/docker-compose/acme-dns/index.md index 59872c44e..84fc9c8dc 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/index.md +++ b/docs/content/user-guides/docker-compose/acme-dns/index.md @@ -3,9 +3,9 @@ title: "Traefik Docker DNS Challenge Documentation" description: "Learn how to create a certificate with the Let's Encrypt DNS challenge to use HTTPS on a Service exposed with Traefik Proxy. Read the tehnical documentation." --- -# Docker-compose with let's encrypt: DNS Challenge +# Docker-compose with Let's Encrypt: DNS Challenge -This guide aim to demonstrate how to create a certificate with the let's encrypt DNS challenge to use https on a simple service exposed with Traefik. +This guide aim to demonstrate how to create a certificate with the Let's Encrypt DNS challenge to use https on a simple service exposed with Traefik. Please also read the [basic example](../basic-example) for details on how to expose such a service. ## Prerequisite @@ -52,7 +52,7 @@ For the DNS challenge, you'll need: !!! Note If you uncommented the `acme.caserver` line, you will get an SSL error, but if you display the certificate and see it was emitted by `Fake LE Intermediate X1` then it means all is good. - (It is the staging environment intermediate certificate used by let's encrypt). + (It is the staging environment intermediate certificate used by Let's Encrypt). You can now safely comment the `acme.caserver` line, remove the `letsencrypt/acme.json` file and restart Traefik to issue a valid certificate. ## Explanation @@ -69,7 +69,7 @@ ports: - "443:443" ``` -- We configure the DNS let's encrypt challenge: +- We configure the DNS Let's Encrypt challenge: ```yaml command: @@ -77,7 +77,7 @@ command: - "--certificatesresolvers.myresolver.acme.dnschallenge=true" # Tell which provider to use - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=ovh" - # The email to provide to let's encrypt + # The email to provide to Let's Encrypt - "--certificatesresolvers.myresolver.acme.email=postmaster@example.com" ``` @@ -175,7 +175,7 @@ services: - "ovh_consumer_key" ``` -- The environment variable within our `whoami` service are suffixed by `_FILE` which allow us to point to files containing the value, instead of exposing the value itself. +- The environment variable within our `traefik` service are suffixed by `_FILE` which allow us to point to files containing the value, instead of exposing the value itself. The acme client will read the content of those file to get the required configuration values. ```yaml diff --git a/docs/content/user-guides/docker-compose/acme-http/index.md b/docs/content/user-guides/docker-compose/acme-http/index.md index 7f8a05873..67808853e 100644 --- a/docs/content/user-guides/docker-compose/acme-http/index.md +++ b/docs/content/user-guides/docker-compose/acme-http/index.md @@ -3,9 +3,9 @@ title: "Traefik Docker HTTP Challenge Documentation" description: "Learn how to create a certificate with the Let's Encrypt HTTP challenge to use HTTPS on a Service exposed with Traefik Proxy. Read the technical documentation." --- -# Docker-compose with let's encrypt : HTTP Challenge +# Docker-compose with Let's Encrypt : HTTP Challenge -This guide aim to demonstrate how to create a certificate with the let's encrypt HTTP challenge to use https on a simple service exposed with Traefik. +This guide aim to demonstrate how to create a certificate with the Let's Encrypt HTTP challenge to use https on a simple service exposed with Traefik. Please also read the [basic example](../basic-example) for details on how to expose such a service. ## Prerequisite @@ -38,7 +38,7 @@ For the HTTP challenge you will need: !!! Note If you uncommented the `acme.caserver` line, you will get an SSL error, but if you display the certificate and see it was emitted by `Fake LE Intermediate X1` then it means all is good. - (It is the staging environment intermediate certificate used by let's encrypt). + (It is the staging environment intermediate certificate used by Let's Encrypt). You can now safely comment the `acme.caserver` line, remove the `letsencrypt/acme.json` file and restart Traefik to issue a valid certificate. ## Explanation @@ -55,7 +55,7 @@ ports: - "443:443" ``` -- We configure the HTTPS let's encrypt challenge: +- We configure the HTTPS Let's Encrypt challenge: ```yaml command: @@ -63,7 +63,7 @@ command: - "--certificatesresolvers.myresolver.acme.httpchallenge=true" # Tell it to use our predefined entrypoint named "web" - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web" - # The email to provide to let's encrypt + # The email to provide to Let's Encrypt - "--certificatesresolvers.myresolver.acme.email=postmaster@example.com" ``` diff --git a/docs/content/user-guides/docker-compose/acme-tls/index.md b/docs/content/user-guides/docker-compose/acme-tls/index.md index 3457b6ae1..b201a4c95 100644 --- a/docs/content/user-guides/docker-compose/acme-tls/index.md +++ b/docs/content/user-guides/docker-compose/acme-tls/index.md @@ -3,9 +3,9 @@ title: "Traefik Docker TLS Challenge Documentation" description: "Learn how to create a certificate with the Let's Encrypt TLS challenge to use HTTPS on a service exposed with Traefik Proxy. Read the technical documentation." --- -# Docker-compose with let's encrypt: TLS Challenge +# Docker-compose with Let's Encrypt: TLS Challenge -This guide aim to demonstrate how to create a certificate with the let's encrypt TLS challenge to use https on a simple service exposed with Traefik. +This guide aim to demonstrate how to create a certificate with the Let's Encrypt TLS challenge to use https on a simple service exposed with Traefik. Please also read the [basic example](../basic-example) for details on how to expose such a service. ## Prerequisite @@ -38,7 +38,7 @@ For the TLS challenge you will need: !!! Note If you uncommented the `acme.caserver` line, you will get an SSL error, but if you display the certificate and see it was emitted by `Fake LE Intermediate X1` then it means all is good. - (It is the staging environment intermediate certificate used by let's encrypt). + (It is the staging environment intermediate certificate used by Let's Encrypt). You can now safely comment the `acme.caserver` line, remove the `letsencrypt/acme.json` file and restart Traefik to issue a valid certificate. ## Explanation @@ -55,7 +55,7 @@ ports: - "443:443" ``` -- We configure the Https let's encrypt challenge: +- We configure the TLS Let's Encrypt challenge: ```yaml command: diff --git a/docs/content/user-guides/docker-compose/basic-example/index.md b/docs/content/user-guides/docker-compose/basic-example/index.md index affedba37..dff9d635e 100644 --- a/docs/content/user-guides/docker-compose/basic-example/index.md +++ b/docs/content/user-guides/docker-compose/basic-example/index.md @@ -1,16 +1,15 @@ --- title: "Traefik Docker Documentation" -description: "This guide covers a Docker Compose file exposing a service using the Docker provider in Traefik Proxy. Read the technical documentation." +description: "Learn how to use Docker Compose to expose a service with Traefik Proxy." --- # Docker Compose example -In this section, we quickly go over a Docker Compose file exposing a service using the Docker provider. -This will also be used as a starting point for the other Docker Compose guides. +In this section, you will learn how to use [Docker Compose](https://docs.docker.com/compose/ "Link to Docker Compose") to expose a service using the Docker provider. ## Setup -- Edit a `docker-compose.yml` file with the following content: +Create a `docker-compose.yml` file with the following content: ```yaml --8<-- "content/user-guides/docker-compose/basic-example/docker-compose.yml" @@ -45,33 +44,44 @@ This will also be used as a starting point for the other Docker Compose guides. ``` -- Replace `whoami.localhost` by your **own domain** within the `traefik.http.routers.whoami.rule` label of the `whoami` service. -- Run `docker-compose up -d` within the folder where you created the previous file. -- Wait a bit and visit `http://your_own_domain` to confirm everything went fine. - You should see the output of the whoami service. Something similar to: +Replace `whoami.localhost` by your **own domain** within the `traefik.http.routers.whoami.rule` label of the `whoami` service. - ```text - Hostname: d7f919e54651 - IP: 127.0.0.1 - IP: 192.168.64.2 - GET / HTTP/1.1 - Host: whoami.localhost - User-Agent: curl/7.52.1 - Accept: */* - Accept-Encoding: gzip - X-Forwarded-For: 192.168.64.1 - X-Forwarded-Host: whoami.localhost - X-Forwarded-Port: 80 - X-Forwarded-Proto: http - X-Forwarded-Server: 7f0c797dbc51 - X-Real-Ip: 192.168.64.1 - ``` +Now run `docker-compose up -d` within the folder where you created the previous file. +This will start Docker Compose in background mode. + +!!! info "This can take a moment" + + Docker Compose will now create and start the services declared in the `docker-compose.yml`. + +Wait a bit and visit `http://your_own_domain` to confirm everything went fine. + +You should see the output of the whoami service. +It should be similar to the following example: + +```text +Hostname: d7f919e54651 +IP: 127.0.0.1 +IP: 192.168.64.2 +GET / HTTP/1.1 +Host: whoami.localhost +User-Agent: curl/7.52.1 +Accept: */* +Accept-Encoding: gzip +X-Forwarded-For: 192.168.64.1 +X-Forwarded-Host: whoami.localhost +X-Forwarded-Port: 80 +X-Forwarded-Proto: http +X-Forwarded-Server: 7f0c797dbc51 +X-Real-Ip: 192.168.64.1 +``` ## Details -- As an example, we use [whoami](https://github.com/traefik/whoami "Link to the GitHub repo of whoami") (a tiny Go server that prints OS information and HTTP request to output) which was used to define our `simple-service` container. +Let's break it down and go through it, step-by-step. -- We define an entry point, along with the exposure of the matching port within Docker Compose, which allow us to "open and accept" HTTP traffic: +You use [whoami](https://github.com/traefik/whoami "Link to the GitHub repo of whoami"), a tiny Go server that prints OS information and HTTP request to output as service container. + +Second, you define an entry point, along with the exposure of the matching port within Docker Compose, which allows to "open and accept" HTTP traffic: ```yaml command: @@ -82,7 +92,7 @@ ports: - "80:80" ``` -- We expose the Traefik API to be able to check the configuration if needed: +Third, you expose the Traefik API to be able to check the configuration if needed: ```yaml command: @@ -101,7 +111,7 @@ ports: curl -s 127.0.0.1:8080/api/rawdata | jq . ``` -- We allow Traefik to gather configuration from Docker: +Fourth, you allow Traefik to gather configuration from Docker: ```yaml traefik: diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 786b966bf..6ba875a3f 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -27,7 +27,7 @@ theme: prev: 'Previous' next: 'Next' -copyright: 'Traefik Labs • Copyright © 2016-2023' +copyright: 'Traefik Labs • Copyright © 2016-2024' extra_javascript: - assets/js/hljs/highlight.pack.js # Download from https://highlightjs.org/download/ and enable YAML, TOML and Dockerfile @@ -125,7 +125,8 @@ nav: - 'ForwardAuth': 'middlewares/http/forwardauth.md' - 'GrpcWeb': 'middlewares/http/grpcweb.md' - 'Headers': 'middlewares/http/headers.md' - - 'IpAllowList': 'middlewares/http/ipallowlist.md' + - 'IPWhiteList': 'middlewares/http/ipwhitelist.md' + - 'IPAllowList': 'middlewares/http/ipallowlist.md' - 'InFlightReq': 'middlewares/http/inflightreq.md' - 'PassTLSClientCert': 'middlewares/http/passtlsclientcert.md' - 'RateLimit': 'middlewares/http/ratelimit.md' @@ -139,7 +140,8 @@ nav: - 'TCP': - 'Overview': 'middlewares/tcp/overview.md' - 'InFlightConn': 'middlewares/tcp/inflightconn.md' - - 'IpAllowList': 'middlewares/tcp/ipallowlist.md' + - 'IPWhiteList': 'middlewares/tcp/ipwhitelist.md' + - 'IPAllowList': 'middlewares/tcp/ipallowlist.md' - 'Plugins & Plugin Catalog': 'plugins/index.md' - 'Operations': - 'CLI': 'operations/cli.md' @@ -151,19 +153,9 @@ nav: - 'Access Logs': 'observability/access-logs.md' - 'Metrics': - 'Overview': 'observability/metrics/overview.md' - - 'Datadog': 'observability/metrics/datadog.md' - - 'InfluxDB2': 'observability/metrics/influxdb2.md' - 'OpenTelemetry': 'observability/metrics/opentelemetry.md' - - 'Prometheus': 'observability/metrics/prometheus.md' - - 'StatsD': 'observability/metrics/statsd.md' - 'Tracing': - 'Overview': 'observability/tracing/overview.md' - - 'Jaeger': 'observability/tracing/jaeger.md' - - 'Zipkin': 'observability/tracing/zipkin.md' - - 'Datadog': 'observability/tracing/datadog.md' - - 'Instana': 'observability/tracing/instana.md' - - 'Haystack': 'observability/tracing/haystack.md' - - 'Elastic': 'observability/tracing/elastic.md' - 'OpenTelemetry': 'observability/tracing/opentelemetry.md' - 'User Guides': - 'Kubernetes and Let''s Encrypt': 'user-guides/crd-acme/index.md' diff --git a/exp.Dockerfile b/exp.Dockerfile deleted file mode 100644 index 843f8ca0f..000000000 --- a/exp.Dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -# WEBUI -FROM node:12.11 as webui - -ENV WEBUI_DIR /src/webui -RUN mkdir -p $WEBUI_DIR - -COPY ./webui/ $WEBUI_DIR/ - -WORKDIR $WEBUI_DIR - -RUN yarn install -RUN yarn build - -# BUILD -FROM golang:1.20-alpine as gobuild - -RUN apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \ - && update-ca-certificates \ - && rm -rf /var/cache/apk/* - -WORKDIR /go/src/github.com/traefik/traefik - -# Download go modules -COPY go.mod . -COPY go.sum . -RUN GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download - -COPY . /go/src/github.com/traefik/traefik - -RUN rm -rf /go/src/github.com/traefik/traefik/webui/static/ -COPY --from=webui /src/webui/static/ /go/src/github.com/traefik/traefik/webui/static/ - -RUN ./script/make.sh generate binary - -## IMAGE -FROM alpine:3.14 - -RUN apk --no-cache --no-progress add bash curl ca-certificates tzdata \ - && update-ca-certificates \ - && rm -rf /var/cache/apk/* - -COPY --from=gobuild /go/src/github.com/traefik/traefik/dist/traefik / - -EXPOSE 80 -VOLUME ["/tmp"] - -ENTRYPOINT ["/traefik"] diff --git a/go.mod b/go.mod index ff8540989..1e3228e15 100644 --- a/go.mod +++ b/go.mod @@ -1,46 +1,41 @@ module github.com/traefik/traefik/v3 -go 1.20 +go 1.21 require ( github.com/BurntSushi/toml v1.3.2 - github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61 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.4 - github.com/aws/aws-sdk-go v1.44.47 + 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/compose-spec/compose-go v1.0.3 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.1 - github.com/docker/cli v20.10.11+incompatible - github.com/docker/compose/v2 v2.0.1 - github.com/docker/docker v20.10.21+incompatible + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc + github.com/docker/cli v24.0.7+incompatible + github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.4.0 github.com/fatih/structs v1.1.0 - github.com/go-acme/lego/v4 v4.13.2 - github.com/go-check/check v0.0.0-00010101000000-000000000000 + github.com/fsnotify/fsnotify v1.7.0 + github.com/go-acme/lego/v4 v4.14.0 github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea - github.com/golang/protobuf v1.5.2 + github.com/golang/protobuf v1.5.3 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 v1.10.12 - github.com/hashicorp/consul/api v1.14.0 - github.com/hashicorp/go-hclog v1.2.0 + github.com/hashicorp/consul/api v1.26.1 + github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/nomad/api v0.0.0-20220506174431-b5665129cd1f - github.com/improbable-eng/grpc-web v0.15.0 + github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b + github.com/http-wasm/http-wasm-host-go v0.5.2 github.com/influxdata/influxdb-client-go/v2 v2.7.0 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d - github.com/instana/go-sensor v1.38.3 - github.com/klauspost/compress v1.16.6 + github.com/klauspost/compress v1.17.2 github.com/kvtools/consul v1.0.2 github.com/kvtools/etcdv3 v1.0.2 - github.com/kvtools/redis v1.0.2 + github.com/kvtools/redis v1.1.0 github.com/kvtools/valkeyrie v1.0.0 github.com/kvtools/zookeeper v1.0.2 github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f @@ -49,68 +44,61 @@ require ( github.com/mitchellh/hashstructure v1.0.0 github.com/mitchellh/mapstructure v1.5.0 github.com/natefinch/lumberjack v0.0.0-20201021141957-47ffae23317c - github.com/opentracing/opentracing-go v1.2.0 - github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 - github.com/openzipkin/zipkin-go v0.2.2 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pires/go-proxyproto v0.6.1 - github.com/pmezard/go-difflib v1.0.0 - github.com/prometheus/client_golang v1.14.0 - github.com/prometheus/client_model v0.3.0 - github.com/quic-go/quic-go v0.33.0 - github.com/rs/zerolog v1.28.0 - github.com/sirupsen/logrus v1.9.0 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 + github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/client_model v0.5.0 + github.com/quic-go/quic-go v0.40.1 + github.com/rs/zerolog v1.29.0 + github.com/sirupsen/logrus v1.9.3 github.com/spiffe/go-spiffe/v2 v2.1.1 github.com/stretchr/testify v1.8.4 github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2 + github.com/testcontainers/testcontainers-go v0.27.0 + github.com/tetratelabs/wazero v1.5.0 + github.com/tidwall/gjson v1.17.0 + github.com/traefik/grpc-web v0.16.0 github.com/traefik/paerser v0.2.0 github.com/traefik/yaegi v0.15.1 - github.com/uber/jaeger-client-go v2.30.0+incompatible - github.com/uber/jaeger-lib v2.2.0+incompatible github.com/unrolled/render v1.0.2 github.com/unrolled/secure v1.0.9 - github.com/vdemeester/shakers v0.1.0 github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c github.com/vulcand/predicate v1.2.0 - go.elastic.co/apm v1.13.1 - go.elastic.co/apm/module/apmot v1.13.1 - go.opentelemetry.io/collector/pdata v0.64.1 - go.opentelemetry.io/otel v1.14.0 - go.opentelemetry.io/otel/bridge/opentracing v1.11.2 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.37.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.37.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 - go.opentelemetry.io/otel/metric v0.37.0 - go.opentelemetry.io/otel/sdk v1.14.0 - go.opentelemetry.io/otel/sdk/metric v0.37.0 - go.opentelemetry.io/otel/trace v1.14.0 - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db - golang.org/x/mod v0.11.0 - golang.org/x/net v0.11.0 - golang.org/x/text v0.10.0 + go.opentelemetry.io/collector/pdata v0.66.0 + go.opentelemetry.io/otel v1.21.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 + go.opentelemetry.io/otel/metric v1.21.0 + go.opentelemetry.io/otel/sdk v1.21.0 + go.opentelemetry.io/otel/sdk/metric v1.21.0 + go.opentelemetry.io/otel/trace v1.21.0 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d + golang.org/x/mod v0.13.0 + golang.org/x/net v0.17.0 + golang.org/x/text v0.13.0 golang.org/x/time v0.3.0 - golang.org/x/tools v0.10.0 - google.golang.org/grpc v1.53.0 - gopkg.in/DataDog/dd-trace-go.v1 v1.51.0 - gopkg.in/fsnotify.v1 v1.4.7 + golang.org/x/tools v0.14.0 + google.golang.org/grpc v1.59.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.26.3 - k8s.io/apiextensions-apiserver v0.26.3 - k8s.io/apimachinery v0.26.3 - k8s.io/client-go v0.26.3 - k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 - mvdan.cc/xurls/v2 v2.1.0 - sigs.k8s.io/gateway-api v0.4.0 + k8s.io/api v0.28.3 + k8s.io/apiextensions-apiserver v0.28.3 + k8s.io/apimachinery v0.28.3 + k8s.io/client-go v0.28.3 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b + mvdan.cc/xurls/v2 v2.5.0 + sigs.k8s.io/gateway-api v1.0.0 ) require ( - cloud.google.com/go/compute v1.18.0 // indirect + cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect + dario.cat/mergo v1.0.0 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect - github.com/AlecAivazis/survey/v2 v2.2.3 // 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 @@ -128,133 +116,104 @@ require ( 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/DataDog/appsec-internal-go v1.0.0 // indirect - github.com/DataDog/datadog-agent/pkg/obfuscate v0.45.0-rc.1 // indirect - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.45.0-rc.1 // indirect - github.com/DataDog/datadog-go v4.8.2+incompatible // indirect - github.com/DataDog/datadog-go/v5 v5.1.1 // indirect - github.com/DataDog/go-libddwaf v1.2.0 // indirect - github.com/DataDog/go-tuf v0.3.0--fix-localmeta-fork // indirect - github.com/DataDog/sketches-go v1.2.1 // 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.0 // indirect - github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/Microsoft/hcsshim v0.8.25 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect - github.com/Shopify/sarama v1.23.1 // indirect github.com/VividCortex/gohistogram v1.0.0 // indirect - github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // 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/armon/go-metrics v0.3.10 // indirect - github.com/armon/go-radix v1.0.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.20.3 // indirect + github.com/aws/aws-sdk-go-v2/config v1.18.28 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.27 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.40 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.34 // indirect + github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect + github.com/aws/smithy-go v1.14.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect - github.com/buger/goterm v1.0.0 // indirect + github.com/bytedance/sonic v1.10.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible // indirect - github.com/circonus-labs/circonusllhist v0.1.3 // indirect github.com/civo/civogo v0.3.11 // indirect github.com/cloudflare/cloudflare-go v0.70.0 // indirect - github.com/compose-spec/godotenv v1.0.0 // indirect - github.com/containerd/cgroups v1.0.3 // indirect - github.com/containerd/console v1.0.3 // indirect - github.com/containerd/containerd v1.5.17 // indirect - github.com/containerd/continuity v0.3.0 // indirect - github.com/containerd/typeurl v1.0.2 // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect + github.com/containerd/containerd v1.7.11 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpu/goacmedns v0.1.1 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/deepmap/oapi-codegen v1.9.1 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e // indirect github.com/dnsimple/dnsimple-go v1.2.0 // indirect - github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker-credential-helpers v0.6.4-0.20210125172408-38bea2ce277a // indirect - github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect - github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/go-units v0.4.0 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect - github.com/elastic/go-licenser v0.3.1 // indirect - github.com/elastic/go-sysinfo v1.1.1 // indirect - github.com/elastic/go-windows v1.0.0 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // 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/exoscale/egoscale v0.100.1 // indirect - github.com/fatih/color v1.13.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/fvbommel/sortorder v1.0.1 // indirect + github.com/fatih/color v1.15.0 // 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/v3 v3.0.0 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect - github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + 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.7.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect - github.com/gofrs/flock v0.8.0 // indirect - github.com/gogo/googleapis v1.4.0 // 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/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect + github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect + github.com/google/s2a-go v0.1.5 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/gophercloud/gophercloud v1.0.0 // indirect github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.14.0 // indirect - github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect - github.com/hashicorp/consul/sdk v0.10.0 // indirect - github.com/hashicorp/cronexpr v1.1.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.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-msgpack v0.5.5 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/memberlist v0.3.1 // indirect - github.com/hashicorp/raft v1.3.6 // indirect - github.com/hashicorp/raft-autopilot v0.1.5 // indirect - github.com/hashicorp/serf v0.9.7 // indirect - github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 // 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 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect - github.com/imdario/mergo v0.3.12 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect - github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea // indirect - github.com/jcchavezs/porto v0.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect - github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/jonboulle/clockwork v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect @@ -263,28 +222,22 @@ require ( github.com/liquidweb/go-lwApi v0.0.5 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/liquidweb/liquidweb-go v1.6.3 // indirect - github.com/looplab/fsm v0.1.0 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f // indirect github.com/mailgun/multibuf v0.1.2 // indirect github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-shellwords v1.0.12 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect - github.com/miekg/pkcs11 v1.0.3 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect - github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf // indirect - github.com/moby/locker v1.0.1 // indirect - github.com/moby/sys/mount v0.2.0 // indirect - github.com/moby/sys/mountinfo v0.5.0 // indirect - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect @@ -299,104 +252,93 @@ require ( github.com/nrdcg/nodion v0.1.0 // indirect github.com/nrdcg/porkbun v0.2.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect - github.com/onsi/ginkgo/v2 v2.4.0 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/ginkgo/v2 v2.11.0 // indirect + github.com/onsi/gomega v1.27.10 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/opencontainers/runc v1.1.5 // indirect - github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect - github.com/outcaste-io/ristretto v0.2.1 // indirect github.com/ovh/go-ovh v1.4.1 // indirect - github.com/philhofer/fwd v1.1.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/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/pquerna/otp v1.4.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.2.1 // indirect - github.com/quic-go/qtls-go1-20 v0.1.1 // indirect + github.com/quic-go/qtls-go1-20 v0.4.1 // 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/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect - github.com/santhosh-tekuri/jsonschema v1.2.4 // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect - github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.5.0 // indirect + github.com/shirou/gopsutil/v3 v3.23.11 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/softlayer/softlayer-go v1.1.2 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/cobra v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/objx v0.5.1 // 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/theupdateframework/notary v0.6.1 // indirect - github.com/tinylib/msgp v1.1.6 // indirect - github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 // indirect - github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // 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 + github.com/tklauser/numcpus v0.6.1 // indirect github.com/transip/gotransip/v6 v6.20.0 // indirect - github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect github.com/vultr/govultr/v2 v2.17.2 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // 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/yusufpapurcu/wmi v1.2.3 // indirect github.com/zeebo/errs v1.2.2 // indirect - go.elastic.co/apm/module/apmhttp v1.13.1 // indirect - go.elastic.co/fastjson v1.1.0 // indirect - go.etcd.io/etcd/api/v3 v3.5.5 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect - go.etcd.io/etcd/client/v3 v3.5.5 // indirect + go.etcd.io/etcd/api/v3 v3.5.9 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect + go.etcd.io/etcd/client/v3 v3.5.9 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.37.0 // indirect - go.opentelemetry.io/proto/otlp v0.19.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/mock v0.3.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect - go.uber.org/zap v1.21.0 // indirect - go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect - go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect - golang.org/x/crypto v0.10.0 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/oauth2 v0.9.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/term v0.9.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.111.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect - google.golang.org/protobuf v1.28.1 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/arch v0.4.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.13.0 // indirect + google.golang.org/api v0.128.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/protobuf v1.31.0 // 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.6 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect - inet.af/netaddr v0.0.0-20220811202034-502d2d690317 // indirect - k8s.io/klog/v2 v2.80.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect nhooyr.io/websocket v1.8.7 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) // Containous forks replace ( github.com/abbot/go-http-auth => github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e - github.com/go-check/check => github.com/containous/check v0.0.0-20170915194414-ca0bf163426a + github.com/gorilla/mux => github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f github.com/mailgun/minheap => github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595 ) @@ -406,3 +348,6 @@ replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402 // ambiguous import: found package github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http in multiple modules // tencentcloud uses monorepo with multimodule but the go.mod files are incomplete. exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible + +// https://github.com/docker/compose/blob/v2.19.0/go.mod#L12 +replace github.com/cucumber/godog => github.com/cucumber/godog v0.13.0 diff --git a/go.sum b/go.sum index b6ff4ebf7..9c0c8a0eb 100644 --- a/go.sum +++ b/go.sum @@ -1,60 +1,26 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4= -cloud.google.com/go v0.25.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 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 v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= 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/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= -github.com/AlecAivazis/survey/v2 v2.2.3 h1:utJR2X4Ibp2fBxdjalQUiMFf3zfQNjA15YE8+ftlKEs= -github.com/AlecAivazis/survey/v2 v2.2.3/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v19.1.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v40.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 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= @@ -66,62 +32,34 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aov 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/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= 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 v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v12.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 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.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= -github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= 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.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= 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/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= 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.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 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= @@ -130,222 +68,138 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 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/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/appsec-internal-go v1.0.0 h1:2u5IkF4DBj3KVeQn5Vg2vjPUtt513zxEYglcqnd500U= -github.com/DataDog/appsec-internal-go v1.0.0/go.mod h1:+Y+4klVWKPOnZx6XESG7QHydOaUGEXyH2j/vSg9JiNM= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.45.0-rc.1 h1:XyYvstMFpSyZtfJHWJm1Sf1meNyCdfhKJrjB6+rUNOk= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.45.0-rc.1/go.mod h1:e933RWa4kAWuHi5jpzEuOiULlv21HcCFEVIYegmaB5c= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.45.0-rc.1 h1:0OK84DbAucLUwoDYoBFve1cuhDWtoquruVVDjgucYlI= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.45.0-rc.1/go.mod h1:VVMDDibJxYEkwcLdZBT2g8EHKpbMT4JdOhRbQ9GdjbM= -github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/datadog-go v4.8.2+incompatible h1:qbcKSx29aBLD+5QLvlQZlGmRMF/FfGqFLFev/1TDzRo= -github.com/DataDog/datadog-go v4.8.2+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/datadog-go/v5 v5.1.1 h1:JLZ6s2K1pG2h9GkvEvMdEGqMDyVLEAccdX5TltWcLMU= -github.com/DataDog/datadog-go/v5 v5.1.1/go.mod h1:KhiYb2Badlv9/rofz+OznKoEF5XKTonWyhx5K83AP8E= -github.com/DataDog/go-libddwaf v1.2.0 h1:fKHP5U29E597eV2hU501fcW40bL8zcQ081jEGuRw2kM= -github.com/DataDog/go-libddwaf v1.2.0/go.mod h1:DI5y8obPajk+Tvy2o+nZc2g/5Ria/Rfq5/624k7pHpE= -github.com/DataDog/go-tuf v0.3.0--fix-localmeta-fork h1:yBq5PrAtrM4yVeSzQ+bn050+Ysp++RKF1QmtkL4VqvU= -github.com/DataDog/go-tuf v0.3.0--fix-localmeta-fork/go.mod h1:yA5JwkZsHTLuqq3zaRgUQf35DfDkpOZqgtBqHKpwrBs= -github.com/DataDog/gostackparse v0.5.0 h1:jb72P6GFHPHz2W0onsN51cS3FkaMDcjb0QzgxxA4gDk= -github.com/DataDog/sketches-go v1.2.1 h1:qTBzWLnZ3kM2kw39ymh6rMcnN+5VULwFs++lEYUUsro= -github.com/DataDog/sketches-go v1.2.1/go.mod h1:1xYmPLY1So10AwxV6MJV0J53XVH+WL9Ad1KetxVivVI= -github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798 h1:2T/jmrHeTezcCM58lvEQXs0UpQJCo5SoGAcg+mbSTIg= -github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61 h1:1NIUJ+MAMpqDr4LWIfNsoJR+G7zg/8GZVwuRkmJxtTc= -github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61/go.mod h1:62qWSDaEI0BLykU+zQza5CAKgW0lOy9oBSz3/DvYz4w= -github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= -github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.25 h1:fRMwXiwk3qDwc0P05eHnh+y2v07JdtsfQ1fuAc69m9g= -github.com/Microsoft/hcsshim v0.8.25/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= +github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/sarama v1.23.1 h1:XxJBCZEoWJtoWjf/xRbmGUpAmTZGnuuF0ON0EvxxBrs= -github.com/Shopify/sarama v1.23.1/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= -github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= -github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY= github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY= -github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 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/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= 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/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +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= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= -github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= -github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= -github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= -github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.15.90/go.mod h1:es1KtYUFs7le0xQ3rOihkuoVD90z7D0fR2Qm4S00/gU= -github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.44.47 h1:uyiNvoR4wfZ8Bp4ghgbyzGFIg5knjZMUAd5S9ba9qNU= -github.com/aws/aws-sdk-go v1.44.47/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +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/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2 v1.20.3 h1:lgeKmAZhlj1JqN43bogrM75spIvYnRxqTAh1iupu1yE= +github.com/aws/aws-sdk-go-v2 v1.20.3/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= +github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw= +github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A= +github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA= +github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.40 h1:CXceCS9BrDInRc74GDCQ8Qyk/Gp9VLdK+Rlve+zELSE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.40/go.mod h1:5kKmFhLeOVy6pwPDpDNA6/hK/d6URC98pqDDqHgdBx4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.34 h1:B+nZtd22cbko5+793hg7LEaTeLMiZwlgCLUrN5Y0uzg= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.34/go.mod h1:RZP0scceAyhMIQ9JvFp7HvkpcgqjL4l/4C+7RAeGbuM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.34 h1:JwvXk+1ePAD9xkFHprhHYqwsxLDcbNFsPI1IAT2sPS0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.34/go.mod h1:ytsF+t+FApY2lFnN51fJKPhH6ICKOPXKEcwwgmJEdWI= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 h1:PwNeYoonBzmTdCztKiiutws3U24KrnDBuabzRfIlZY4= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2/go.mod h1:gQhLZrTEath4zik5ixIe6axvgY5jJrgSBDJ360Fxnco= +github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 h1:p4mTxJfCAyiTT4Wp6p/mOPa6j5MqCSRGot8qZwFs+Z0= +github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4/go.mod h1:VBLWpaHvhQNeu7N9rMEf00SWeOONb/HvaDUxe/7b44k= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY= +github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= +github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= 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= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/goterm v1.0.0 h1:ZB6uUlY8+sjJyFGzz2WpRqX2XYPeXVgtZAOJMwOsTWM= -github.com/buger/goterm v1.0.0/go.mod h1:16STi3LquiscTIHA8SXUNKEa/Cnu4ZHBH8NsCaWgso0= -github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/bugsnag-go v1.4.1/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/bugsnag-go v1.5.0 h1:tP8hiPv1pGGW3LA6LKy5lW6WG+y9J2xWUdPd3WC452k= -github.com/bugsnag/bugsnag-go v1.5.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA= -github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= +github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= 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.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 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/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= -github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= -github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw= -github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo= github.com/cloudflare/cloudflare-go v0.70.0 h1:4opGbUygM8DjirUuaz23jn3akuAcnOCEx+0nQtQEcFo= github.com/cloudflare/cloudflare-go v0.70.0/go.mod h1:VW6GuazkaZ4xEDkFt24lkXQUsE8q7BiGqDniC2s8WEM= -github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4= -github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo= -github.com/cnabio/cnab-go v0.10.0-beta1/go.mod h1:5c4uOP6ZppR4nUGtCMAElscRiYEUi44vNQwtSAvISXk= -github.com/cnabio/cnab-to-oci v0.3.1-beta1/go.mod h1:8BomA5Vye+3V/Kd2NSFblCBmp1rJV5NfXBYKbIGT5Rw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 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= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -353,464 +207,215 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go v1.0.2/go.mod h1:gCIA3No0j5nNszz2X0yd/mkigTIWcOgHiPa9guWvoec= -github.com/compose-spec/compose-go v1.0.3 h1:yvut1x9H4TUMptNA4mZ63VGVtDNX1OKy2TZCi6yFw8Q= -github.com/compose-spec/compose-go v1.0.3/go.mod h1:gCIA3No0j5nNszz2X0yd/mkigTIWcOgHiPa9guWvoec= -github.com/compose-spec/godotenv v1.0.0 h1:TV24JYhh5GCC1G14npQVhCtxeoiwd0NcT0VdwcCQyXU= -github.com/compose-spec/godotenv v1.0.0/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.0.0.20210122062454-5a66c2ae5cec/go.mod h1:p9z+oqCID32tZ7LKgej316N9pJf1iIkQ/7nK1VvEFZE= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.5/go.mod h1:oSTh0QpT1w6jYcGmbiSbxv9OSQYaa88mPyWIuU79zyo= -github.com/containerd/containerd v1.5.17 h1:NLDEI//zhMZpR3DS/AP0qiN+dzYKNAwJaNXCnCmYcgY= -github.com/containerd/containerd v1.5.17/go.mod h1:7IN9MtIzTZH4WPEmD1gNH8bbTQXVX68yd3ZXxSHYCis= -github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190816180239-bda0ff6ed73c/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0 h1:6PirWBr9/L7GDamKr+XM0IeUFXu5mf3M/BPpH9gaLBU= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fuse-overlayfs-snapshotter v1.0.2/go.mod h1:nRZceC8a7dRm3Ao6cJAwuJWPFiBPaibHiFntRUnzhwU= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/stargz-snapshotter v0.4.1/go.mod h1:H59SY9Rw+vkIfWtuuyjeLeAc7uuALmuvUdAS6w+xmEw= -github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.1.0 h1:GbtyLRxb0gOLR0TYQWt3O6B0NvT8tMdorEHqIQo/lWI= -github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= +github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd h1:0n+lFLh5zU0l6KSk3KpnDwfbPGAR44aRLgTbCnhRBHU= github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd/go.mod h1:BbQgeDS5i0tNvypwEoF1oNjOJw8knRAE1DnVvjDstcQ= -github.com/containous/check v0.0.0-20170915194414-ca0bf163426a h1:8esAQaPKjfntQR1bag/mAOvWJd5HqSX5nsa+0KT63zo= -github.com/containous/check v0.0.0-20170915194414-ca0bf163426a/go.mod h1:eQOqZ7GoFsLxI7jFKLs7+Nv2Rm1x4FyK8d2NV+yGjwQ= github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e h1:D+uTEzDZc1Fhmd0Pq06c+O9+KkAyExw0eVmu/NOqaHU= github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e/go.mod h1:s8kLgBQolDbsJOPVIGCEEv9zGAKUUf/685Gi0Qqg8z8= github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595 h1:aPspFRO6b94To3gl4yTDOEtpjFwXI7V2W+z0JcNljQ4= github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595/go.mod h1:+lHFbEasIiQVGzhVDVw/cn0ZaOzde2OwNncp1NhXV4c= -github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= +github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f h1:1uEtynq2C0ljy3630jt7EAxg8jZY2gy6YHdGwdqEpWw= +github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4= github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= -github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= -github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/deepmap/oapi-codegen v1.9.1 h1:yHmEnA7jSTUMQgV+uN02WpZtwHnz2CBW3mZRIxr1vtI= github.com/deepmap/oapi-codegen v1.9.1/go.mod h1:PLqNAhdedP8ttRpBBkzLKU3bp+Fpy+tTgeAMlztR2cw= -github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= -github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= -github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.1.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= -github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= -github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e h1:n81KvOMrLZa+VWHwST7dun9f0G98X3zREHS1ztYzZKU= -github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= 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/docker/buildx v0.5.2-0.20210422185057-908a856079fc h1:oqPGOy23wxFCyOMSfdZTk02F7qvPi7kUEEeKrExKtfw= -github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc/go.mod h1:T5sa7xGu8G7dLXwaLLj6dRbJ5mxugPFDfELGnO2B5lQ= -github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc= -github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli-docs-tool v0.1.1/go.mod h1:oMzPNt1wC3TcxuY22GMnOODNOxkwGH51gV3AhqAjFQ4= -github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE= -github.com/docker/compose-switch v1.0.2/go.mod h1:uyPj8S3oH1O9rSZ5QVozw28OIjdNIflSSYElC2P0plQ= -github.com/docker/compose/v2 v2.0.1 h1:7LGiEWWFSTxTq2iN8hKHgFWi+iTBmHYNW/0Xo9JXffw= -github.com/docker/compose/v2 v2.0.1/go.mod h1:urQiI7sPYgS5RqIqZeDLAwumdEMjNFE2ziofshucFrM= -github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= +github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v0.0.0-20200511152416-a93e9eb0e95c/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v1.4.2-0.20180531152204-71cd53e4a197/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v1.4.2-0.20181229214054-f76d6a078d88/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v17.12.0-ce-rc1.0.20200730172259-9f28837c1d93+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog= -github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/docker-credential-helpers v0.6.4-0.20210125172408-38bea2ce277a h1:1DndKi+f9qs3AexOfI6di7RkWcWlNHzdqHu1cX19/ac= -github.com/docker/docker-credential-helpers v0.6.4-0.20210125172408-38bea2ce277a/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= -github.com/docker/go v1.5.1-1/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= -github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= -github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libnetwork v0.8.0-dev.2.0.20201215162534-fa125a3512ee h1:VQGPaek8TO9sRNRVNXmjzrya1SmleN0cMf/vvyjjJHo= -github.com/docker/libnetwork v0.8.0-dev.2.0.20201215162534-fa125a3512ee/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= -github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/elastic/go-licenser v0.3.1 h1:RmRukU/JUmts+rpexAw0Fvt2ly7VVu6mw8z4HrEzObU= -github.com/elastic/go-licenser v0.3.1/go.mod h1:D8eNQk70FOCVBl3smCGQt/lv7meBeQno2eI1S5apiHQ= -github.com/elastic/go-sysinfo v1.1.1 h1:ZVlaLDyhVkDfjwPGU55CQRCRolNpc7P0BbyhhQZQmMI= -github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= -github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= -github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= -github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exoscale/egoscale v0.100.1 h1:iXsV1Ei7daqe/6FYSCSDyrFs1iUG1l1X9qNh2uMw6z0= github.com/exoscale/egoscale v0.100.1/go.mod h1:BAb9p4rmyU+Wl400CJZO5270H2sXtdsZjLcm5xMKkz4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 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/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.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e/go.mod h1:HyVoz1Mz5Co8TFO8EupIdlcpwShBmY98dkT2xeHkvEI= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= -github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.87.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= -github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-acme/lego/v4 v4.13.2 h1:liIHWM9Wr3bmQ5s8UukfPhC4HOOaue9jRkUyrd8Dk7Y= -github.com/go-acme/lego/v4 v4.13.2/go.mod h1:c/iodVGMeBXG/+KiQczoNkySo3YLWTVa0kiyeVd/FHc= -github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +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.14.0 h1:/skZoRHgVh0d2RK7l1g3Ch8HqeqP9LB8ZEjLdGEpcDE= +github.com/go-acme/lego/v4 v4.14.0/go.mod h1:zjmvNCDLGz7GrC1OqdVpVmZFKSRabEDtWbdzmcpBsGo= 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-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= 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.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea h1:CnEQOUv4ilElSwFB9g/lVmz206oLE4aNZDYngIY1Gvg= github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 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.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +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-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= -github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +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-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= -github.com/gobuffalo/flect v0.2.3/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +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/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +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= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.3.0/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU= -github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= @@ -822,32 +427,21 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 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/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -858,95 +452,62 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 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/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= -github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= -github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= -github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.0.0-20191015185424-71da34e4d9b3/go.mod h1:ZXFeSndFcK4vB1NR4voH1Zm38K7ViUNiYtfIBDxrwf0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= +github.com/google/s2a-go v0.1.5 h1:8IYp3w9nysqv3JH+NJgXJzGbDHzLOTj43BmSkp+O7qg= +github.com/google/s2a-go v0.1.5/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= +github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= 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.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= 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= @@ -954,100 +515,54 @@ github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15 github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA= 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/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf h1:C1GPyPJrOlJlIrcaBBiBpDsqZena2Ks8spa5xZqr1XQ= github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf/go.mod h1:zXqxTI6jXDdKnlf8s+nT+3c8LrwUEy3yNpO4XJL90lA= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 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.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.14.0 h1:t7uX3JBHdVwAi3G7sSSdbsk8NfgA+LnUS88V/2EKaA0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.14.0/go.mod h1:4OGVnY4qf2+gw+ssiHbW+pq4mo2yko94YxxMmXZ7jCA= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= -github.com/hanwen/go-fuse/v2 v2.0.4-0.20201208195215-4a458845028b/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY= -github.com/hashicorp/consul v1.10.12 h1:xMazys3KaH5JsZS4Ra6KEAXO0nAj20EsTpsDyhd/3Do= -github.com/hashicorp/consul v1.10.12/go.mod h1:z9c9dZQI0/tKODT1x3s32wLbHyLfkG8G+FRYtn36UAc= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/api v1.10.0/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= -github.com/hashicorp/consul/api v1.14.0 h1:Y64GIJ8hYTu+tuGekwO4G4ardXoiCivX9wv1iP/kihk= -github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ= +github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= +github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/consul/sdk v0.10.0 h1:rGLEh2AWK4K0KCMvqWAz2EYxQqgciIfMagWZ0nVe5MI= -github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= -github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= -github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= +github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-bexpr v0.1.2 h1:ijMXI4qERbzxbCnkxmfUtwMyjrrk3y+Vt0MxojNCbBs= -github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= -github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 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-connlimit v0.3.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VVPGNyVPnUX8AqS0= -github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840/go.mod h1:Abjk0jbRkDaNCzsRhOv2iDCofYpX1eVsjozoiK63qLA= -github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +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-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.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= -github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= -github.com/hashicorp/go-memdb v1.3.1/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 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-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -1059,77 +574,43 @@ 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 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcl/v2 v2.8.2/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= -github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.1 h1:MXgUXLqva1QvpVEDQW1IQLG0wivQAtmFlHRQ+1vWZfM= -github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= -github.com/hashicorp/nomad/api v0.0.0-20220506174431-b5665129cd1f h1:jSBbBJcPca465gK6XfwdXRQnFCd63e0oJmqllZTsawI= -github.com/hashicorp/nomad/api v0.0.0-20220506174431-b5665129cd1f/go.mod h1:b/AoT79m3PEpb6tKCFKva/M+q1rKJNUk5mdu1S8DymM= -github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= -github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= -github.com/hashicorp/raft v1.3.6 h1:v5xW5KzByoerQlN/o31VJrFNiozgzGyDoMgDJgXpsto= -github.com/hashicorp/raft v1.3.6/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= -github.com/hashicorp/raft-autopilot v0.1.5 h1:onEfMH5uHVdXQqtas36zXUHEZxLdsJVu/nXHLcLdL1I= -github.com/hashicorp/raft-autopilot v0.1.5/go.mod h1:Af4jZBwaNOI+tXfIqIdbcAnh/UyyqIMj/pOISIfhArw= -github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b h1:R1UDhkwGltpSPY9bCBBxIMQd+NY9BkN0vFHnJo/8o8w= +github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b/go.mod h1:ijDwa6o1uG1jFSq6kERiX2PamKGpZzTmo0XOFNeFZgw= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/serf v0.9.7 h1:hkdgbqizGQHuU5IPqYM1JdSMV8nKfpuOnZYXssk9muY= -github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0= -github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= -github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= -github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 h1:brI5vBRUlAlM34VFmnLPwjnCL/FxAJp9XvOdX6Zt+XE= -github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/http-wasm/http-wasm-host-go v0.5.2 h1:5d/QgaaJtTF+qd0goBaxJJ7tcHP9n+gQUldJ7TsTexA= +github.com/http-wasm/http-wasm-host-go v0.5.2/go.mod h1:zQB3w+df4hryDEqBorGyA1DwPJ86LfKIASNLFuj6CuI= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= -github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb-client-go/v2 v2.7.0 h1:QgP5mlBE9sGnzplpnf96pr+p7uqlIlL4W2GAP3n+XZg= github.com/influxdata/influxdb-client-go/v2 v2.7.0/go.mod h1:Y/0W1+TZir7ypoQZYd2IrnVOKB3Tq6oegAQeSVN/+EU= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= @@ -1138,103 +619,54 @@ github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7 github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= 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/instana/go-sensor v1.38.3 h1:/PdHEDveLmUCvK+O6REvSv8kKljX8vaj9JMjMeCHAWk= -github.com/instana/go-sensor v1.38.3/go.mod h1:E42MelHWFz11qqaLwvgt0j98v2s2O/bq22UDkGaG0Gg= -github.com/instana/testify v1.6.2-0.20200721153833-94b1851f4d65 h1:T25FL3WEzgmKB0m6XCJNZ65nw09/QIp3T1yXr487D+A= -github.com/instana/testify v1.6.2-0.20200721153833-94b1851f4d65/go.mod h1:nYhEREG/B7HUY7P+LKOrqy53TpIqmJ9JyUShcaEKtGw= -github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= -github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= 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/jcchavezs/porto v0.1.0 h1:Xmxxn25zQMmgE7/yHYmh19KcItG81hIwfbEEFnd6w/Q= -github.com/jcchavezs/porto v0.1.0/go.mod h1:fESH0gzDHiutHRdX2hv27ojnOVFco37hg1W6E9EZF4A= -github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM= -github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= -github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE= -github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw= -github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc= -github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= 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= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= -github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= -github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= -github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +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/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= -github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 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= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= -github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= -github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= -github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -1242,14 +674,12 @@ github.com/kvtools/consul v1.0.2 h1:ltPgs4Ld09Xaa7zrOJ/TewBYKAsr11/LRFpErdkb8AA= github.com/kvtools/consul v1.0.2/go.mod h1:bFnzfGJ5ZIRRXCBGBmwhJlLdEWOlrjOcS1WjyAQzaJA= github.com/kvtools/etcdv3 v1.0.2 h1:EB0mAtzqe1folE7m7Q6wnCXcGwaOmrYmsVmF3hNsTKI= github.com/kvtools/etcdv3 v1.0.2/go.mod h1:Xr6DbwqjuCEcXAIWmXxw0DX+N5BhuvablXgN90XeqMM= -github.com/kvtools/redis v1.0.2 h1:D3GjGGtssJF2w8mniWtIxcT/YX9YnRc4jNCm0hrVygQ= -github.com/kvtools/redis v1.0.2/go.mod h1:wuUNwwKOHi2TYxDxj1sGF74Jdg0jywydnatXtnOR3hA= +github.com/kvtools/redis v1.1.0 h1:nXRAyh2nsaWiJyrX449/qHMc3SvGUqRqRXcrA/MplEo= +github.com/kvtools/redis v1.1.0/go.mod h1:cqg3esJOIYMQ1qy5LVIbPZz9kuiBBcFREP2N5b9+Dn0= github.com/kvtools/valkeyrie v1.0.0 h1:LAITop2wPoYCMitR24GZZsW0b57hmI+ePD18VRTtOf0= github.com/kvtools/valkeyrie v1.0.0/go.mod h1:bDi/OdhJCSbGPMsCgUQl881yuEweKCSItAtTBI+ZjpU= github.com/kvtools/zookeeper v1.0.2 h1:uK0CzQa+mtKGxDDH+DeqXo2HC1Kx4hWXZ7pX/zS4aTo= github.com/kvtools/zookeeper v1.0.2/go.mod h1:6TfxUwJ7IuBk5srgnoe528W0ftanNECHgOiShx/t0Aw= -github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPCK0jE6YNBAevnk= @@ -1263,6 +693,7 @@ github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= github.com/lestrrat-go/codegen v1.0.2/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM= @@ -1270,13 +701,8 @@ github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++ github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= github.com/lestrrat-go/jwx v1.2.7/go.mod h1:bw24IXWbavc0R2RsOtpXL7RtMyP589yZ1+L7kd09ZGA= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= github.com/linode/linodego v1.17.2 h1:b32dj4662PGG5P9qVa6nBezccWdqgukndlMIuPGq1CQ= github.com/linode/linodego v1.17.2/go.mod h1:C2iyT3Vg2O2sPxkWka4XAQ5WSUtm5LmTZ3Adw43Ra7Q= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= @@ -1286,30 +712,23 @@ github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAW github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOEYGSKrk4= github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20= -github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailgun/multibuf v0.1.2 h1:QE9kE27lK6LFZB4aYNVtUPlWVHVCT0zpgUr2uoq/+jk= github.com/mailgun/multibuf v0.1.2/go.mod h1:E+sUhIy69qgT6EM57kCPdUTlHnjTuxQBO/yf6af9Hes= github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 h1:Kg/NPZLLC3aAFr1YToMs98dbCdhootQ1hZIvZU28hAQ= github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51/go.mod h1:RYmqHbhWwIz3z9eVmQ2rx82rulEMG0t+Q1bzfc9DYN4= github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f h1:ZZYhg16XocqSKPGNQAe0aeweNtFxuedbwwb4fSlg7h4= github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f/go.mod h1:8heskWJ5c0v5J9WH89ADhyal1DOZcayll8fSbhB+/9A= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1319,8 +738,9 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -1329,36 +749,26 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 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.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= -github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= 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/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -1369,76 +779,43 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= -github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1-0.20210112042008-8ebf2d61a8b4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/mitchellh/pointerstructure v1.0.0 h1:ATSdz4NWrmWPOF1CeCBU4sMCno2hgqdbSrRPFWQSVZI= -github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf h1:dHwWBX8OhYb69qVcT27rFSwzKsn4CRbg0HInQyVh33c= -github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf/go.mod h1:GJcrUlTGFAPlEmPQtbrTsJYn+cy+Jwl7vTZS7jYAoow= -github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= -github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM= -github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= -github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= -github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= -github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= -github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/natefinch/lumberjack v0.0.0-20201021141957-47ffae23317c h1:194MYKszq5DlJ73wpFuOTEsC/ryOOxt2F901D/07tec= @@ -1451,10 +828,7 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= 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/desec v0.7.0 h1:iuGhi4pstF3+vJWwt292Oqe2+AsSPKDynQna/eu1fDs= @@ -1480,94 +854,45 @@ github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtb github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= 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.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc92/go.mod h1:X1zlU4p7wOlX4+WRCz+hvlRv8phdL7UqbYD+vQwNMmE= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.1/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= 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.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU= 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 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= 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/outcaste-io/ristretto v0.2.1 h1:KCItuNIGJZcursqHr3ghO7fc5ddZLEHspL9UR0cQM64= -github.com/outcaste-io/ristretto v0.2.1/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/ovh/go-ovh v1.4.1 h1:VBGa5wMyQtTP7Zb+w97zRCh9sLtM/2YKRyy+MEJmWaM= github.com/ovh/go-ovh v1.4.1/go.mod h1:6bL6pPyUT7tBfI0pqOegJgRjgjuO+mOo+MyXd1EEC0M= -github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= 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= @@ -1577,134 +902,90 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= -github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= -github.com/pierrec/lz4 v2.5.2+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/pivotal/image-relocation v0.0.0-20191111101224-e94aff6df06c/go.mod h1:/JNbQwGylYm3AQh8q+MBF8e/h0W1Jy20JGTvozuXYTE= 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/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-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/qri-io/jsonpointer v0.1.0/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64= -github.com/qri-io/jsonschema v0.1.1/go.mod h1:QpzJ6gBQ0GYgGmh7mDQ1YsvvhSgE4rYj0k8t5MBOmUY= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= -github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk= -github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= -github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= -github.com/rboyer/safeio v0.2.1/go.mod h1:Cq/cEPK+YXFn622lsQ0K4KsPZSPtaptHHEldsy7Fmig= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= +github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= +github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q= +github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= -github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 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.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +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/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.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= -github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 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= @@ -1713,54 +994,40 @@ github.com/sacloud/iaas-api-go v1.11.1 h1:2MsFZ4H1uRdRVx2nVXuERWQ3swoFc3XreIV5hJ 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/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA= -github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY= -github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= -github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 h1:1WuWJu7/e8SqK+uQl7lfk/N/oMZTL2NE/TJsNKRNMc4= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= -github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= -github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= 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/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/secure-systems-lab/go-securesystemslib v0.3.1/go.mod h1:o8hhjkbNl2gOamKUA/eNW3xUrntHT9L4W89W1nfj43U= -github.com/secure-systems-lab/go-securesystemslib v0.5.0 h1:oTiNu0QnulMQgN/hLK124wJD/r2f9ZhIUuKIeBsCBT8= -github.com/secure-systems-lab/go-securesystemslib v0.5.0/go.mod h1:uoCqUC0Ap7jrBSEanxT+SdACYJTVplRXWLkGMuDjXqk= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= -github.com/shirou/gopsutil/v3 v3.20.10/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= +github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ= +github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 h1:ZTzdx88+AcnjqUfJwnz89UBrMSBQ1NEysg9u5d+dU9c= github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04/go.mod h1:5KS21fpch8TIMyAUv/qQqTa3GZfBDYgjaZbd2KXKYfg= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= +github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 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/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= +github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= github.com/softlayer/softlayer-go v1.1.2 h1:rUSSGCyaxymvTOsaFjwr+cGxA8muw3xg2LSrIMNcN/c= github.com/softlayer/softlayer-go v1.1.2/go.mod h1:hvAbzGH4LRXA6yXY8BNx99yoqZ7urfDdtl9mvBf0G+g= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= @@ -1768,58 +1035,32 @@ github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVd 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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= -github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spiffe/go-spiffe/v2 v2.1.1 h1:RT9kM8MZLZIsPTH+HKQEP5yaAk3yd/VBzlINaRjXs8k= github.com/spiffe/go-spiffe/v2 v2.1.1/go.mod h1:5qg6rpqlwIub0JAiF1UK9IMD6BpPTmvG6yfSgDBs5lg= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1827,278 +1068,186 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g= github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2 h1:xwMw7LFhV9dbvot9A7NLClP9udqbjrQlIwWMH8e7uiQ= github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2/go.mod h1:hL4gB6APAasMR2NNi/JHzqKkxW3EPQlFgLEq9PMi2t0= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= 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/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= -github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0= -github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY= -github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw= -github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= +github.com/testcontainers/testcontainers-go v0.27.0 h1:IeIrJN4twonTDuMuBNQdKZ+K97yd7VrmNGu+lDpYcDk= +github.com/testcontainers/testcontainers-go v0.27.0/go.mod h1:+HgYZcd17GshBUZv9b+jKFJ198heWPQq3KQIp2+N+7U= +github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0= +github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 h1:014iQD8i8EabPWK2XgUuOTxg5s2nhfDmq6GupskfUO8= -github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo= -github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= -github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= -github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305 h1:y/1cL5AL2oRcfzz8CAHHhR6kDDfIOT0WEyH5k40sccM= -github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305/go.mod h1:gXOLibKqQTRAVuVZ9gX7G9Ykky8ll8yb4slxsEMoY0c= +github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltascY= +github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4= github.com/traefik/paerser v0.2.0 h1:zqCLGSXoNlcBd+mzqSCLjon/I6phqIjeJL2xFB2ysgQ= github.com/traefik/paerser v0.2.0/go.mod h1:afzaVcgF8A+MpTnPG4wBr4whjanCSYA6vK5RwaYVtRc= github.com/traefik/yaegi v0.15.1 h1:YA5SbaL6HZA0Exh9T/oArRHqGN2HQ+zgmCY7dkoTXu4= github.com/traefik/yaegi v0.15.1/go.mod h1:AVRxhaI2G+nUsaM1zyktzwXn69G3t/AuTDrCiTds9p0= github.com/transip/gotransip/v6 v6.20.0 h1:AuvwyOZ51f2brzMbTqlRy/wmaM3kF7Vx5Wds8xcDflY= github.com/transip/gotransip/v6 v6.20.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -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.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= -github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +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/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= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c h1:mKnW6IGLw7uXu6DL6RitufZWcXS6hCnauXRUFof7rKM= github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c/go.mod h1:F4UyVEmq4/m5lAmx+GccrxyRCXmnBjzUL09JLTQFp94= github.com/unrolled/render v1.0.2 h1:dGS3EmChQP3yOi1YeFNO/Dx+MbWZhdvhQJTXochM5bs= github.com/unrolled/render v1.0.2/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM= github.com/unrolled/secure v1.0.9 h1:BWRuEb1vDrBFFDdbCnKkof3gZ35I/bnHGyt0LB0TNyQ= github.com/unrolled/secure v1.0.9/go.mod h1:fO+mEan+FLB0CdEnHf6Q4ZZVNqG+5fuLFnP8p0BXDPI= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vdemeester/shakers v0.1.0 h1:K+n9sSyUCg2ywmZkv+3c7vsYZfivcfKhMh8kRxCrONM= -github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LWiKsh9RZtAQ= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c h1:Qt/YKpE8uAKNF4x2mwBZxmVo2WtgUL1WFDeXr1nlfpA= github.com/vulcand/oxy/v2 v2.0.0-20230427132221-be5cf38f3c1c/go.mod h1:A2voDnpONyqdplUDK0lt5y4XHLiBXPBw7iQES8+ZWRw= github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50= github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= -github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= -github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU= -github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 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/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/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= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ= -github.com/zclconf/go-cty v1.7.1/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= -github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= -github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw= -github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8= -github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb h1:vxqkjztXSaPVDc8FQCdHTaejm2x747f6yPbnu1h2xkg= -github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY= -go.elastic.co/apm v1.13.1 h1:ICIcUcQOImg/bve9mQVyLCvm1cSUZ1afdwK6ACnxczU= -go.elastic.co/apm v1.13.1/go.mod h1:dylGv2HKR0tiCV+wliJz1KHtDyuD8SPe69oV7VyK6WY= -go.elastic.co/apm/module/apmhttp v1.13.1 h1:g2id6+AY8NRSA6nzwPDSU1AmBiHyZeh/lJRBlXq2yfQ= -go.elastic.co/apm/module/apmhttp v1.13.1/go.mod h1:PmSy4HY0asQzoFpl+gna9n+ebfI43fPvo21sd22gquE= -go.elastic.co/apm/module/apmot v1.13.1 h1:4PCbjgVz0A/9a/wuiHu1en83TLgmBbK9fJwLgES8Rr8= -go.elastic.co/apm/module/apmot v1.13.1/go.mod h1:NnG6U6ahaixUHpjQioL0QvVtOxWjVn8Z09qJHSCsmqU= -go.elastic.co/fastjson v1.1.0 h1:3MrGBWWVIxe/xvsbpghtkFoPciPhOCmjsR/HfwEeQR4= -go.elastic.co/fastjson v1.1.0/go.mod h1:boNGISWMjQsUPy/t6yqt2/1Wx4YNPSe+mZjlyw9vKKI= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= -go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.5 h1:9S0JUVvmrVl7wCF39iTQthdaaNIiAaQbmK75ogO6GU8= -go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.5 h1:q++2WTJbUgpQu4B6hCuT7VkdwaTP7Qz6Daak3WzbrlI= -go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/collector/pdata v0.64.1 h1:8E06uHr0nnenGftFwhwdenA88QhVnF4dJam+qVXgdVg= -go.opentelemetry.io/collector/pdata v0.64.1/go.mod h1:IzvXUGQml2mrnvdb8zIlEW3qQs9oFLdD2hLwJdZ+pek= -go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= -go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= -go.opentelemetry.io/otel/bridge/opentracing v1.11.2 h1:Wx51zQDSZDNo5wxMPhkPwzgpUZLQYYDtT41LCcl7opg= -go.opentelemetry.io/otel/bridge/opentracing v1.11.2/go.mod h1:kBrIQ2vqDIqtuS7Np7ALjmm8Tml7yxgsAGQwBhNvuU0= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 h1:/fXHZHGvro6MVqV34fJzDhi7sHGpX3Ej/Qjmfn003ho= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0/go.mod h1:UFG7EBMRdXyFstOwH028U0sVf+AvukSGhF0g8+dmNG8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.37.0 h1:22J9c9mxNAZugv86zhwjBnER0DbO0VVpW9Oo/j3jBBQ= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.37.0/go.mod h1:QD8SSO9fgtBOvXYpcX5NXW+YnDJByTnh7a/9enQWFmw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.37.0 h1:CI6DSdsSkJxX1rsfPSQ0SciKx6klhdDRBXqKb+FwXG8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.37.0/go.mod h1:WLBYPrz8srktckhCjFaau4VHSfGaMuqoKSXwpzaiRZg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.37.0 h1:Ad4fpLq5t4s4+xB0chYBmbp1NNMqG4QRkseRmbx3bOw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.37.0/go.mod h1:hgpB6JpYB/K403Z2wCxtX5fENB1D4bSdAHG0vJI+Koc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 h1:fqR1kli93643au1RKo0Uma3d2aPQKT+WBKfTSBaKbOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2/go.mod h1:5Qn6qvgkMsLDX+sYK64rHb1FPhpn0UtxF+ouX1uhyJE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 h1:ERwKPn9Aer7Gxsc0+ZlutlH1bEEAUXAUhqm3Y45ABbk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2/go.mod h1:jWZUM2MWhWCJ9J9xVbRx7tzK1mXKpAlze4CeulycwVY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 h1:Us8tbCmuN16zAnK5TC69AtODLycKbwnskQzaB6DfFhc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2/go.mod h1:GZWSQQky8AgdJj50r1KJm8oiQiIPaAX7uZCFQX9GzC8= -go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs= -go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s= -go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= -go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= -go.opentelemetry.io/otel/sdk/metric v0.37.0 h1:haYBBtZZxiI3ROwSmkZnI+d0+AVzBWeviuYQDeBWosU= -go.opentelemetry.io/otel/sdk/metric v0.37.0/go.mod h1:mO2WV1AZKKwhwHTV3AKOoIEb9LbUaENZDuGUQd+j4A0= -go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= -go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +go.opentelemetry.io/collector/pdata v0.66.0 h1:UdE5U6MsDNzuiWaXdjGx2lC3ElVqWmN/hiUE8vyvSuM= +go.opentelemetry.io/collector/pdata v0.66.0/go.mod h1:pqyaznLzk21m+1KL6fwOsRryRELL+zNM0qiVSn0MbVc= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= 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.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +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.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +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/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= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= -go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= +golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 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-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 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= @@ -2107,14 +1256,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-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +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/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= @@ -2125,33 +1268,21 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.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-20180811021610-c39426892332/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= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2163,97 +1294,52 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs= -golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= 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= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2262,116 +1348,54 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/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-20210603081109-ebe580a85c40/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= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2380,26 +1404,27 @@ golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.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-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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= @@ -2408,26 +1433,22 @@ 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.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -2439,207 +1460,94 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191014205221-18e3458ac98b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -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.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 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.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.111.0 h1:bwKi+z2BsdwYFRKrqwutM+axAlYLz83gt5pDSXCJT+0= -google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg= +google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= 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.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.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +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-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= 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= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 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.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= 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= @@ -2655,34 +1563,23 @@ 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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/DataDog/dd-trace-go.v1 v1.51.0 h1:nFsTjolqdh8slG6F1B7AGdFHX7/kp26Jkb8UvGSIIFY= -gopkg.in/DataDog/dd-trace-go.v1 v1.51.0/go.mod h1:+m1wWLyQfqd6fX0uy6YFbP1soWgmjQ+5TveksDt/fHc= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/dancannon/gorethink.v3 v3.0.5 h1:/g7PWP7zUS6vSNmHSDbjCHQh1Rqn8Jy6zSMQxAsBSMQ= -gopkg.in/dancannon/gorethink.v3 v3.0.5/go.mod h1:GXsi1e3N2OcKhcP6nsYABTiUejbWMFO4GY5a4pEaeEc= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg= -gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU= -gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I= -gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= +gopkg.in/h2non/gentleman.v1 v1.0.4/go.mod h1:JYuHVdFzS4MKOXe0o+chKJ4hCe6tqKKw9XH9YP6WFrg= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/h2non/gock.v1 v1.0.16 h1:F11k+OafeuFENsjei5t2vMTSTs9L62AdyTe4E1cgdG8= +gopkg.in/h2non/gock.v1 v1.0.16/go.mod h1:XVuDAssexPLwgxCLMvDTWNU5eqklsydR6I5phZ9oPB8= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -2691,20 +1588,9 @@ 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/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= -gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc44010= -gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= -gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/ns1/ns1-go.v2 v2.7.6 h1:mCPl7q0jbIGACXvGBljAuuApmKZo3rRi4tlRIEbMvjA= gopkg.in/ns1/ns1-go.v2 v2.7.6/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 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= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -2722,142 +1608,45 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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-20200615113413-eeeca48fe776/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= -howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= -inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU= -inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k= -k8s.io/api v0.0.0-20180904230853-4e7be11eab3f/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/api v0.0.0-20191016110408-35e52d86657a/go.mod h1:/L5qH+AD540e7Cetbui1tuJeXdmNhO8jM6VkXeDdDhQ= -k8s.io/api v0.16.9/go.mod h1:Y7dZNHs1Xy0mSwSlzL9QShi6qkljnN41yR8oWCRTDe8= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= -k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= -k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= -k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= -k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= -k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE= -k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= -k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= -k8s.io/apimachinery v0.0.0-20180904193909-def12e63c512/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f/go.mod h1:+ntn62igV2hyNj7/0brOvXSMONE2KxcePkSxK7/9FFQ= -k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= -k8s.io/apimachinery v0.16.9/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= -k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= -k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= -k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= -k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU= -k8s.io/client-go v0.0.0-20180910083459-2cefa64ff137/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= -k8s.io/client-go v0.0.0-20191016111102-bec269661e48/go.mod h1:hrwktSwYGI4JK+TJA3dMaFyyvHVi/aLarVHpbs8bgCU= -k8s.io/client-go v0.16.9/go.mod h1:ThjPlh7Kx+XoBFOCt775vx5J7atwY7F/zaFzTco5gL0= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= -k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= -k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= -k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= -k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= -k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= -k8s.io/code-generator v0.22.0/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= -k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ= -k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= -k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= -k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 h1:xMMXJlJbsU8w3V5N2FLDQ8YgU8s1EoULdbQBcAeNJkY= -k8s.io/utils v0.0.0-20230313181309-38a27ef9d749/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= -mvdan.cc/xurls/v2 v2.1.0 h1:KaMb5GLhlcSX+e+qhbRJODnUUBvlw01jt4yrjFIHAuA= -mvdan.cc/xurls/v2 v2.1.0/go.mod h1:5GrSd9rOnKOpZaji1OZLYL/yeAAtGDlo/cFe+8K5n8E= -nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= +k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= +k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= +k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= +k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= +k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= +k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= +k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= +mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.9.6/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA= -sigs.k8s.io/controller-tools v0.6.2/go.mod h1:oaeGpjXn6+ZSEIQkUe/+3I40PNiDYp9aeawbt3xTgJ8= -sigs.k8s.io/gateway-api v0.4.0 h1:07IJkTt21NetZTHtPKJk2I4XIgDN4BAlTIq1wK7V11o= -sigs.k8s.io/gateway-api v0.4.0/go.mod h1:r3eiNP+0el+NTLwaTfOrCNXy8TukC+dIM3ggc+fbNWk= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/gateway-api v1.0.0 h1:iPTStSv41+d9p0xFydll6d7f7MOBGuqXM6p2/zVYMAs= +sigs.k8s.io/gateway-api v1.0.0/go.mod h1:4cUgr0Lnp5FZ0Cdq8FdRwCvpiWws7LVhLHGIudLlf4c= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= -vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/integration/access_log_test.go b/integration/access_log_test.go index 272a1b371..8e2c9581f 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -3,19 +3,23 @@ package integration import ( "crypto/md5" "crypto/rand" + "encoding/hex" + "encoding/json" "fmt" "io" "net/http" "os" "strconv" "strings" + "testing" "time" - "github.com/go-check/check" "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" - checker "github.com/vdemeester/shakers" ) const ( @@ -26,6 +30,10 @@ const ( // AccessLogSuite tests suite. type AccessLogSuite struct{ BaseSuite } +func TestAccessLogSuite(t *testing.T) { + suite.Run(t, new(AccessLogSuite)) +} + type accessLogValue struct { formatOnly bool code string @@ -34,67 +42,67 @@ type accessLogValue struct { serviceURL string } -func (s *AccessLogSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "access_log") - s.composeUp(c) +func (s *AccessLogSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + s.createComposeProject("access_log") + s.composeUp() } -func (s *AccessLogSuite) TearDownTest(c *check.C) { - displayTraefikLogFile(c, traefikTestLogFile) +func (s *AccessLogSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *AccessLogSuite) TearDownTest() { + s.displayTraefikLogFile(traefikTestLogFile) _ = os.Remove(traefikTestAccessLogFile) } -func (s *AccessLogSuite) TestAccessLog(c *check.C) { +func (s *AccessLogSuite) TestAccessLog() { ensureWorkingDirectoryIsClean() // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) + s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) defer func() { traefikLog, err := os.ReadFile(traefikTestLogFile) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) log.Info().Msg(string(traefikLog)) }() - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.waitForTraefik("server1") - waitForTraefik(c, "server1") - - checkStatsForLogFile(c) + s.checkStatsForLogFile() // Verify Traefik started OK - checkTraefikStarted(c) + s.checkTraefikStarted() // Make some requests req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "frontend1.docker.local" err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "frontend2.docker.local" err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify access.log output as expected - count := checkAccessLogOutput(c) + count := s.checkAccessLogOutput() - c.Assert(count, checker.GreaterOrEqualThan, 3) + assert.Equal(s.T(), 3, count) // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) + s.checkNoOtherTraefikProblems() } -func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) { +func (s *AccessLogSuite) TestAccessLogAuthFrontend() { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ @@ -122,48 +130,43 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) { } // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) + s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.checkStatsForLogFile() - checkStatsForLogFile(c) - - waitForTraefik(c, "authFrontend") + s.waitForTraefik("authFrontend") // Verify Traefik started OK - checkTraefikStarted(c) + s.checkTraefikStarted() // Test auth entrypoint req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8006/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "frontend.auth.docker.local" err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.SetBasicAuth("test", "") err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.SetBasicAuth("test", "test") err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) + count := s.checkAccessLogExactValuesOutput(expected) - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) + assert.GreaterOrEqual(s.T(), count, len(expected)) // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) + s.checkNoOtherTraefikProblems() } -func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware(c *check.C) { +func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware() { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ @@ -191,27 +194,22 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware(c *check.C) { } // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) + s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.checkStatsForLogFile() - checkStatsForLogFile(c) - - waitForTraefik(c, "digestAuthMiddleware") + s.waitForTraefik("digestAuthMiddleware") // Verify Traefik started OK - checkTraefikStarted(c) + s.checkTraefikStarted() // Test auth entrypoint req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8008/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "entrypoint.digest.auth.docker.local" resp, err := try.ResponseUntilStatusCode(req, 500*time.Millisecond, http.StatusUnauthorized) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) digest := digestParts(resp) digest["uri"] = "/" @@ -223,22 +221,22 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware(c *check.C) { req.Header.Set("Content-Type", "application/json") err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) digest["password"] = "test" req.Header.Set("Authorization", getDigestAuthorization(digest)) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) + count := s.checkAccessLogExactValuesOutput(expected) - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) + assert.GreaterOrEqual(s.T(), count, len(expected)) // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) + s.checkNoOtherTraefikProblems() } // Thanks to mvndaai for digest authentication @@ -264,7 +262,8 @@ func getMD5(data string) string { if _, err := digest.Write([]byte(data)); err != nil { log.Error().Err(err).Send() } - return fmt.Sprintf("%x", digest.Sum(nil)) + + return hex.EncodeToString(digest.Sum(nil)) } func getCnonce() string { @@ -272,7 +271,8 @@ func getCnonce() string { if _, err := io.ReadFull(rand.Reader, b); err != nil { log.Error().Err(err).Send() } - return fmt.Sprintf("%x", b)[:16] + + return hex.EncodeToString(b)[:16] } func getDigestAuthorization(digestParts map[string]string) string { @@ -287,7 +287,7 @@ func getDigestAuthorization(digestParts map[string]string) string { return authorization } -func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) { +func (s *AccessLogSuite) TestAccessLogFrontendRedirect() { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ @@ -304,38 +304,93 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) { } // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) + s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.checkStatsForLogFile() - checkStatsForLogFile(c) - - waitForTraefik(c, "frontendRedirect") + s.waitForTraefik("frontendRedirect") // Verify Traefik started OK - checkTraefikStarted(c) + s.checkTraefikStarted() // Test frontend redirect req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8005/test", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "" err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) + count := s.checkAccessLogExactValuesOutput(expected) - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) + assert.GreaterOrEqual(s.T(), count, len(expected)) // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) + s.checkNoOtherTraefikProblems() } -func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) { +func (s *AccessLogSuite) TestAccessLogJSONFrontendRedirect() { + ensureWorkingDirectoryIsClean() + + type logLine struct { + DownstreamStatus int `json:"downstreamStatus"` + OriginStatus int `json:"originStatus"` + RouterName string `json:"routerName"` + ServiceName string `json:"serviceName"` + } + + expected := []logLine{ + { + DownstreamStatus: 302, + OriginStatus: 0, + RouterName: "rt-frontendRedirect@docker", + ServiceName: "", + }, + { + DownstreamStatus: 200, + OriginStatus: 200, + RouterName: "rt-server0@docker", + ServiceName: "service1@docker", + }, + } + + // Start Traefik + s.traefikCmd(withConfigFile("fixtures/access_log_json_config.toml")) + + s.checkStatsForLogFile() + + s.waitForTraefik("frontendRedirect") + + // Verify Traefik started OK + s.checkTraefikStarted() + + // Test frontend redirect + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8005/test", nil) + require.NoError(s.T(), err) + req.Host = "" + + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) + require.NoError(s.T(), err) + + lines := s.extractLines() + assert.GreaterOrEqual(s.T(), len(lines), len(expected)) + + for i, line := range lines { + if line == "" { + continue + } + var logline logLine + err := json.Unmarshal([]byte(line), &logline) + require.NoError(s.T(), err) + assert.Equal(s.T(), expected[i].DownstreamStatus, logline.DownstreamStatus) + assert.Equal(s.T(), expected[i].OriginStatus, logline.OriginStatus) + assert.Equal(s.T(), expected[i].RouterName, logline.RouterName) + assert.Equal(s.T(), expected[i].ServiceName, logline.ServiceName) + } +} + +func (s *AccessLogSuite) TestAccessLogRateLimit() { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ @@ -355,42 +410,37 @@ func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) { } // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) + s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.checkStatsForLogFile() - checkStatsForLogFile(c) - - waitForTraefik(c, "rateLimit") + s.waitForTraefik("rateLimit") // Verify Traefik started OK - checkTraefikStarted(c) + s.checkTraefikStarted() // Test rate limit req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8007/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "ratelimit.docker.local" err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) + count := s.checkAccessLogExactValuesOutput(expected) - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) + assert.GreaterOrEqual(s.T(), count, len(expected)) // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) + s.checkNoOtherTraefikProblems() } -func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) { +func (s *AccessLogSuite) TestAccessLogBackendNotFound() { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ @@ -404,38 +454,33 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) { } // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) + s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.waitForTraefik("server1") - waitForTraefik(c, "server1") - - checkStatsForLogFile(c) + s.checkStatsForLogFile() // Verify Traefik started OK - checkTraefikStarted(c) + s.checkTraefikStarted() // Test rate limit req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "backendnotfound.docker.local" err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) + count := s.checkAccessLogExactValuesOutput(expected) - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) + assert.GreaterOrEqual(s.T(), count, len(expected)) // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) + s.checkNoOtherTraefikProblems() } -func (s *AccessLogSuite) TestAccessLogFrontendAllowlist(c *check.C) { +func (s *AccessLogSuite) TestAccessLogFrontendAllowlist() { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ @@ -449,38 +494,33 @@ func (s *AccessLogSuite) TestAccessLogFrontendAllowlist(c *check.C) { } // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) + s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.checkStatsForLogFile() - checkStatsForLogFile(c) - - waitForTraefik(c, "frontendAllowlist") + s.waitForTraefik("frontendAllowlist") // Verify Traefik started OK - checkTraefikStarted(c) + s.checkTraefikStarted() // Test rate limit req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "frontend.allowlist.docker.local" err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusForbidden), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) + count := s.checkAccessLogExactValuesOutput(expected) - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) + assert.GreaterOrEqual(s.T(), count, len(expected)) // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) + s.checkNoOtherTraefikProblems() } -func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess(c *check.C) { +func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess() { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ @@ -494,78 +534,115 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess(c *check.C) { } // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) + s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.checkStatsForLogFile() - checkStatsForLogFile(c) - - waitForTraefik(c, "authFrontend") + s.waitForTraefik("authFrontend") // Verify Traefik started OK - checkTraefikStarted(c) + s.checkTraefikStarted() // Test auth entrypoint req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8006/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "frontend.auth.docker.local" req.SetBasicAuth("test", "test") err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) + count := s.checkAccessLogExactValuesOutput(expected) - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) + assert.GreaterOrEqual(s.T(), count, len(expected)) // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) + s.checkNoOtherTraefikProblems() } -func checkNoOtherTraefikProblems(c *check.C) { +func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() { + ensureWorkingDirectoryIsClean() + + expected := []accessLogValue{ + { + formatOnly: false, + code: "200", + user: "-", + routerName: "rt-preflightCORS", + serviceURL: "-", + }, + } + + // Start Traefik + s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + + s.checkStatsForLogFile() + + s.waitForTraefik("preflightCORS") + + // Verify Traefik started OK + s.checkTraefikStarted() + + // Test preflight response + req, err := http.NewRequest(http.MethodOptions, "http://127.0.0.1:8009/", nil) + require.NoError(s.T(), err) + req.Host = "preflight.docker.local" + req.Header.Set("Origin", "whatever") + req.Header.Set("Access-Control-Request-Method", "GET") + + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + // Verify access.log output as expected + count := s.checkAccessLogExactValuesOutput(expected) + + assert.GreaterOrEqual(s.T(), count, len(expected)) + + // Verify no other Traefik problems + s.checkNoOtherTraefikProblems() +} + +func (s *AccessLogSuite) checkNoOtherTraefikProblems() { traefikLog, err := os.ReadFile(traefikTestLogFile) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if len(traefikLog) > 0 { fmt.Printf("%s\n", string(traefikLog)) } } -func checkAccessLogOutput(c *check.C) int { - lines := extractLines(c) +func (s *AccessLogSuite) checkAccessLogOutput() int { + lines := s.extractLines() count := 0 for i, line := range lines { if len(line) > 0 { count++ - CheckAccessLogFormat(c, line, i) + s.CheckAccessLogFormat(line, i) } } return count } -func checkAccessLogExactValuesOutput(c *check.C, values []accessLogValue) int { - lines := extractLines(c) +func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue) int { + lines := s.extractLines() count := 0 for i, line := range lines { fmt.Println(line) if len(line) > 0 { count++ if values[i].formatOnly { - CheckAccessLogFormat(c, line, i) + s.CheckAccessLogFormat(line, i) } else { - checkAccessLogExactValues(c, line, i, values[i]) + s.checkAccessLogExactValues(line, i, values[i]) } } } return count } -func extractLines(c *check.C) []string { +func (s *AccessLogSuite) extractLines() []string { accessLog, err := os.ReadFile(traefikTestAccessLogFile) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) lines := strings.Split(string(accessLog), "\n") @@ -578,14 +655,14 @@ func extractLines(c *check.C) []string { return clean } -func checkStatsForLogFile(c *check.C) { +func (s *AccessLogSuite) checkStatsForLogFile() { err := try.Do(1*time.Second, func() error { if _, errStat := os.Stat(traefikTestLogFile); errStat != nil { return fmt.Errorf("could not get stats for log file: %w", errStat) } return nil }) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } func ensureWorkingDirectoryIsClean() { @@ -593,69 +670,38 @@ func ensureWorkingDirectoryIsClean() { os.Remove(traefikTestLogFile) } -func checkTraefikStarted(c *check.C) []byte { +func (s *AccessLogSuite) checkTraefikStarted() []byte { traefikLog, err := os.ReadFile(traefikTestLogFile) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if len(traefikLog) > 0 { fmt.Printf("%s\n", string(traefikLog)) } return traefikLog } -func CheckAccessLogFormat(c *check.C, line string, i int) { +func (s *BaseSuite) CheckAccessLogFormat(line string, i int) { results, err := accesslog.ParseAccessLog(line) - c.Assert(err, checker.IsNil) - c.Assert(results, checker.HasLen, 14) - c.Assert(results[accesslog.OriginStatus], checker.Matches, `^(-|\d{3})$`) + require.NoError(s.T(), err) + assert.Len(s.T(), results, 14) + assert.Regexp(s.T(), `^(-|\d{3})$`, results[accesslog.OriginStatus]) count, _ := strconv.Atoi(results[accesslog.RequestCount]) - c.Assert(count, checker.GreaterOrEqualThan, i+1) - c.Assert(results[accesslog.RouterName], checker.Matches, `"(rt-.+@docker|api@internal)"`) - c.Assert(results[accesslog.ServiceURL], checker.HasPrefix, `"http://`) - c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`) + assert.GreaterOrEqual(s.T(), count, i+1) + assert.Regexp(s.T(), `"(rt-.+@docker|api@internal)"`, results[accesslog.RouterName]) + assert.True(s.T(), strings.HasPrefix(results[accesslog.ServiceURL], `"http://`)) + assert.Regexp(s.T(), `^\d+ms$`, results[accesslog.Duration]) } -func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue) { +func (s *AccessLogSuite) checkAccessLogExactValues(line string, i int, v accessLogValue) { results, err := accesslog.ParseAccessLog(line) - c.Assert(err, checker.IsNil) - c.Assert(results, checker.HasLen, 14) + require.NoError(s.T(), err) + assert.Len(s.T(), results, 14) if len(v.user) > 0 { - c.Assert(results[accesslog.ClientUsername], checker.Equals, v.user) + assert.Equal(s.T(), v.user, results[accesslog.ClientUsername]) } - c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code) + assert.Equal(s.T(), v.code, results[accesslog.OriginStatus]) count, _ := strconv.Atoi(results[accesslog.RequestCount]) - c.Assert(count, checker.GreaterOrEqualThan, i+1) - c.Assert(results[accesslog.RouterName], checker.Matches, `^"?`+v.routerName+`.*(@docker)?$`) - c.Assert(results[accesslog.ServiceURL], checker.Matches, `^"?`+v.serviceURL+`.*$`) - c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`) -} - -func waitForTraefik(c *check.C, containerName string) { - time.Sleep(1 * time.Second) - - // Wait for Traefik to turn ready. - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil) - c.Assert(err, checker.IsNil) - - err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains(containerName)) - c.Assert(err, checker.IsNil) -} - -func displayTraefikLogFile(c *check.C, path string) { - if c.Failed() { - if _, err := os.Stat(path); !os.IsNotExist(err) { - content, errRead := os.ReadFile(path) - fmt.Printf("%s: Traefik logs: \n", c.TestName()) - if errRead == nil { - fmt.Println(content) - } else { - fmt.Println(errRead) - } - } else { - fmt.Printf("%s: No Traefik logs.\n", c.TestName()) - } - errRemove := os.Remove(path) - if errRemove != nil { - fmt.Println(errRemove) - } - } + assert.GreaterOrEqual(s.T(), count, i+1) + assert.Regexp(s.T(), `^"?`+v.routerName+`.*(@docker)?$`, results[accesslog.RouterName]) + assert.Regexp(s.T(), `^"?`+v.serviceURL+`.*$`, results[accesslog.ServiceURL]) + assert.Regexp(s.T(), `^\d+ms$`, results[accesslog.Duration]) } diff --git a/integration/acme_test.go b/integration/acme_test.go index fe5fc413b..c6c92e24b 100644 --- a/integration/acme_test.go +++ b/integration/acme_test.go @@ -8,16 +8,19 @@ import ( "net/http" "os" "path/filepath" + "testing" "time" - "github.com/go-check/check" "github.com/miekg/dns" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/config/static" "github.com/traefik/traefik/v3/pkg/provider/acme" "github.com/traefik/traefik/v3/pkg/testhelpers" "github.com/traefik/traefik/v3/pkg/types" - checker "github.com/vdemeester/shakers" ) // ACME test suites. @@ -27,6 +30,10 @@ type AcmeSuite struct { fakeDNSServer *dns.Server } +func TestAcmeSuite(t *testing.T) { + suite.Run(t, new(AcmeSuite)) +} + type subCases struct { host string expectedCommonName string @@ -87,17 +94,18 @@ func setupPebbleRootCA() (*http.Transport, error) { }, nil } -func (s *AcmeSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "pebble") - s.composeUp(c) +func (s *AcmeSuite) SetupSuite() { + s.BaseSuite.SetupSuite() - s.fakeDNSServer = startFakeDNSServer(s.getContainerIP(c, "traefik")) - s.pebbleIP = s.getComposeServiceIP(c, "pebble") + s.createComposeProject("pebble") + s.composeUp() + + // Retrieving the Docker host ip. + s.fakeDNSServer = startFakeDNSServer(s.hostIP) + s.pebbleIP = s.getComposeServiceIP("pebble") pebbleTransport, err := setupPebbleRootCA() - if err != nil { - c.Fatal(err) - } + require.NoError(s.T(), err) // wait for pebble req := testhelpers.MustNewRequest(http.MethodGet, s.getAcmeURL(), nil) @@ -113,21 +121,24 @@ func (s *AcmeSuite) SetUpSuite(c *check.C) { } return try.StatusCodeIs(http.StatusOK)(resp) }) - c.Assert(err, checker.IsNil) + + require.NoError(s.T(), err) } -func (s *AcmeSuite) TearDownSuite(c *check.C) { +func (s *AcmeSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() + if s.fakeDNSServer != nil { err := s.fakeDNSServer.Shutdown() if err != nil { - c.Log(err) + log.Info().Msg(err.Error()) } } - s.composeDown(c) + s.composeDown() } -func (s *AcmeSuite) TestHTTP01Domains(c *check.C) { +func (s *AcmeSuite) TestHTTP01Domains() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_domains.toml", subCases: []subCases{{ @@ -147,10 +158,10 @@ func (s *AcmeSuite) TestHTTP01Domains(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestHTTP01StoreDomains(c *check.C) { +func (s *AcmeSuite) TestHTTP01StoreDomains() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_store_domains.toml", subCases: []subCases{{ @@ -170,10 +181,10 @@ func (s *AcmeSuite) TestHTTP01StoreDomains(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestHTTP01DomainsInSAN(c *check.C) { +func (s *AcmeSuite) TestHTTP01DomainsInSAN() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_domains.toml", subCases: []subCases{{ @@ -194,10 +205,10 @@ func (s *AcmeSuite) TestHTTP01DomainsInSAN(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestHTTP01OnHostRule(c *check.C) { +func (s *AcmeSuite) TestHTTP01OnHostRule() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_base.toml", subCases: []subCases{{ @@ -214,10 +225,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRule(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestMultipleResolver(c *check.C) { +func (s *AcmeSuite) TestMultipleResolver() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_multiple_resolvers.toml", subCases: []subCases{ @@ -245,10 +256,10 @@ func (s *AcmeSuite) TestMultipleResolver(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestHTTP01OnHostRuleECDSA(c *check.C) { +func (s *AcmeSuite) TestHTTP01OnHostRuleECDSA() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_base.toml", subCases: []subCases{{ @@ -266,10 +277,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleECDSA(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestHTTP01OnHostRuleInvalidAlgo(c *check.C) { +func (s *AcmeSuite) TestHTTP01OnHostRuleInvalidAlgo() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_base.toml", subCases: []subCases{{ @@ -287,10 +298,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleInvalidAlgo(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestHTTP01OnHostRuleDefaultDynamicCertificatesWithWildcard(c *check.C) { +func (s *AcmeSuite) TestHTTP01OnHostRuleDefaultDynamicCertificatesWithWildcard() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_tls.toml", subCases: []subCases{{ @@ -307,10 +318,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleDefaultDynamicCertificatesWithWildcard(c }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestHTTP01OnHostRuleDynamicCertificatesWithWildcard(c *check.C) { +func (s *AcmeSuite) TestHTTP01OnHostRuleDynamicCertificatesWithWildcard() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_tls_dynamic.toml", subCases: []subCases{{ @@ -327,10 +338,10 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleDynamicCertificatesWithWildcard(c *check }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestTLSALPN01OnHostRuleTCP(c *check.C) { +func (s *AcmeSuite) TestTLSALPN01OnHostRuleTCP() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_tcp.toml", subCases: []subCases{{ @@ -347,10 +358,10 @@ func (s *AcmeSuite) TestTLSALPN01OnHostRuleTCP(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestTLSALPN01OnHostRule(c *check.C) { +func (s *AcmeSuite) TestTLSALPN01OnHostRule() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_base.toml", subCases: []subCases{{ @@ -367,10 +378,10 @@ func (s *AcmeSuite) TestTLSALPN01OnHostRule(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestTLSALPN01Domains(c *check.C) { +func (s *AcmeSuite) TestTLSALPN01Domains() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_domains.toml", subCases: []subCases{{ @@ -390,10 +401,10 @@ func (s *AcmeSuite) TestTLSALPN01Domains(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } -func (s *AcmeSuite) TestTLSALPN01DomainsInSAN(c *check.C) { +func (s *AcmeSuite) TestTLSALPN01DomainsInSAN() { testCase := acmeTestCase{ traefikConfFilePath: "fixtures/acme/acme_domains.toml", subCases: []subCases{{ @@ -414,12 +425,12 @@ func (s *AcmeSuite) TestTLSALPN01DomainsInSAN(c *check.C) { }, } - s.retrieveAcmeCertificate(c, testCase) + s.retrieveAcmeCertificate(testCase) } // Test Let's encrypt down. -func (s *AcmeSuite) TestNoValidLetsEncryptServer(c *check.C) { - file := s.adaptFile(c, "fixtures/acme/acme_base.toml", templateModel{ +func (s *AcmeSuite) TestNoValidLetsEncryptServer() { + file := s.adaptFile("fixtures/acme/acme_base.toml", templateModel{ Acme: map[string]static.CertificateResolver{ "default": {ACME: &acme.Configuration{ CAServer: "http://wrongurl:4001/directory", @@ -427,21 +438,16 @@ func (s *AcmeSuite) TestNoValidLetsEncryptServer(c *check.C) { }}, }, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // Expected traefik works - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) } // Doing an HTTPS request and test the response certificate. -func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) { +func (s *AcmeSuite) retrieveAcmeCertificate(testCase acmeTestCase) { if len(testCase.template.PortHTTP) == 0 { testCase.template.PortHTTP = ":5002" } @@ -456,14 +462,10 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) { } } - file := s.adaptFile(c, testCase.traefikConfFilePath, testCase.template) - defer os.Remove(file) + file := s.adaptFile(testCase.traefikConfFilePath, testCase.template) + + s.traefikCmd(withConfigFile(file)) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) // A real file is needed to have the right mode on acme.json file defer os.Remove("/tmp/acme.json") @@ -477,11 +479,11 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) { } // wait for traefik (generating acme account take some seconds) - err = try.Do(60*time.Second, func() error { + err := try.Do(60*time.Second, func() error { _, errGet := client.Get("https://127.0.0.1:5001") return errGet }) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) for _, sub := range testCase.subCases { client = &http.Client{ @@ -503,7 +505,7 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) { var resp *http.Response // Retry to send a Request which uses the LE generated certificate - err = try.Do(60*time.Second, func() error { + err := try.Do(60*time.Second, func() error { resp, err = client.Do(req) if err != nil { return err @@ -517,10 +519,10 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) { return nil }) - c.Assert(err, checker.IsNil) - c.Assert(resp.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, resp.StatusCode) // Check Domain into response certificate - c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, checker.Equals, sub.expectedCommonName) - c.Assert(resp.TLS.PeerCertificates[0].PublicKeyAlgorithm, checker.Equals, sub.expectedAlgorithm) + assert.Equal(s.T(), sub.expectedCommonName, resp.TLS.PeerCertificates[0].Subject.CommonName) + assert.Equal(s.T(), sub.expectedAlgorithm, resp.TLS.PeerCertificates[0].PublicKeyAlgorithm) } } diff --git a/integration/conf_throttling_test.go b/integration/conf_throttling_test.go index 3a7b89baa..297bb56fd 100644 --- a/integration/conf_throttling_test.go +++ b/integration/conf_throttling_test.go @@ -8,36 +8,38 @@ import ( "net/http" "regexp" "strconv" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/config/dynamic" - checker "github.com/vdemeester/shakers" ) type ThrottlingSuite struct{ BaseSuite } -func (s *ThrottlingSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "rest") - s.composeUp(c) +func TestThrottlingSuite(t *testing.T) { + suite.Run(t, new(ThrottlingSuite)) } -func (s *ThrottlingSuite) TestThrottleConfReload(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/throttling/simple.toml")) +func (s *ThrottlingSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + s.createComposeProject("rest") + s.composeUp() +} - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *ThrottlingSuite) TestThrottleConfReload() { + s.traefikCmd(withConfigFile("fixtures/throttling/simple.toml")) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal")) + require.NoError(s.T(), err) // Expected a 404 as we did not configure anything. err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) config := &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ @@ -47,7 +49,7 @@ func (s *ThrottlingSuite) TestThrottleConfReload(c *check.C) { LoadBalancer: &dynamic.ServersLoadBalancer{ Servers: []dynamic.Server{ { - URL: "http://" + s.getComposeServiceIP(c, "whoami1") + ":80", + URL: "http://" + s.getComposeServiceIP("whoami1") + ":80", }, }, }, @@ -68,28 +70,28 @@ func (s *ThrottlingSuite) TestThrottleConfReload(c *check.C) { for i := 0; i < confChanges; i++ { config.HTTP.Routers[fmt.Sprintf("routerHTTP%d", i)] = router data, err := json.Marshal(config) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(data)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) response, err := http.DefaultClient.Do(request) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) time.Sleep(200 * time.Millisecond) } reloadsRegexp := regexp.MustCompile(`traefik_config_reloads_total (\d*)\n`) resp, err := http.Get("http://127.0.0.1:8080/metrics") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) defer resp.Body.Close() body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) fields := reloadsRegexp.FindStringSubmatch(string(body)) - c.Assert(len(fields), checker.Equals, 2) + assert.Len(s.T(), fields, 2) reloads, err := strconv.Atoi(fields[1]) if err != nil { @@ -101,5 +103,5 @@ func (s *ThrottlingSuite) TestThrottleConfReload(c *check.C) { // Therefore the throttling (set at 400ms for this test) should only let // (2s / 400 ms =) 5 config reloads happen in theory. // In addition, we have to take into account the extra config reload from the internal provider (5 + 1). - c.Assert(reloads, checker.LessOrEqualThan, 6) + assert.LessOrEqual(s.T(), reloads, 6) } diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index bf0db5da1..95f0ab713 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -4,13 +4,14 @@ import ( "fmt" "net" "net/http" - "os" + "testing" "time" - "github.com/go-check/check" "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type ConsulCatalogSuite struct { @@ -20,26 +21,36 @@ type ConsulCatalogSuite struct { consulURL string } -func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "consul_catalog") - s.composeUp(c) +func TestConsulCatalogSuite(t *testing.T) { + suite.Run(t, new(ConsulCatalogSuite)) +} - s.consulURL = "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "consul"), "8500") +func (s *ConsulCatalogSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("consul_catalog") + s.composeUp() + + s.consulURL = "http://" + net.JoinHostPort(s.getComposeServiceIP("consul"), "8500") var err error s.consulClient, err = api.NewClient(&api.Config{ Address: s.consulURL, }) - c.Check(err, check.IsNil) + require.NoError(s.T(), err) // Wait for consul to elect itself leader err = s.waitToElectConsulLeader() - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) s.consulAgentClient, err = api.NewClient(&api.Config{ - Address: "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "consul-agent"), "8500"), + Address: "http://" + net.JoinHostPort(s.getComposeServiceIP("consul-agent"), "8500"), }) - c.Check(err, check.IsNil) + require.NoError(s.T(), err) +} + +func (s *ConsulCatalogSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() } func (s *ConsulCatalogSuite) waitToElectConsulLeader() error { @@ -83,36 +94,36 @@ func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error { return client.Agent().ServiceDeregister(id) } -func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *check.C) { +func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings() { reg1 := &api.AgentServiceRegistration{ ID: "whoami1", Name: "whoami", Tags: []string{"traefik.enable=true"}, Port: 80, - Address: s.getComposeServiceIP(c, "whoami1"), + Address: s.getComposeServiceIP("whoami1"), } err := s.registerService(reg1, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) reg2 := &api.AgentServiceRegistration{ ID: "whoami2", Name: "whoami", Tags: []string{"traefik.enable=true"}, Port: 80, - Address: s.getComposeServiceIP(c, "whoami2"), + Address: s.getComposeServiceIP("whoami2"), } err = s.registerService(reg2, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) reg3 := &api.AgentServiceRegistration{ ID: "whoami3", Name: "whoami", Tags: []string{"traefik.enable=true"}, Port: 80, - Address: s.getComposeServiceIP(c, "whoami3"), + Address: s.getComposeServiceIP("whoami3"), } err = s.registerService(reg3, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tempObjects := struct { ConsulAddress string @@ -120,23 +131,18 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c ConsulAddress: s.consulURL, } - file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/default_not_exposed.toml", tempObjects) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "whoami" err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.StatusCodeIs(200), @@ -145,18 +151,18 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c fmt.Sprintf(`"http://%s:80":"UP"`, reg2.Address), fmt.Sprintf(`"http://%s:80":"UP"`, reg3.Address), )) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami2", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami3", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestByLabels(c *check.C) { - containerIP := s.getComposeServiceIP(c, "whoami1") +func (s *ConsulCatalogSuite) TestByLabels() { + containerIP := s.getComposeServiceIP("whoami1") reg := &api.AgentServiceRegistration{ ID: "whoami1", @@ -171,7 +177,7 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) { Address: containerIP, } err := s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tempObjects := struct { ConsulAddress string @@ -179,23 +185,18 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) { ConsulAddress: s.consulURL, } - file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/default_not_exposed.toml", tempObjects) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { +func (s *ConsulCatalogSuite) TestSimpleConfiguration() { tempObjects := struct { ConsulAddress string DefaultRule string @@ -204,37 +205,32 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", } - file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects) reg := &api.AgentServiceRegistration{ ID: "whoami1", Name: "whoami", Tags: []string{"traefik.enable=true"}, Port: 80, - Address: s.getComposeServiceIP(c, "whoami1"), + Address: s.getComposeServiceIP("whoami1"), } err := s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "whoami.consul.localhost" err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch(c *check.C) { +func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch() { tempObjects := struct { ConsulAddress string DefaultRule string @@ -243,39 +239,34 @@ func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", } - file := s.adaptFile(c, "fixtures/consul_catalog/simple_watch.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/simple_watch.toml", tempObjects) reg := &api.AgentServiceRegistration{ ID: "whoami1", Name: "whoami", Tags: []string{"traefik.enable=true"}, Port: 80, - Address: s.getComposeServiceIP(c, "whoami1"), + Address: s.getComposeServiceIP("whoami1"), } err := s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "whoami.consul.localhost" err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - whoamiIP := s.getComposeServiceIP(c, "whoami1") + whoamiIP := s.getComposeServiceIP("whoami1") reg.Check = &api.AgentServiceCheck{ CheckID: "some-ok-check", TCP: whoamiIP + ":80", @@ -285,10 +276,10 @@ func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch(c *check.C) { } err = s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1")) - c.Assert(err, checker.IsNil) + err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1")) + require.NoError(s.T(), err) reg.Check = &api.AgentServiceCheck{ CheckID: "some-failing-check", @@ -299,16 +290,16 @@ func (s *ConsulCatalogSuite) TestSimpleConfigurationWithWatch(c *check.C) { } err = s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) { +func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP() { tempObjects := struct { ConsulAddress string DefaultRule string @@ -317,8 +308,7 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", } - file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects) reg := &api.AgentServiceRegistration{ ID: "whoami1", @@ -328,25 +318,21 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) { Address: "", } err := s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/http/services", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("whoami@consulcatalog", "\"http://127.0.0.1:80\": \"UP\"")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) { +func (s *ConsulCatalogSuite) TestDefaultConsulService() { tempObjects := struct { ConsulAddress string DefaultRule string @@ -355,37 +341,32 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", } - file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects) reg := &api.AgentServiceRegistration{ ID: "whoami1", Name: "whoami", Port: 80, - Address: s.getComposeServiceIP(c, "whoami1"), + Address: s.getComposeServiceIP("whoami1"), } err := s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "whoami.consul.localhost" err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) { +func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels() { tempObjects := struct { ConsulAddress string DefaultRule string @@ -394,8 +375,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", } - file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects) // Start a container with some tags reg := &api.AgentServiceRegistration{ @@ -407,32 +387,28 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) { "traefik.tcp.Services.Super.Loadbalancer.server.port=8080", }, Port: 8080, - Address: s.getComposeServiceIP(c, "whoamitcp"), + Address: s.getComposeServiceIP("whoamitcp"), } err := s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`my.super.host`)")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) who, err := guessWho("127.0.0.1:8000", "my.super.host", true) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - c.Assert(who, checker.Contains, "whoamitcp") + assert.Contains(s.T(), who, "whoamitcp") err = s.deregisterService("whoamitcp", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) { +func (s *ConsulCatalogSuite) TestConsulServiceWithLabels() { tempObjects := struct { ConsulAddress string DefaultRule string @@ -441,8 +417,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", } - file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects) // Start a container with some tags reg1 := &api.AgentServiceRegistration{ @@ -452,11 +427,11 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) { "traefik.http.Routers.Super.Rule=Host(`my.super.host`)", }, Port: 80, - Address: s.getComposeServiceIP(c, "whoami1"), + Address: s.getComposeServiceIP("whoami1"), } err := s.registerService(reg1, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Start another container by replacing a '.' by a '-' reg2 := &api.AgentServiceRegistration{ @@ -466,40 +441,36 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) { "traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)", }, Port: 80, - Address: s.getComposeServiceIP(c, "whoami2"), + Address: s.getComposeServiceIP("whoami2"), } err = s.registerService(reg2, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "my-super.host" err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "my.super.host" err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami2", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C) { +func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent() { tempObjects := struct { ConsulAddress string DefaultRule string @@ -508,8 +479,7 @@ func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C) DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", } - file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/default_not_exposed.toml", tempObjects) // Start a container with some tags tags := []string{ @@ -523,50 +493,46 @@ func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C) Name: "whoami", Tags: tags, Port: 80, - Address: s.getComposeServiceIP(c, "whoami1"), + Address: s.getComposeServiceIP("whoami1"), } err := s.registerService(reg1, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) reg2 := &api.AgentServiceRegistration{ ID: "whoami", Name: "whoami", Tags: tags, Port: 80, - Address: s.getComposeServiceIP(c, "whoami2"), + Address: s.getComposeServiceIP("whoami2"), } err = s.registerService(reg2, true) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "my.super.host" err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), - try.BodyContainsOr(s.getComposeServiceIP(c, "whoami1"), s.getComposeServiceIP(c, "whoami2"))) - c.Assert(err, checker.IsNil) + try.BodyContainsOr(s.getComposeServiceIP("whoami1"), s.getComposeServiceIP("whoami2"))) + require.NoError(s.T(), err) err = s.deregisterService("whoami", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami", true) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) { +func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels() { tempObjects := struct { ConsulAddress string DefaultRule string @@ -575,8 +541,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", } - file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects) // Start a container with some tags reg := &api.AgentServiceRegistration{ @@ -586,32 +551,28 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) { "traefik.random.value=my.super.host", }, Port: 80, - Address: s.getComposeServiceIP(c, "whoami1"), + Address: s.getComposeServiceIP("whoami1"), } err := s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "my.super.host" // TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) // TODO validate : run on 80 // Expected a 404 as we did not configure anything err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) { - whoamiIP := s.getComposeServiceIP(c, "whoami1") +func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck() { + whoamiIP := s.getComposeServiceIP("whoami1") tags := []string{ "traefik.enable=true", "traefik.http.routers.router1.rule=Path(`/whoami`)", @@ -635,7 +596,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) { } err := s.registerService(reg1, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tempObjects := struct { ConsulAddress string @@ -643,22 +604,17 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) { ConsulAddress: s.consulURL, } - file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/simple.toml", tempObjects) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - whoami2IP := s.getComposeServiceIP(c, "whoami2") + whoami2IP := s.getComposeServiceIP("whoami2") reg2 := &api.AgentServiceRegistration{ ID: "whoami2", Name: "whoami", @@ -675,26 +631,26 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithHealthCheck(c *check.C) { } err = s.registerService(reg2, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "whoami" // TODO Need to wait for up to 10 seconds (for consul discovery or traefik to boot up ?) err = try.Request(req, 10*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami2", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestConsulConnect(c *check.C) { +func (s *ConsulCatalogSuite) TestConsulConnect() { // Wait for consul to fully initialize connect CA err := s.waitForConnectCA() - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - connectIP := s.getComposeServiceIP(c, "connect") + connectIP := s.getComposeServiceIP("connect") reg := &api.AgentServiceRegistration{ ID: "uuid-api1", Name: "uuid-api", @@ -712,9 +668,9 @@ func (s *ConsulCatalogSuite) TestConsulConnect(c *check.C) { Address: connectIP, } err = s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - whoamiIP := s.getComposeServiceIP(c, "whoami1") + whoamiIP := s.getComposeServiceIP("whoami1") regWhoami := &api.AgentServiceRegistration{ ID: "whoami1", Name: "whoami", @@ -727,40 +683,35 @@ func (s *ConsulCatalogSuite) TestConsulConnect(c *check.C) { Address: whoamiIP, } err = s.registerService(regWhoami, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tempObjects := struct { ConsulAddress string }{ ConsulAddress: s.consulURL, } - file := s.adaptFile(c, "fixtures/consul_catalog/connect.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/connect.toml", tempObjects) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) err = try.GetRequest("http://127.0.0.1:8000/", 10*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("uuid-api1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault(c *check.C) { +func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault() { // Wait for consul to fully initialize connect CA err := s.waitForConnectCA() - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - connectIP := s.getComposeServiceIP(c, "connect") + connectIP := s.getComposeServiceIP("connect") reg := &api.AgentServiceRegistration{ ID: "uuid-api1", Name: "uuid-api", @@ -777,9 +728,9 @@ func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault(c *check.C) { Address: connectIP, } err = s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - whoamiIP := s.getComposeServiceIP(c, "whoami1") + whoamiIP := s.getComposeServiceIP("whoami1") regWhoami := &api.AgentServiceRegistration{ ID: "whoami1", Name: "whoami1", @@ -792,9 +743,9 @@ func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault(c *check.C) { Address: whoamiIP, } err = s.registerService(regWhoami, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - whoami2IP := s.getComposeServiceIP(c, "whoami2") + whoami2IP := s.getComposeServiceIP("whoami2") regWhoami2 := &api.AgentServiceRegistration{ ID: "whoami2", Name: "whoami2", @@ -808,45 +759,40 @@ func (s *ConsulCatalogSuite) TestConsulConnect_ByDefault(c *check.C) { Address: whoami2IP, } err = s.registerService(regWhoami2, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tempObjects := struct { ConsulAddress string }{ ConsulAddress: s.consulURL, } - file := s.adaptFile(c, "fixtures/consul_catalog/connect_by_default.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/connect_by_default.toml", tempObjects) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) err = try.GetRequest("http://127.0.0.1:8000/", 10*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami2", 10*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("uuid-api1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami2", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulCatalogSuite) TestConsulConnect_NotAware(c *check.C) { +func (s *ConsulCatalogSuite) TestConsulConnect_NotAware() { // Wait for consul to fully initialize connect CA err := s.waitForConnectCA() - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - connectIP := s.getComposeServiceIP(c, "connect") + connectIP := s.getComposeServiceIP("connect") reg := &api.AgentServiceRegistration{ ID: "uuid-api1", Name: "uuid-api", @@ -864,9 +810,9 @@ func (s *ConsulCatalogSuite) TestConsulConnect_NotAware(c *check.C) { Address: connectIP, } err = s.registerService(reg, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - whoamiIP := s.getComposeServiceIP(c, "whoami1") + whoamiIP := s.getComposeServiceIP("whoami1") regWhoami := &api.AgentServiceRegistration{ ID: "whoami1", Name: "whoami", @@ -879,30 +825,25 @@ func (s *ConsulCatalogSuite) TestConsulConnect_NotAware(c *check.C) { Address: whoamiIP, } err = s.registerService(regWhoami, false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tempObjects := struct { ConsulAddress string }{ ConsulAddress: s.consulURL, } - file := s.adaptFile(c, "fixtures/consul_catalog/connect_not_aware.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/consul_catalog/connect_not_aware.toml", tempObjects) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) err = try.GetRequest("http://127.0.0.1:8000/", 10*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("uuid-api1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = s.deregisterService("whoami1", false) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } diff --git a/integration/consul_test.go b/integration/consul_test.go index 441d56db7..076fd4560 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -4,21 +4,23 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "net" "net/http" "os" "path/filepath" + "testing" "time" - "github.com/go-check/check" "github.com/kvtools/consul" "github.com/kvtools/valkeyrie" "github.com/kvtools/valkeyrie/store" "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/api" - checker "github.com/vdemeester/shakers" ) // Consul test suites. @@ -28,11 +30,16 @@ type ConsulSuite struct { consulURL string } -func (s *ConsulSuite) setupStore(c *check.C) { - s.createComposeProject(c, "consul") - s.composeUp(c) +func TestConsulSuite(t *testing.T) { + suite.Run(t, new(ConsulSuite)) +} - consulAddr := net.JoinHostPort(s.getComposeServiceIP(c, "consul"), "8500") +func (s *ConsulSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + s.createComposeProject("consul") + s.composeUp() + + consulAddr := net.JoinHostPort(s.getComposeServiceIP("consul"), "8500") s.consulURL = fmt.Sprintf("http://%s", consulAddr) kv, err := valkeyrie.NewStore( @@ -43,21 +50,27 @@ func (s *ConsulSuite) setupStore(c *check.C) { ConnectionTimeout: 10 * time.Second, }, ) - if err != nil { - c.Fatal("Cannot create store consul") - } + require.NoError(s.T(), err, "Cannot create store consul") s.kvClient = kv // wait for consul err = try.Do(60*time.Second, try.KVExists(kv, "test")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { - s.setupStore(c) +func (s *ConsulSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} - file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulAddress string }{s.consulURL}) - defer os.Remove(file) +func (s *ConsulSuite) TearDownTest() { + err := s.kvClient.DeleteTree(context.Background(), "traefik") + if err != nil && !errors.Is(err, store.ErrKeyNotFound) { + require.ErrorIs(s.T(), err, store.ErrKeyNotFound) + } +} + +func (s *ConsulSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/consul/simple.toml", struct{ ConsulAddress string }{s.consulURL}) data := map[string]string{ "traefik/http/routers/Router0/entryPoints/0": "web", @@ -106,39 +119,35 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { for k, v := range data { err := s.kvClient.Put(context.Background(), k, []byte(v), nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains(`"striper@consul":`, `"compressor@consul":`, `"srvcA@consul":`, `"srvcB@consul":`), ) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) var obtained api.RunTimeRepresentation err = json.NewDecoder(resp.Body).Decode(&obtained) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) got, err := json.MarshalIndent(obtained, "", " ") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) expectedJSON := filepath.FromSlash("testdata/rawdata-consul.json") if *updateExpected { err = os.WriteFile(expectedJSON, got, 0o666) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } expected, err := os.ReadFile(expectedJSON) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if !bytes.Equal(expected, got) { diff := difflib.UnifiedDiff{ @@ -150,7 +159,65 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { } text, err := difflib.GetUnifiedDiffString(diff) - c.Assert(err, checker.IsNil) - c.Error(text) + require.NoError(s.T(), err, text) } } + +func (s *ConsulSuite) assertWhoami(host string, expectedStatusCode int) { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) + require.NoError(s.T(), err) + req.Host = host + + resp, err := try.ResponseUntilStatusCode(req, 15*time.Second, expectedStatusCode) + require.NoError(s.T(), err) + resp.Body.Close() +} + +func (s *ConsulSuite) TestDeleteRootKey() { + // This test case reproduce the issue: https://github.com/traefik/traefik/issues/8092 + + file := s.adaptFile("fixtures/consul/simple.toml", struct{ ConsulAddress string }{s.consulURL}) + + ctx := context.Background() + svcaddr := net.JoinHostPort(s.getComposeServiceIP("whoami"), "80") + + data := map[string]string{ + "traefik/http/routers/Router0/entryPoints/0": "web", + "traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)", + "traefik/http/routers/Router0/service": "simplesvc0", + + "traefik/http/routers/Router1/entryPoints/0": "web", + "traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)", + "traefik/http/routers/Router1/service": "simplesvc1", + + "traefik/http/services/simplesvc0/loadBalancer/servers/0/url": "http://" + svcaddr, + "traefik/http/services/simplesvc1/loadBalancer/servers/0/url": "http://" + svcaddr, + } + + for k, v := range data { + err := s.kvClient.Put(ctx, k, []byte(v), nil) + require.NoError(s.T(), err) + } + + s.traefikCmd(withConfigFile(file)) + + // wait for traefik + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, + try.BodyContains(`"Router0@consul":`, `"Router1@consul":`, `"simplesvc0@consul":`, `"simplesvc1@consul":`), + ) + require.NoError(s.T(), err) + s.assertWhoami("kv1.localhost", http.StatusOK) + s.assertWhoami("kv2.localhost", http.StatusOK) + + // delete router1 + err = s.kvClient.DeleteTree(ctx, "traefik/http/routers/Router1") + require.NoError(s.T(), err) + s.assertWhoami("kv1.localhost", http.StatusOK) + s.assertWhoami("kv2.localhost", http.StatusNotFound) + + // delete simple services and router0 + err = s.kvClient.DeleteTree(ctx, "traefik") + require.NoError(s.T(), err) + s.assertWhoami("kv1.localhost", http.StatusNotFound) + s.assertWhoami("kv2.localhost", http.StatusNotFound) +} diff --git a/integration/docker_compose_test.go b/integration/docker_compose_test.go index 39347dca5..39f4fbf92 100644 --- a/integration/docker_compose_test.go +++ b/integration/docker_compose_test.go @@ -3,15 +3,16 @@ package integration import ( "encoding/json" "net/http" - "os" "strings" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/api" "github.com/traefik/traefik/v3/pkg/testhelpers" - checker "github.com/vdemeester/shakers" ) // Docker tests suite. @@ -19,12 +20,21 @@ type DockerComposeSuite struct { BaseSuite } -func (s *DockerComposeSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "minimal") - s.composeUp(c) +func TestDockerComposeSuite(t *testing.T) { + suite.Run(t, new(DockerComposeSuite)) } -func (s *DockerComposeSuite) TestComposeScale(c *check.C) { +func (s *DockerComposeSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + s.createComposeProject("minimal") + s.composeUp() +} + +func (s *DockerComposeSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *DockerComposeSuite) TestComposeScale() { tempObjects := struct { DockerHost string DefaultRule string @@ -32,41 +42,36 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) { DockerHost: s.getDockerHost(), DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } - file := s.adaptFile(c, "fixtures/docker/minimal.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/docker/minimal.toml", tempObjects) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req := testhelpers.MustNewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) req.Host = "my.super.host" - _, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) - c.Assert(err, checker.IsNil) + _, err := try.ResponseUntilStatusCode(req, 5*time.Second, http.StatusOK) + require.NoError(s.T(), err) resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) defer resp.Body.Close() var rtconf api.RunTimeRepresentation err = json.NewDecoder(resp.Body).Decode(&rtconf) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // check that we have only three routers (the one from this test + 2 unrelated internal ones) - c.Assert(rtconf.Routers, checker.HasLen, 3) + assert.Len(s.T(), rtconf.Routers, 3) // check that we have only one service (not counting the internal ones) with n servers services := rtconf.Services - c.Assert(services, checker.HasLen, 4) + assert.Len(s.T(), services, 4) for name, service := range services { if strings.HasSuffix(name, "@internal") { continue } - c.Assert(name, checker.Equals, "whoami1-"+s.composeProject.Name+"@docker") - c.Assert(service.LoadBalancer.Servers, checker.HasLen, 2) + assert.Equal(s.T(), "service-mini@docker", name) + assert.Len(s.T(), service.LoadBalancer.Servers, 2) // We could break here, but we don't just to keep us honest. } } diff --git a/integration/docker_test.go b/integration/docker_test.go index dad1ef809..1a767ff6f 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -2,15 +2,15 @@ package integration import ( "encoding/json" - "fmt" "io" "net/http" - "os" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) // Docker tests suite. @@ -18,15 +18,24 @@ type DockerSuite struct { BaseSuite } -func (s *DockerSuite) SetUpTest(c *check.C) { - s.createComposeProject(c, "docker") +func TestDockerSuite(t *testing.T) { + suite.Run(t, new(DockerSuite)) } -func (s *DockerSuite) TearDownTest(c *check.C) { - s.composeDown(c) +func (s *DockerSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + s.createComposeProject("docker") } -func (s *DockerSuite) TestSimpleConfiguration(c *check.C) { +func (s *DockerSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *DockerSuite) TearDownTest() { + s.composeStop("simple", "withtcplabels", "withlabels1", "withlabels2", "withonelabelmissing", "powpow") +} + +func (s *DockerSuite) TestSimpleConfiguration() { tempObjects := struct { DockerHost string DefaultRule string @@ -35,24 +44,18 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } - file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/docker/simple.toml", tempObjects) - s.composeUp(c) + s.composeUp() - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // Expected a 404 as we did not configure anything - err = try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) } -func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) { +func (s *DockerSuite) TestDefaultDockerContainers() { tempObjects := struct { DockerHost string DefaultRule string @@ -61,37 +64,30 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } - file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/docker/simple.toml", tempObjects) - s.composeUp(c, "simple") + s.composeUp("simple") // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) - c.Assert(err, checker.IsNil) - req.Host = fmt.Sprintf("simple-%s.docker.localhost", s.composeProject.Name) + require.NoError(s.T(), err) + req.Host = "simple.docker.localhost" - // TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) - resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) - c.Assert(err, checker.IsNil) + resp, err := try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK) + require.NoError(s.T(), err) body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) var version map[string]interface{} - c.Assert(json.Unmarshal(body, &version), checker.IsNil) - c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") + assert.NoError(s.T(), json.Unmarshal(body, &version)) + assert.Equal(s.T(), "swarm/1.0.0", version["Version"]) } -func (s *DockerSuite) TestDockerContainersWithTCPLabels(c *check.C) { +func (s *DockerSuite) TestDockerContainersWithTCPLabels() { tempObjects := struct { DockerHost string DefaultRule string @@ -100,29 +96,23 @@ func (s *DockerSuite) TestDockerContainersWithTCPLabels(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } - file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/docker/simple.toml", tempObjects) - s.composeUp(c, "withtcplabels") + s.composeUp("withtcplabels") // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`my.super.host`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`my.super.host`)")) + require.NoError(s.T(), err) who, err := guessWho("127.0.0.1:8000", "my.super.host", true) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - c.Assert(who, checker.Contains, "my.super.host") + assert.Contains(s.T(), who, "my.super.host") } -func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { +func (s *DockerSuite) TestDockerContainersWithLabels() { tempObjects := struct { DockerHost string DefaultRule string @@ -131,44 +121,37 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } - file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/docker/simple.toml", tempObjects) - s.composeUp(c, "withlabels1", "withlabels2") + s.composeUp("withlabels1", "withlabels2") // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "my-super.host" - // TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) - _, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) - c.Assert(err, checker.IsNil) + _, err = try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK) + require.NoError(s.T(), err) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "my.super.host" - // TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) - resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) - c.Assert(err, checker.IsNil) + resp, err := try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK) + require.NoError(s.T(), err) body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) var version map[string]interface{} - c.Assert(json.Unmarshal(body, &version), checker.IsNil) - c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") + assert.NoError(s.T(), json.Unmarshal(body, &version)) + assert.Equal(s.T(), "swarm/1.0.0", version["Version"]) } -func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { +func (s *DockerSuite) TestDockerContainersWithOneMissingLabels() { tempObjects := struct { DockerHost string DefaultRule string @@ -177,31 +160,23 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } - file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/docker/simple.toml", tempObjects) - s.composeUp(c, "withonelabelmissing") + s.composeUp("withonelabelmissing") // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "my.super.host" - // TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) - // TODO validate : run on 80 // Expected a 404 as we did not configure anything - err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err = try.Request(req, 3*time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) } -func (s *DockerSuite) TestRestartDockerContainers(c *check.C) { +func (s *DockerSuite) TestRestartDockerContainers() { tempObjects := struct { DockerHost string DefaultRule string @@ -210,46 +185,40 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) { DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)", } - file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects) - defer os.Remove(file) + file := s.adaptFile("fixtures/docker/simple.toml", tempObjects) - s.composeUp(c, "powpow") + s.composeUp("powpow") // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "my.super.host" // TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) var version map[string]interface{} - c.Assert(json.Unmarshal(body, &version), checker.IsNil) - c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") + assert.NoError(s.T(), json.Unmarshal(body, &version)) + assert.Equal(s.T(), "swarm/1.0.0", version["Version"]) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - s.composeStop(c, "powpow") + s.composeStop("powpow") time.Sleep(5 * time.Second) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("powpow")) - c.Assert(err, checker.NotNil) + assert.Error(s.T(), err) - s.composeUp(c, "powpow") + s.composeUp("powpow") err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } diff --git a/integration/error_pages_test.go b/integration/error_pages_test.go index 8a6deba89..8fd77ac58 100644 --- a/integration/error_pages_test.go +++ b/integration/error_pages_test.go @@ -3,12 +3,12 @@ package integration import ( "net/http" "net/http/httptest" - "os" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) // ErrorPagesSuite test suites. @@ -18,83 +18,78 @@ type ErrorPagesSuite struct { BackendIP string } -func (s *ErrorPagesSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "error_pages") - s.composeUp(c) - - s.ErrorPageIP = s.getComposeServiceIP(c, "nginx2") - s.BackendIP = s.getComposeServiceIP(c, "nginx1") +func TestErrorPagesSuite(t *testing.T) { + suite.Run(t, new(ErrorPagesSuite)) } -func (s *ErrorPagesSuite) TestSimpleConfiguration(c *check.C) { - file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct { +func (s *ErrorPagesSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("error_pages") + s.composeUp() + + s.ErrorPageIP = s.getComposeServiceIP("nginx2") + s.BackendIP = s.getComposeServiceIP("nginx1") +} + +func (s *ErrorPagesSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *ErrorPagesSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/error_pages/simple.toml", struct { Server1 string Server2 string }{"http://" + s.BackendIP + ":80", s.ErrorPageIP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendReq.Host = "test.local" err = try.Request(frontendReq, 2*time.Second, try.BodyContains("nginx")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ErrorPagesSuite) TestErrorPage(c *check.C) { +func (s *ErrorPagesSuite) TestErrorPage() { // error.toml contains a mis-configuration of the backend host - file := s.adaptFile(c, "fixtures/error_pages/error.toml", struct { + file := s.adaptFile("fixtures/error_pages/error.toml", struct { Server1 string Server2 string }{s.BackendIP, s.ErrorPageIP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendReq.Host = "test.local" err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred.")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ErrorPagesSuite) TestErrorPageFlush(c *check.C) { +func (s *ErrorPagesSuite) TestErrorPageFlush() { srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rw.Header().Add("Transfer-Encoding", "chunked") rw.WriteHeader(http.StatusInternalServerError) _, _ = rw.Write([]byte("KO")) })) - file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct { + file := s.adaptFile("fixtures/error_pages/simple.toml", struct { Server1 string Server2 string }{srv.URL, s.ErrorPageIP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendReq.Host = "test.local" err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred."), try.HasHeaderValue("Content-Type", "text/html", true), ) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } diff --git a/integration/etcd_test.go b/integration/etcd_test.go index a9e9bb532..01132ba88 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -8,16 +8,17 @@ import ( "net/http" "os" "path/filepath" + "testing" "time" - "github.com/go-check/check" "github.com/kvtools/etcdv3" "github.com/kvtools/valkeyrie" "github.com/kvtools/valkeyrie/store" "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/api" - checker "github.com/vdemeester/shakers" ) // etcd test suites. @@ -27,12 +28,18 @@ type EtcdSuite struct { etcdAddr string } -func (s *EtcdSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "etcd") - s.composeUp(c) +func TestEtcdSuite(t *testing.T) { + suite.Run(t, new(EtcdSuite)) +} + +func (s *EtcdSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("etcd") + s.composeUp() var err error - s.etcdAddr = net.JoinHostPort(s.getComposeServiceIP(c, "etcd"), "2379") + s.etcdAddr = net.JoinHostPort(s.getComposeServiceIP("etcd"), "2379") s.kvClient, err = valkeyrie.NewStore( context.Background(), etcdv3.StoreName, @@ -41,18 +48,19 @@ func (s *EtcdSuite) SetUpSuite(c *check.C) { ConnectionTimeout: 10 * time.Second, }, ) - if err != nil { - c.Fatal("Cannot create store etcd") - } + require.NoError(s.T(), err) // wait for etcd err = try.Do(60*time.Second, try.KVExists(s.kvClient, "test")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) { - file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdAddress string }{s.etcdAddr}) - defer os.Remove(file) +func (s *EtcdSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *EtcdSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/etcd/simple.toml", struct{ EtcdAddress string }{s.etcdAddr}) data := map[string]string{ "traefik/http/routers/Router0/entryPoints/0": "web", @@ -101,39 +109,35 @@ func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) { for k, v := range data { err := s.kvClient.Put(context.Background(), k, []byte(v), nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains(`"striper@etcd":`, `"compressor@etcd":`, `"srvcA@etcd":`, `"srvcB@etcd":`), ) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) var obtained api.RunTimeRepresentation err = json.NewDecoder(resp.Body).Decode(&obtained) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) got, err := json.MarshalIndent(obtained, "", " ") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) expectedJSON := filepath.FromSlash("testdata/rawdata-etcd.json") if *updateExpected { err = os.WriteFile(expectedJSON, got, 0o666) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } expected, err := os.ReadFile(expectedJSON) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if !bytes.Equal(expected, got) { diff := difflib.UnifiedDiff{ @@ -145,7 +149,6 @@ func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) { } text, err := difflib.GetUnifiedDiffString(diff) - c.Assert(err, checker.IsNil) - c.Error(text) + require.NoError(s.T(), err, text) } } diff --git a/integration/file_test.go b/integration/file_test.go index 38c30db35..7e909e109 100644 --- a/integration/file_test.go +++ b/integration/file_test.go @@ -2,61 +2,58 @@ package integration import ( "net/http" - "os" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) // File tests suite. type FileSuite struct{ BaseSuite } -func (s *FileSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "file") - s.composeUp(c) +func TestFileSuite(t *testing.T) { + suite.Run(t, new(FileSuite)) } -func (s *FileSuite) TestSimpleConfiguration(c *check.C) { - file := s.adaptFile(c, "fixtures/file/simple.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *FileSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("file") + s.composeUp() +} + +func (s *FileSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *FileSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/file/simple.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // Expected a 404 as we did not configure anything - err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) } // #56 regression test, make sure it does not fail? -func (s *FileSuite) TestSimpleConfigurationNoPanic(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/file/56-simple-panic.toml")) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *FileSuite) TestSimpleConfigurationNoPanic() { + s.traefikCmd(withConfigFile("fixtures/file/56-simple-panic.toml")) // Expected a 404 as we did not configure anything - err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) } -func (s *FileSuite) TestDirectoryConfiguration(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/file/directory.toml")) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *FileSuite) TestDirectoryConfiguration() { + s.traefikCmd(withConfigFile("fixtures/file/directory.toml")) // Expected a 404 as we did not configure anything at /test - err = try.GetRequest("http://127.0.0.1:8000/test", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/test", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) // Expected a 502 as there is no backend server err = try.GetRequest("http://127.0.0.1:8000/test2", 1000*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } diff --git a/integration/fixtures/access_log_config.toml b/integration/fixtures/access_log_config.toml index 68e653788..8d954450e 100644 --- a/integration/fixtures/access_log_config.toml +++ b/integration/fixtures/access_log_config.toml @@ -20,6 +20,8 @@ address = ":8007" [entryPoints.digestAuth] address = ":8008" + [entryPoints.preflight] + address = ":8009" [api] insecure = true diff --git a/integration/fixtures/access_log_json_config.toml b/integration/fixtures/access_log_json_config.toml new file mode 100644 index 000000000..1d4a8c832 --- /dev/null +++ b/integration/fixtures/access_log_json_config.toml @@ -0,0 +1,32 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "ERROR" + filePath = "traefik.log" + +[accessLog] + format = "json" + filePath = "access.log" + +[entryPoints] + [entryPoints.web] + address = ":8000" + [entryPoints.frontendRedirect] + address = ":8005" + [entryPoints.httpFrontendAuth] + address = ":8006" + [entryPoints.httpRateLimit] + address = ":8007" + [entryPoints.digestAuth] + address = ":8008" + +[api] + insecure = true + +[providers] + [providers.docker] + exposedByDefault = false + defaultRule = "Host(`{{ normalize .Name }}.docker.local`)" + watch = true diff --git a/integration/fixtures/headers/remove_reverseproxy_headers.toml b/integration/fixtures/headers/remove_reverseproxy_headers.toml new file mode 100644 index 000000000..48ce504cf --- /dev/null +++ b/integration/fixtures/headers/remove_reverseproxy_headers.toml @@ -0,0 +1,31 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router1] + rule = "Host(`test.localhost`)" + middlewares = ["remove"] + service = "service1" + +[http.middlewares] + [http.middlewares.remove.headers.customRequestHeaders] + X-Forwarded-For = "" + Foo = "" + +[http.services] + [http.services.service1.loadBalancer] + [[http.services.service1.loadBalancer.servers]] + url = "http://127.0.0.1:9000" diff --git a/integration/fixtures/k8s/01-gateway-api-crd.yml b/integration/fixtures/k8s/01-gateway-api-crd.yml index cb10d17d5..f483bcb0a 100644 --- a/integration/fixtures/k8s/01-gateway-api-crd.yml +++ b/integration/fixtures/k8s/01-gateway-api-crd.yml @@ -1,10 +1,10 @@ - ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: gatewayclasses.gateway.networking.k8s.io spec: @@ -21,9 +21,12 @@ spec: scope: Cluster versions: - additionalPrinterColumns: - - jsonPath: .spec.controller + - jsonPath: .spec.controllerName name: Controller type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date @@ -31,7 +34,7 @@ spec: name: Description priority: 1 type: string - name: v1alpha2 + name: v1 schema: openAPIV3Schema: description: "GatewayClass describes a class of Gateways available to the @@ -43,7 +46,7 @@ spec: to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, - implementations MUST add the `gateway-exists-finalizer.gateway.networking.k8s.io` + implementations SHOULD add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." @@ -72,6 +75,9 @@ spec: minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf description: description: Description helps describe a GatewayClass with more details. maxLength: 64 @@ -84,7 +90,7 @@ spec: resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" - status condition will be true. \n Support: Custom" + status condition will be true. \n Support: Implementation-specific" properties: group: description: Group is the group of the referent. @@ -126,13 +132,15 @@ spec: reason: Waiting status: Unknown type: Accepted - description: Status defines the current state of GatewayClass. + description: "Status defines the current state of GatewayClass. \n Implementations + MUST populate status on all GatewayClass resources which specify their + controller name." properties: conditions: default: - lastTransitionTime: "1970-01-01T00:00:00Z" message: Waiting for controller - reason: Waiting + reason: Pending status: Unknown type: Accepted description: "Conditions is the current status from the controller @@ -142,13 +150,12 @@ spec: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition @@ -210,6 +217,269 @@ spec: x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map + supportedFeatures: + description: 'SupportedFeatures is the set of features the GatewayClass + support. It MUST be sorted in ascending alphabetical order. ' + items: + description: SupportedFeature is used to describe distinct features + that are covered by conformance tests. + enum: + - Gateway + - GatewayPort8080 + - GatewayStaticAddresses + - HTTPRoute + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - Mesh + - ReferenceGrant + - TLSRoute + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the + user for creating Gateway resources. \n It is recommended that this resource + be used as a template for Gateways. This means that a Gateway is based on + the state of the GatewayClass at the time it was created and changes to + the GatewayClass or associated parameters are not propagated down to existing + Gateways. This recommendation is intended to limit the blast radius of changes + to GatewayClass or associated parameters. If implementations choose to propagate + GatewayClass changes to existing Gateways, that MUST be clearly documented + by the implementation. \n Whenever one or more Gateways are using a GatewayClass, + implementations SHOULD add the `gateway-exists-finalizer.gateway.networking.k8s.io` + finalizer on the associated GatewayClass. This ensures that a GatewayClass + associated with a Gateway is not deleted while in use. \n GatewayClass is + a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is + managing Gateways of this class. The value of this field MUST be + a domain prefixed path. \n Example: \"example.net/gateway-controller\". + \n This field is not mutable and cannot be empty. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains + the configuration parameters corresponding to the GatewayClass. + This is optional if the controller does not require any additional + configuration. \n ParametersRef can reference a standard Kubernetes + resource, i.e. ConfigMap, or an implementation-specific custom resource. + The resource can be cluster-scoped or namespace-scoped. \n If the + referent cannot be found, the GatewayClass's \"InvalidParameters\" + status condition will be true. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. This + field is required when referring to a Namespace-scoped resource + and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: "Status defines the current state of GatewayClass. \n Implementations + MUST populate status on all GatewayClass resources which specify their + controller name." + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller + for this GatewayClass. \n Controllers should prefer to publish conditions + using values of GatewayClassConditionType for the type of each Condition." + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: 'SupportedFeatures is the set of features the GatewayClass + support. It MUST be sorted in ascending alphabetical order. ' + items: + description: SupportedFeature is used to describe distinct features + that are covered by conformance tests. + enum: + - Gateway + - GatewayPort8080 + - GatewayStaticAddresses + - HTTPRoute + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - Mesh + - ReferenceGrant + - TLSRoute + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set type: object required: - spec @@ -222,15 +492,16 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] - + conditions: null + storedVersions: null --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: gateways.gateway.networking.k8s.io spec: @@ -253,13 +524,13 @@ spec: - jsonPath: .status.addresses[*].value name: Address type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha2 + name: v1 schema: openAPIV3Schema: description: Gateway represents an instance of a service-traffic handling @@ -289,25 +560,36 @@ spec: for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, - or some other address that traffic will be sent to. \n The .listener.hostname - field is used to route traffic that has already arrived at the Gateway - to the correct in-cluster destination. \n If no Addresses are specified, - the implementation MAY schedule the Gateway in an implementation-specific - manner, assigning an appropriate set of Addresses. \n The implementation - MUST bind all Listeners to every GatewayAddress that it assigns - to the Gateway and add a corresponding entry in GatewayStatus.Addresses. - \n Support: Core" + or some other address that traffic will be sent to. \n If no Addresses + are specified, the implementation MAY schedule the Gateway in an + implementation-specific manner, assigning an appropriate set of + Addresses. \n The implementation MUST bind all Listeners to every + GatewayAddress that it assigns to the Gateway and add a corresponding + entry in GatewayStatus.Addresses. \n Support: Extended \n " items: description: GatewayAddress describes an address that can be bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress properties: type: default: IPAddress description: Type of the address. - enum: - - IPAddress - - Hostname - - NamedAddress + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ type: string value: description: "Value of the address. The validity of the values @@ -319,40 +601,154 @@ spec: required: - value type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' maxItems: 16 type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' gatewayClassName: description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. maxLength: 253 minLength: 1 type: string + infrastructure: + description: "Infrastructure defines infrastructure level attributes + about this Gateway instance. \n Support: Core \n " + properties: + annotations: + additionalProperties: + description: AnnotationValue is the value of an annotation in + Gateway API. This is used for validation of maps such as TLS + options. This roughly matches Kubernetes annotation validation, + although the length validation in that case is based on the + entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Annotations that SHOULD be applied to any resources + created in response to this Gateway. \n For implementations + creating other Kubernetes objects, this should be the `metadata.annotations` + field on resources. For other implementations, this refers to + any relevant (implementation specific) \"annotations\" concepts. + \n An implementation may chose to add additional implementation-specific + annotations as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + labels: + additionalProperties: + description: AnnotationValue is the value of an annotation in + Gateway API. This is used for validation of maps such as TLS + options. This roughly matches Kubernetes annotation validation, + although the length validation in that case is based on the + entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Labels that SHOULD be applied to any resources created + in response to this Gateway. \n For implementations creating + other Kubernetes objects, this should be the `metadata.labels` + field on resources. For other implementations, this refers to + any relevant (implementation specific) \"labels\" concepts. + \n An implementation may chose to add additional implementation-specific + labels as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + type: object listeners: description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At - least one Listener MUST be specified. \n Each listener in a Gateway - must have a unique combination of Hostname, Port, and Protocol. - \n An implementation MAY group Listeners by Port and then collapse - each group of Listeners into a single Listener if the implementation - determines that the Listeners in the group are \"compatible\". An - implementation MAY also group together and collapse compatible Listeners - belonging to different Gateways. \n For example, an implementation - might consider Listeners to be compatible with each other if all - of the following conditions are met: \n 1. Either each Listener - within the group specifies the \"HTTP\" Protocol or each Listener - within the group specifies either the \"HTTPS\" or \"TLS\" Protocol. - \n 2. Each Listener within the group specifies a Hostname that is - unique within the group. \n 3. As a special case, one Listener - within a group may omit Hostname, in which case this Listener - matches when no other Listener matches. \n If the implementation - does collapse compatible Listeners, the hostname provided in the - incoming client request MUST be matched to a Listener to find the - correct set of Routes. The incoming hostname MUST be matched using - the Hostname field for each Listener in order of most to least specific. - That is, exact matches must be processed before wildcard matches. - \n If this field specifies multiple Listeners that have the same - Port value but are not compatible, the implementation must raise - a \"Conflicted\" condition in the Listener status. \n Support: Core" + least one Listener MUST be specified. \n Each Listener in a set + of Listeners (for example, in a single Gateway) MUST be _distinct_, + in that a traffic flow MUST be able to be assigned to exactly one + listener. (This section uses \"set of Listeners\" rather than \"Listeners + in a single Gateway\" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules + _also_ apply in that case). \n Practically, this means that each + listener in a set MUST have a unique combination of Port, Protocol, + and, if supported by the protocol, Hostname. \n Some combinations + of port, protocol, and TLS settings are considered Core support + and MUST be supported by implementations based on their targeted + conformance profile: \n HTTP Profile \n 1. HTTPRoute, Port: 80, + Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: + Terminate, TLS keypair provided \n TLS Profile \n 1. TLSRoute, Port: + 443, Protocol: TLS, TLS Mode: Passthrough \n \"Distinct\" Listeners + have the following property: \n The implementation can match inbound + requests to a single distinct Listener. When multiple Listeners + share values for fields (for example, two Listeners with the same + Port value), the implementation can match requests to only one of + the Listeners using other Listener fields. \n For example, the following + Listener scenarios are distinct: \n 1. Multiple Listeners with the + same Port that all use the \"HTTP\" Protocol that all have unique + Hostname values. 2. Multiple Listeners with the same Port that use + either the \"HTTPS\" or \"TLS\" Protocol that all have unique Hostname + values. 3. A mixture of \"TCP\" and \"UDP\" Protocol Listeners, + where no Listener with the same Protocol has the same Port value. + \n Some fields in the Listener struct have possible values that + affect whether the Listener is distinct. Hostname is particularly + relevant for HTTP or HTTPS protocols. \n When using the Hostname + value to select between same-Port, same-Protocol Listeners, the + Hostname value must be different on each Listener for the Listener + to be distinct. \n When the Listeners are distinct based on Hostname, + inbound request hostnames MUST match from the most specific to least + specific Hostname values to choose the correct Listener and its + associated set of Routes. \n Exact matches must be processed before + wildcard matches, and wildcard matches must be processed before + fallback (empty Hostname value) matches. For example, `\"foo.example.com\"` + takes precedence over `\"*.example.com\"`, and `\"*.example.com\"` + takes precedence over `\"\"`. \n Additionally, if there are multiple + wildcard entries, more specific wildcard entries must be processed + before less specific wildcard entries. For example, `\"*.foo.example.com\"` + takes precedence over `\"*.example.com\"`. The precise definition + here is that the higher the number of dots in the hostname to the + right of the wildcard character, the higher the precedence. \n The + wildcard character will match any number of characters _and dots_ + to the left, however, so `\"*.example.com\"` will match both `\"foo.bar.example.com\"` + _and_ `\"bar.example.com\"`. \n If a set of Listeners contains Listeners + that are not distinct, then those Listeners are Conflicted, and + the implementation MUST set the \"Conflicted\" condition in the + Listener Status to \"True\". \n Implementations MAY choose to accept + a Gateway with some Conflicted Listeners only if they only accept + the partial Listener set that contains no Conflicted Listeners. + To put this another way, implementations may accept a partial Listener + set only if they throw out *all* the conflicting Listeners. No picking + one of the conflicting listeners as the winner. This also means + that the Gateway must have at least one non-conflicting Listener + in this case, otherwise it violates the requirement that at least + one Listener must be present. \n The implementation MUST set a \"ListenersNotValid\" + condition on the Gateway Status when the Gateway contains Conflicted + Listeners whether or not they accept the Gateway. That Condition + SHOULD clearly indicate in the Message which Listeners are conflicted, + and which are Accepted. Additionally, the Listener status for those + listeners SHOULD indicate which Listeners are conflicted and not + Accepted. \n A Gateway's Listeners are considered \"compatible\" + if: \n 1. They are distinct. 2. The implementation can serve them + in compliance with the Addresses requirement that all Listeners + are available on all assigned addresses. \n Compatible combinations + in Extended support are expected to vary across implementations. + A combination that is compatible for one implementation may not + be compatible for another. \n For example, an implementation that + cannot serve both TCP and UDP listeners on the same address, or + cannot mix HTTPS and generic TLS listens on the same port would + not consider those cases compatible, even though they are distinct. + \n Note that requests SHOULD match at most one Listener. For example, + if Listeners are defined for \"foo.example.com\" and \"*.example.com\", + a request to \"foo.example.com\" SHOULD only be routed using routes + attached to the \"foo.example.com\" Listener (and not the \"*.example.com\" + Listener). This concept is known as \"Listener Isolation\". Implementations + that do not support Listener Isolation MUST clearly document this. + \n Implementations MAY merge separate Gateways onto a single set + of Addresses if all Listeners across all Gateways are compatible. + \n Support: Core" items: description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. @@ -369,19 +765,18 @@ spec: determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with - \ a creation timestamp of \"2020-09-08 01:02:03\" is given - precedence over a Route with a creation timestamp of \"2020-09-08 - 01:02:04\". * If everything else is equivalent, the Route - appearing first in alphabetical order (namespace/name) should - be given precedence. For example, foo/bar is given precedence - over foo/baz. \n All valid rules within a Route attached to - this Listener should be implemented. Invalid Route rules can - be ignored (sometimes that will mean the full Route). If a - Route rule transitions from valid to invalid, support for - that Route rule should be dropped to ensure consistency. For - example, even if a filter specified by a Route rule is invalid, - the rest of the rules within that Route should still be supported. - \n Support: Core" + a creation timestamp of \"2020-09-08 01:02:03\" is given precedence + over a Route with a creation timestamp of \"2020-09-08 01:02:04\". + * If everything else is equivalent, the Route appearing first + in alphabetical order (namespace/name) should be given precedence. + For example, foo/bar is given precedence over foo/baz. \n + All valid rules within a Route attached to this Listener should + be implemented. Invalid Route rules can be ignored (sometimes + that will mean the full Route). If a Route rule transitions + from valid to invalid, support for that Route rule should + be dropped to ensure consistency. For example, even if a filter + specified by a Route rule is invalid, the rest of the rules + within that Route should still be supported. \n Support: Core" properties: kinds: description: "Kinds specifies the groups and kinds of Routes @@ -392,7 +787,7 @@ spec: with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" - condition to False for this Listener with the \"InvalidRoutesRef\" + condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" items: description: RouteGroupKind indicates the group and kind @@ -426,12 +821,12 @@ spec: from: default: Same description: "From indicates where Routes will be selected - for this Gateway. Possible values are: * All: Routes + for this Gateway. Possible values are: \n * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may - be used by this Gateway. * Same: Only Routes in - the same namespace may be used by this Gateway. \n - Support: Core" + be used by this Gateway. * Same: Only Routes in the + same namespace may be used by this Gateway. \n Support: + Core" enum: - All - Selector @@ -487,6 +882,7 @@ spec: requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: object hostname: @@ -498,22 +894,25 @@ spec: following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname - SHOULD match at both the TLS and HTTP protocol layers as - described above. If an implementation does not ensure that - both the SNI and Host header match the Listener hostname, - \ it MUST clearly document that. \n For HTTPRoute and TLSRoute - resources, there is an interaction with the `spec.hostnames` - array. When both listener and route specify hostnames, there - MUST be an intersection between the values for a Route to - be accepted. For more information, refer to the Route specific - Hostnames documentation. \n Support: Core" + SHOULD match at both the TLS and HTTP protocol layers as described + above. If an implementation does not ensure that both the + SNI and Host header match the Listener hostname, it MUST clearly + document that. \n For HTTPRoute and TLSRoute resources, there + is an interaction with the `spec.hostnames` array. When both + listener and route specify hostnames, there MUST be an intersection + between the values for a Route to be accepted. For more information, + refer to the Route specific Hostnames documentation. \n Hostnames + that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` + would match both `test.example.com`, and `foo.test.example.com`, + but not `example.com`. \n Support: Core" maxLength: 253 minLength: 1 pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string name: - description: "Name is the name of the Listener. \n Support: - Core" + description: "Name is the name of the Listener. This name MUST + be unique within a Gateway. \n Support: Core" maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -553,18 +952,19 @@ spec: MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are - invalid UNLESS there is a ReferencePolicy in the target + invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. - If a ReferencePolicy does not allow this reference, the + If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this - listener with the \"InvalidCertificateRef\" reason. \n - This field is required to have at least one element when - the mode is set to \"Terminate\" (default) and is optional + listener with the \"RefNotPermitted\" reason. \n This + field is required to have at least one element when the + mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference - to a Kubernetes Secret \n Support: Implementation-specific - (More than one reference or other resource types)" + to a Kubernetes Secret of type kubernetes.io/tls \n Support: + Implementation-specific (More than one reference or other + resource types)" items: description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. @@ -578,15 +978,15 @@ spec: group: default: "" description: Group is the group of the referent. For - example, "networking.k8s.io". When unspecified (empty - string), core API group is inferred. + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Secret description: Kind is kind of the referent. For example - "HTTPRoute" or "Service". + "Secret". maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -597,12 +997,13 @@ spec: minLength: 1 type: string namespace: - description: "Namespace is the namespace of the backend. - When unspecified, the local namespace is inferred. - \n Note that when a namespace is specified, a ReferencePolicy + description: "Namespace is the namespace of the referenced + object. When unspecified, the local namespace is + inferred. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. - See the ReferencePolicy documentation for details. + See the ReferenceGrant documentation for details. \n Support: Core" maxLength: 63 minLength: 1 @@ -618,13 +1019,13 @@ spec: description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream - client and the Gateway is terminated at the Gateway. - This mode requires certificateRefs to be set and contain - at least one element. - Passthrough: The TLS session is - NOT terminated by the Gateway. This implies that the - Gateway can't decipher the TLS stream except for the - ClientHello message of the TLS protocol. CertificateRefs - field is ignored in this mode. \n Support: Core" + client and the Gateway is terminated at the Gateway. This + mode requires certificateRefs to be set and contain at + least one element. - Passthrough: The TLS session is NOT + terminated by the Gateway. This implies that the Gateway + can't decipher the TLS stream except for the ClientHello + message of the TLS protocol. CertificateRefs field is + ignored in this mode. \n Support: Core" enum: - Terminate - Passthrough @@ -651,6 +1052,11 @@ spec: maxProperties: 16 type: object type: object + x-kubernetes-validations: + - message: certificateRefs must be specified when TLSModeType + is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 : true' required: - name - port @@ -662,6 +1068,24 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must be specified for protocols ['HTTPS', 'TLS'] + rule: 'self.all(l, l.protocol in [''HTTPS'', ''TLS''] ? has(l.tls) + : true)' + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' required: - gatewayClassName - listeners @@ -671,27 +1095,47 @@ spec: conditions: - lastTransitionTime: "1970-01-01T00:00:00Z" message: Waiting for controller - reason: NotReconciled + reason: Pending status: Unknown - type: Scheduled + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed description: Status defines the current state of Gateway. properties: addresses: - description: Addresses lists the IP addresses that have actually been - bound to the Gateway. These addresses may differ from the addresses - in the Spec, e.g. if the Gateway automatically assigns an address - from a reserved pool. + description: "Addresses lists the network addresses that have been + bound to the Gateway. \n This list may differ from the addresses + provided in the spec under some conditions: \n * no addresses are + specified, all addresses are dynamically assigned * a combination + of specified and dynamic addresses are assigned * a specified address + was unusable (e.g. already in use) \n " items: - description: GatewayAddress describes an address that can be bound - to a Gateway. + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress properties: type: default: IPAddress description: Type of the address. - enum: - - IPAddress - - Hostname - - NamedAddress + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ type: string value: description: "Value of the address. The validity of the values @@ -703,32 +1147,41 @@ spec: required: - value type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' maxItems: 16 type: array conditions: default: - lastTransitionTime: "1970-01-01T00:00:00Z" message: Waiting for controller - reason: NotReconciled + reason: Pending status: Unknown - type: Scheduled + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary - to describe Gateway state. \n Known condition types are: \n * \"Scheduled\" - * \"Ready\"" + to describe Gateway state. \n Known condition types are: \n * \"Accepted\" + * \"Programmed\" * \"Ready\"" items: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition @@ -797,8 +1250,23 @@ spec: description: ListenerStatus is the status associated with a Listener. properties: attachedRoutes: - description: AttachedRoutes represents the total number of Routes - that have been successfully attached to this Listener. + description: "AttachedRoutes represents the total number of + Routes that have been successfully attached to this Listener. + \n Successful attachment of a Route to a Listener is based + solely on the combination of the AllowedRoutes field on the + corresponding Listener and the Route's ParentRefs field. A + Route is successfully attached to a Listener when it is selected + by the Listener's AllowedRoutes field AND the Route has a + valid ParentRef selecting the whole Gateway resource or a + specific Listener as a parent resource (more detail on attachment + semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does + not impact successful attachment, i.e. the AttachedRoutes + field count MUST be set for Listeners with condition Accepted: + false and MUST count successfully attached Routes that may + themselves have Accepted: false conditions. \n Uses for this + field include troubleshooting Route attachment and measuring + blast radius/impact of changes to a Listener." format: int32 type: integer conditions: @@ -808,14 +1276,902 @@ spec: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + .status.conditions. For example, \n type FooStatus struct{ + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: \"Available\", \"Progressing\", + and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields + }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds + supported by this listener. This MUST represent the kinds + an implementation supports for that Listener configuration. + \n If kinds are specified in Spec that are not supported, + they MUST NOT appear in this list and an implementation MUST + set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" + reason. If both valid and invalid Route kinds are specified, + the implementation MUST reference the valid Route kinds that + have been specified." + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling + infrastructure by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional + and behavior can depend on the implementation. If a value is set + in the spec and the requested address is invalid or unavailable, + the implementation MUST indicate this in the associated entry in + GatewayStatus.Addresses. \n The Addresses field represents a request + for the address(es) on the \"outside of the Gateway\", that traffic + bound for this Gateway will use. This could be the IP address or + hostname of an external load balancer or other networking infrastructure, + or some other address that traffic will be sent to. \n If no Addresses + are specified, the implementation MAY schedule the Gateway in an + implementation-specific manner, assigning an appropriate set of + Addresses. \n The implementation MUST bind all Listeners to every + GatewayAddress that it assigns to the Gateway and add a corresponding + entry in GatewayStatus.Addresses. \n Support: Extended \n " + items: + description: GatewayAddress describes an address that can be bound + to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name + of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: "Infrastructure defines infrastructure level attributes + about this Gateway instance. \n Support: Core \n " + properties: + annotations: + additionalProperties: + description: AnnotationValue is the value of an annotation in + Gateway API. This is used for validation of maps such as TLS + options. This roughly matches Kubernetes annotation validation, + although the length validation in that case is based on the + entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Annotations that SHOULD be applied to any resources + created in response to this Gateway. \n For implementations + creating other Kubernetes objects, this should be the `metadata.annotations` + field on resources. For other implementations, this refers to + any relevant (implementation specific) \"annotations\" concepts. + \n An implementation may chose to add additional implementation-specific + annotations as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + labels: + additionalProperties: + description: AnnotationValue is the value of an annotation in + Gateway API. This is used for validation of maps such as TLS + options. This roughly matches Kubernetes annotation validation, + although the length validation in that case is based on the + entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Labels that SHOULD be applied to any resources created + in response to this Gateway. \n For implementations creating + other Kubernetes objects, this should be the `metadata.labels` + field on resources. For other implementations, this refers to + any relevant (implementation specific) \"labels\" concepts. + \n An implementation may chose to add additional implementation-specific + labels as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + type: object + listeners: + description: "Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. At + least one Listener MUST be specified. \n Each Listener in a set + of Listeners (for example, in a single Gateway) MUST be _distinct_, + in that a traffic flow MUST be able to be assigned to exactly one + listener. (This section uses \"set of Listeners\" rather than \"Listeners + in a single Gateway\" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules + _also_ apply in that case). \n Practically, this means that each + listener in a set MUST have a unique combination of Port, Protocol, + and, if supported by the protocol, Hostname. \n Some combinations + of port, protocol, and TLS settings are considered Core support + and MUST be supported by implementations based on their targeted + conformance profile: \n HTTP Profile \n 1. HTTPRoute, Port: 80, + Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: + Terminate, TLS keypair provided \n TLS Profile \n 1. TLSRoute, Port: + 443, Protocol: TLS, TLS Mode: Passthrough \n \"Distinct\" Listeners + have the following property: \n The implementation can match inbound + requests to a single distinct Listener. When multiple Listeners + share values for fields (for example, two Listeners with the same + Port value), the implementation can match requests to only one of + the Listeners using other Listener fields. \n For example, the following + Listener scenarios are distinct: \n 1. Multiple Listeners with the + same Port that all use the \"HTTP\" Protocol that all have unique + Hostname values. 2. Multiple Listeners with the same Port that use + either the \"HTTPS\" or \"TLS\" Protocol that all have unique Hostname + values. 3. A mixture of \"TCP\" and \"UDP\" Protocol Listeners, + where no Listener with the same Protocol has the same Port value. + \n Some fields in the Listener struct have possible values that + affect whether the Listener is distinct. Hostname is particularly + relevant for HTTP or HTTPS protocols. \n When using the Hostname + value to select between same-Port, same-Protocol Listeners, the + Hostname value must be different on each Listener for the Listener + to be distinct. \n When the Listeners are distinct based on Hostname, + inbound request hostnames MUST match from the most specific to least + specific Hostname values to choose the correct Listener and its + associated set of Routes. \n Exact matches must be processed before + wildcard matches, and wildcard matches must be processed before + fallback (empty Hostname value) matches. For example, `\"foo.example.com\"` + takes precedence over `\"*.example.com\"`, and `\"*.example.com\"` + takes precedence over `\"\"`. \n Additionally, if there are multiple + wildcard entries, more specific wildcard entries must be processed + before less specific wildcard entries. For example, `\"*.foo.example.com\"` + takes precedence over `\"*.example.com\"`. The precise definition + here is that the higher the number of dots in the hostname to the + right of the wildcard character, the higher the precedence. \n The + wildcard character will match any number of characters _and dots_ + to the left, however, so `\"*.example.com\"` will match both `\"foo.bar.example.com\"` + _and_ `\"bar.example.com\"`. \n If a set of Listeners contains Listeners + that are not distinct, then those Listeners are Conflicted, and + the implementation MUST set the \"Conflicted\" condition in the + Listener Status to \"True\". \n Implementations MAY choose to accept + a Gateway with some Conflicted Listeners only if they only accept + the partial Listener set that contains no Conflicted Listeners. + To put this another way, implementations may accept a partial Listener + set only if they throw out *all* the conflicting Listeners. No picking + one of the conflicting listeners as the winner. This also means + that the Gateway must have at least one non-conflicting Listener + in this case, otherwise it violates the requirement that at least + one Listener must be present. \n The implementation MUST set a \"ListenersNotValid\" + condition on the Gateway Status when the Gateway contains Conflicted + Listeners whether or not they accept the Gateway. That Condition + SHOULD clearly indicate in the Message which Listeners are conflicted, + and which are Accepted. Additionally, the Listener status for those + listeners SHOULD indicate which Listeners are conflicted and not + Accepted. \n A Gateway's Listeners are considered \"compatible\" + if: \n 1. They are distinct. 2. The implementation can serve them + in compliance with the Addresses requirement that all Listeners + are available on all assigned addresses. \n Compatible combinations + in Extended support are expected to vary across implementations. + A combination that is compatible for one implementation may not + be compatible for another. \n For example, an implementation that + cannot serve both TCP and UDP listeners on the same address, or + cannot mix HTTPS and generic TLS listens on the same port would + not consider those cases compatible, even though they are distinct. + \n Note that requests SHOULD match at most one Listener. For example, + if Listeners are defined for \"foo.example.com\" and \"*.example.com\", + a request to \"foo.example.com\" SHOULD only be routed using routes + attached to the \"foo.example.com\" Listener (and not the \"*.example.com\" + Listener). This concept is known as \"Listener Isolation\". Implementations + that do not support Listener Isolation MUST clearly document this. + \n Implementations MAY merge separate Gateways onto a single set + of Addresses if all Listeners across all Gateways are compatible. + \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint + where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that + MAY be attached to a Listener and the trusted namespaces where + those Route resources MAY be present. \n Although a client + request may match multiple route rules, only one rule may + ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: \n * The most + specific match as defined by the Route type. * The oldest + Route based on creation timestamp. For example, a Route with + a creation timestamp of \"2020-09-08 01:02:03\" is given precedence + over a Route with a creation timestamp of \"2020-09-08 01:02:04\". + * If everything else is equivalent, the Route appearing first + in alphabetical order (namespace/name) should be given precedence. + For example, foo/bar is given precedence over foo/baz. \n + All valid rules within a Route attached to this Listener should + be implemented. Invalid Route rules can be ignored (sometimes + that will mean the full Route). If a Route rule transitions + from valid to invalid, support for that Route rule should + be dropped to ensure consistency. For example, even if a filter + specified by a Route rule is invalid, the rest of the rules + within that Route should still be supported. \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes + that are allowed to bind to this Gateway Listener. When + unspecified or empty, the kinds of Routes selected are + determined using the Listener protocol. \n A RouteGroupKind + MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's + Protocol field. If an implementation does not support + or recognize this resource type, it MUST set the \"ResolvedRefs\" + condition to False for this Listener with the \"InvalidRouteKinds\" + reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which + Routes may be attached to this Listener. This is restricted + to the namespace of this Gateway by default. \n Support: + Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected + for this Gateway. Possible values are: \n * All: Routes + in all namespaces may be used by this Gateway. * Selector: + Routes in namespaces selected by the selector may + be used by this Gateway. * Same: Only Routes in the + same namespace may be used by this Gateway. \n Support: + Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is + set to \"Selector\". In that case, only Routes in + Namespaces matching this Selector will be selected + by this Gateway. This field is ignored for other values + of \"From\". \n Support: Core" + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match + for protocol types that define this concept. When unspecified, + all hostnames are matched. This field is ignored for protocols + that don't require hostname based matching. \n Implementations + MUST apply Hostname matching appropriately for each of the + following protocols: \n * TLS: The Listener Hostname MUST + match the SNI. * HTTP: The Listener Hostname MUST match the + Host header of the request. * HTTPS: The Listener Hostname + SHOULD match at both the TLS and HTTP protocol layers as described + above. If an implementation does not ensure that both the + SNI and Host header match the Listener hostname, it MUST clearly + document that. \n For HTTPRoute and TLSRoute resources, there + is an interaction with the `spec.hostnames` array. When both + listener and route specify hostnames, there MUST be an intersection + between the values for a Route to be accepted. For more information, + refer to the Route specific Hostnames documentation. \n Hostnames + that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` + would match both `test.example.com`, and `foo.test.example.com`, + but not `example.com`. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: "Name is the name of the Listener. This name MUST + be unique within a Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: "Port is the network port. Multiple listeners may + use the same port, subject to the Listener compatibility rules. + \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener + expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. + This field is required if the Protocol field is \"HTTPS\" + or \"TLS\". It is invalid to set this field if the Protocol + field is \"HTTP\", \"TCP\", or \"UDP\". \n The association + of SNIs to Certificate defined in GatewayTLSConfig is defined + based on the Hostname field for this listener. \n The GatewayClass + MUST use the longest matching SNI out of all available certificates + for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references + to Kubernetes objects that contains TLS certificates and + private keys. These certificates are used to establish + a TLS handshake for requests that match the hostname of + the associated listener. \n A single CertificateRef to + a Kubernetes Secret has \"Core\" support. Implementations + MAY choose to support attaching multiple certificates + to a Listener, but this behavior is implementation-specific. + \n References to a resource in different namespace are + invalid UNLESS there is a ReferenceGrant in the target + namespace that allows the certificate to be attached. + If a ReferenceGrant does not allow this reference, the + \"ResolvedRefs\" condition MUST be set to False for this + listener with the \"RefNotPermitted\" reason. \n This + field is required to have at least one element when the + mode is set to \"Terminate\" (default) and is optional + otherwise. \n CertificateRefs can reference to standard + Kubernetes resources, i.e. Secret, or implementation-specific + custom resources. \n Support: Core - A single reference + to a Kubernetes Secret of type kubernetes.io/tls \n Support: + Implementation-specific (More than one reference or other + resource types)" + items: + description: "SecretObjectReference identifies an API + object including its namespace, defaulting to Secret. + \n The API object must be valid in the cluster; the + Group and Kind must be registered in the cluster for + this reference to be valid. \n References to objects + with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate + Conditions set on the containing object." + properties: + group: + default: "" + description: Group is the group of the referent. For + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referenced + object. When unspecified, the local namespace is + inferred. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace to + allow that namespace's owner to accept the reference. + See the ReferenceGrant documentation for details. + \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS + session initiated by the client. There are two possible + modes: \n - Terminate: The TLS session between the downstream + client and the Gateway is terminated at the Gateway. This + mode requires certificateRefs to be set and contain at + least one element. - Passthrough: The TLS session is NOT + terminated by the Gateway. This implies that the Gateway + can't decipher the TLS stream except for the ClientHello + message of the TLS protocol. CertificateRefs field is + ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation + in Gateway API. This is used for validation of maps + such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation + in that case is based on the entire size of the annotations + struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable + extended TLS configuration for each implementation. For + example, configuring the minimum TLS version or supported + cipher suites. \n A set of common keys MAY be defined + by the API in the future. To avoid any ambiguity, implementation-specific + definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by + Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs must be specified when TLSModeType + is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must be specified for protocols ['HTTPS', 'TLS'] + rule: 'self.all(l, l.protocol in [''HTTPS'', ''TLS''] ? has(l.tls) + : true)' + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: "Addresses lists the network addresses that have been + bound to the Gateway. \n This list may differ from the addresses + provided in the spec under some conditions: \n * no addresses are + specified, all addresses are dynamically assigned * a combination + of specified and dynamic addresses are assigned * a specified address + was unusable (e.g. already in use) \n " + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values + will depend on the type and support by the controller. \n + Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the Gateway. + \n Implementations should prefer to express Gateway conditions using + the `GatewayConditionType` and `GatewayConditionReason` constants + so that operators and tools can converge on a common vocabulary + to describe Gateway state. \n Known condition types are: \n * \"Accepted\" + * \"Programmed\" * \"Ready\"" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: "AttachedRoutes represents the total number of + Routes that have been successfully attached to this Listener. + \n Successful attachment of a Route to a Listener is based + solely on the combination of the AllowedRoutes field on the + corresponding Listener and the Route's ParentRefs field. A + Route is successfully attached to a Listener when it is selected + by the Listener's AllowedRoutes field AND the Route has a + valid ParentRef selecting the whole Gateway resource or a + specific Listener as a parent resource (more detail on attachment + semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does + not impact successful attachment, i.e. the AttachedRoutes + field count MUST be set for Listeners with condition Accepted: + false and MUST count successfully attached Routes that may + themselves have Accepted: false conditions. \n Uses for this + field include troubleshooting Route attachment and measuring + blast radius/impact of changes to a Listener." + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, \n type FooStatus struct{ + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: \"Available\", \"Progressing\", + and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields + }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition @@ -941,26 +2297,27 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] - + conditions: null + storedVersions: null --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null - name: httproutes.gateway.networking.k8s.io + name: grpcroutes.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io names: categories: - gateway-api - kind: HTTPRoute - listKind: HTTPRouteList - plural: httproutes - singular: httproute + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute scope: Namespaced versions: - additionalPrinterColumns: @@ -973,10 +2330,27 @@ spec: name: v1alpha2 schema: openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes - the capability to match requests by hostname, path, header, or query param. - Filters can be used to specify additional processing steps. Backends specify - where matching requests should be routed. + description: "GRPCRoute provides a way to route gRPC requests. This includes + the capability to match requests by hostname, gRPC service, gRPC method, + or HTTP/2 header. Filters can be used to specify additional processing steps. + Backends specify where matching requests will be routed. \n GRPCRoute falls + under extended support within the Gateway API. Within the following specification, + the word \"MUST\" indicates that an implementation supporting GRPCRoute + must conform to the indicated requirement, but an implementation not supporting + this route type need not follow the requirement unless explicitly indicated. + \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` + MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, + i.e. via ALPN. If the implementation does not support this, then it MUST + set the \"Accepted\" condition to \"False\" for the affected listener with + a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 + connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` + with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, + https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade + from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). + If the implementation does not support this, then it MUST set the \"Accepted\" + condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". + Implementations MAY also accept HTTP/2 connections with an upgrade from + HTTP/1, i.e. without prior knowledge." properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -991,39 +2365,52 @@ spec: metadata: type: object spec: - description: Spec defines the desired state of HTTPRoute. + description: Spec defines the desired state of GRPCRoute. properties: hostnames: - description: "Hostnames defines a set of hostname that should match - against the HTTP Host header to select a HTTPRoute to process the - request. This matches the RFC 1123 definition of a hostname with - 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may - be prefixed with a wildcard label (`*.`). The wildcard label - must appear by itself as the first label. \n If a hostname is specified - by both the Listener and HTTPRoute, there must be at least one intersecting - hostname for the HTTPRoute to be attached to the Listener. For example: + description: "Hostnames defines a set of hostnames to match against + the GRPC Host header to select a GRPCRoute to process the request. + This matches the RFC 1123 definition of a hostname with 2 notable + exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed + with a wildcard label (`*.`). The wildcard label MUST appear by + itself as the first label. \n If a hostname is specified by both + the Listener and GRPCRoute, there MUST be at least one intersecting + hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches - HTTPRoutes that have either not specified any hostnames, or have - specified at least one of `test.example.com` or `*.example.com`. - * A Listener with `*.example.com` as the hostname matches HTTPRoutes - \ that have either not specified any hostnames or have specified - at least one hostname that matches the Listener hostname. For - example, `test.example.com` and `*.example.com` would both match. - On the other hand, `example.com` and `test.example.net` would - not match. \n If both the Listener and HTTPRoute have specified - hostnames, any HTTPRoute hostnames that do not match the Listener - hostname MUST be ignored. For example, if a Listener specified `*.example.com`, - and the HTTPRoute specified `test.example.com` and `test.example.net`, - `test.example.net` must not be considered for a match. \n If both - the Listener and HTTPRoute have specified hostnames, and none match - with the criteria above, then the HTTPRoute is not accepted. The - implementation must raise an 'Accepted' Condition with a status - of `False` in the corresponding RouteParentStatus. \n Support: Core" + GRPCRoutes that have either not specified any hostnames, or have + specified at least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames or have specified at + least one hostname that matches the Listener hostname. For example, + `test.example.com` and `*.example.com` would both match. On the + other hand, `example.com` and `test.example.net` would not match. + \n Hostnames that are prefixed with a wildcard label (`*.`) are + interpreted as a suffix match. That means that a match for `*.example.com` + would match both `test.example.com`, and `foo.test.example.com`, + but not `example.com`. \n If both the Listener and GRPCRoute have + specified hostnames, any GRPCRoute hostnames that do not match the + Listener hostname MUST be ignored. For example, if a Listener specified + `*.example.com`, and the GRPCRoute specified `test.example.com` + and `test.example.net`, `test.example.net` MUST NOT be considered + for a match. \n If both the Listener and GRPCRoute have specified + hostnames, and none match with the criteria above, then the GRPCRoute + MUST NOT be accepted by the implementation. The implementation MUST + raise an 'Accepted' Condition with a status of `False` in the corresponding + RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute + is attached to a Listener and that listener already has another + Route (B) of the other type attached and the intersection of the + hostnames of A and B is non-empty, then the implementation MUST + accept exactly one of these two routes, determined by the following + criteria, in order: \n * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by \"{namespace}/{name}\". + \n The rejected Route MUST raise an 'Accepted' condition with a + status of 'False' in the corresponding RouteParentStatus. \n Support: + Core" items: description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname - may be prefixed with a wildcard label (`*.`). The wildcard label + may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain @@ -1042,40 +2429,76 @@ spec: that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from - Routes of this kind and namespace. \n The only kind of parent resource - with \"Core\" support is Gateway. This API may be extended in the - future to support additional kinds of parent resources such as one - of the route kinds. \n It is invalid to reference an identical parent - more than once. It is valid to reference multiple distinct sections - within the same parent resource, such as 2 Listeners within a Gateway. - \n It is possible to separately reference multiple distinct objects - that may be collapsed by an implementation. For example, some implementations - may choose to merge compatible Gateway Listeners together. If that - is the case, the list of routes attached to those resources should - also be merged." + Routes of this kind and namespace. For Services, that means the + Service must either be in the same namespace for a \"producer\" + route, or the mesh implementation must support and allow \"consumer\" + routes for the referenced Service. ReferenceGrant is not applicable + for governing ParentRefs to Services - it is not possible to create + a \"producer\" route for a Service in a different namespace from + the Route. \n There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services only) This + API may be extended in the future to support additional kinds of + parent resources. \n ParentRefs must be _distinct_. This means either + that: \n * They select different objects. If this is the case, + then parentRef entries are distinct. In terms of fields, this means + that the multi-part key defined by `group`, `kind`, `namespace`, + and `name` must be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field + used, each ParentRef that selects the same object must set the same + set of optional fields to different values. If one ParentRef sets + a combination of optional fields, all must set the same combination. + \n Some examples: \n * If one ParentRef sets `sectionName`, all + ParentRefs referencing the same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. * If one ParentRef sets `sectionName` + and `port`, all ParentRefs referencing the same object must also + set `sectionName` and `port`. \n It is possible to separately reference + multiple distinct objects that may be collapsed by an implementation. + For example, some implementations may choose to merge compatible + Gateway Listeners together. If that is the case, the list of routes + attached to those resources should also be merged. \n Note that + for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For + example, Gateway has the AllowedRoutes field, and ReferenceGrant + provides a generic way to enable other kinds of cross-namespace + reference. \n ParentRefs from a Route to a Service in the same + namespace are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. \n ParentRefs + from a Route to a Service in a different namespace are \"consumer\" + routes, and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for which the + intended destination of the connections are a Service targeted as + a ParentRef of the Route. \n " items: - description: "ParentRef identifies an API object (usually a Gateway) - that can be considered a parent of this resource (usually a route). - The only kind of parent resource with \"Core\" support is Gateway. - This API may be extended in the future to support additional kinds - of parent resources, such as HTTPRoute. \n The API object must - be valid in the cluster; the Group and Kind must be registered - in the cluster for this reference to be valid. \n References to - objects with invalid Group and Kind are not valid, and must be - rejected by the implementation, with appropriate Conditions set - on the containing object." + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service + (Mesh conformance profile, experimental, ClusterIP Services only) + \n This API may be extended in the future to support additional + kinds of parent resources. \n The API object must be valid in + the cluster; the Group and Kind must be registered in the cluster + for this reference to be valid." properties: group: default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: Core" maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Gateway - description: "Kind is kind of the referent. \n Support: Core - (Gateway) Support: Custom (Other Resources)" + description: "Kind is kind of the referent. \n There are two + kinds of parent resources with \"Core\" support: \n * Gateway + (Gateway conformance profile) * Service (Mesh conformance + profile, experimental, ClusterIP Services only) \n Support + for other resources is Implementation-Specific." maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -1088,29 +2511,79 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. When - unspecified (or empty string), this refers to the local namespace - of the Route. \n Support: Core" + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n ParentRefs from a Route to a Service in the same namespace + are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. + \n ParentRefs from a Route to a Service in a different namespace + are \"consumer\" routes, and these routing rules are only + applied to outbound connections originating from the same + namespace as the Route, for which the intended destination + of the connections are a Service targeted as a ParentRef of + the Route. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string - sectionName: - description: "SectionName is the name of a section within the - target resource. In the following resources, SectionName is - interpreted as the following: \n * Gateway: Listener Name - \n Implementations MAY choose to support attaching Routes - to other resources. If that is the case, they MUST clearly - document how SectionName is interpreted. \n When unspecified - (empty string), this will reference the entire resource. For - the purpose of status, an attachment is considered successful - if at least one section in the parent resource accepts it. + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n When the parent resource + is a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified + values. \n Implementations MAY choose to support other parent + resources. Implementations supporting other types of parent + resources MUST clearly document how/if Port is interpreted. + \n For the purpose of status, an attachment is considered + successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n - Support: Core" + Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. * Service: Port Name. When both Port (experimental) + and SectionName are specified, the name and port of the selected + listener must match both specified values. Note that attaching + Routes to Services as Parents is part of experimental Mesh + support and is not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this will + reference the entire resource. For the purpose of status, + an attachment is considered successful if at least one section + in the parent resource accepts it. For example, Gateway listeners + can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept + attachment from the referencing Route, the Route MUST be considered + successfully attached. If no Gateway listeners accept attachment + from this Route, the Route MUST be considered detached from + the Gateway. \n Support: Core" maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -1120,42 +2593,87 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. + description: Rules are a list of GRPC matchers, filters and actions. items: - description: HTTPRouteRule defines semantics for matching an HTTP - request based on conditions (matches), processing it (filters), + description: GRPCRouteRule defines the semantics for matching a + gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). properties: backendRefs: - description: "If unspecified or invalid (refers to a non-existent - resource or a Service with no endpoints), the rule performs - no forwarding. If there are also no filters specified that - would result in a response being sent, a HTTP 503 status code - is returned. 503 responses must be sent so that the overall - weight is respected; if an invalid backend is requested to - have 80% of requests, then 80% of requests must get a 503 - instead. \n Support: Core for Kubernetes Service Support: - Custom for any other resource \n Support for weight: Core" + description: "BackendRefs defines the backend(s) where matching + requests should be sent. \n Failure behavior here depends + on how many BackendRefs are specified and how many are invalid. + \n If *all* entries in BackendRefs are invalid, and there + are also no filters specified in this route rule, *all* traffic + which matches this rule MUST receive an `UNAVAILABLE` status. + \n See the GRPCBackendRef definition for the rules about what + makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef + is invalid, `UNAVAILABLE` statuses MUST be returned for requests + that would have otherwise been routed to an invalid backend. + If multiple backends are specified, and some are invalid, + the proportion of requests that would otherwise have been + routed to an invalid backend MUST receive an `UNAVAILABLE` + status. \n For example, if two backends are specified with + equal weights, and one is invalid, 50 percent of traffic MUST + receive an `UNAVAILABLE` status. Implementations may choose + how that 50 percent is determined. \n Support: Core for Kubernetes + Service \n Support: Implementation-specific for any other + resource \n Support for weight: Core" items: - description: HTTPBackendRef defines how a HTTPRoute should - forward an HTTP request. + description: "GRPCBackendRef defines how a GRPCRoute forwards + a gRPC request. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. \n + \n When the BackendRef points to a Kubernetes Service, implementations + SHOULD honor the appProtocol field if it is set for the + target Service Port. \n Implementations supporting appProtocol + SHOULD recognize the Kubernetes Standard Application Protocols + defined in KEP-3726. \n If a Service appProtocol isn't specified, + an implementation MAY infer the backend protocol through + its own means. Implementations MAY infer the protocol from + the Route type referring to the backend Service. \n If a + Route is not able to send traffic to the backend using the + specified protocol then the backend is considered invalid. + Implementations MUST set the \"ResolvedRefs\" condition + to \"False\" with the \"UnsupportedProtocol\" reason. \n + " properties: filters: - description: "Filters defined at this level should be - executed if and only if the request is being forwarded - to the backend defined here. \n Support: Custom (For - broader support of filters, use the Filters field in - HTTPRouteRule.)" + description: "Filters defined at this level MUST be executed + if and only if the request is being forwarded to the + backend defined here. \n Support: Implementation-specific + (For broader support of filters, use the Filters field + in GRPCRouteRule.)" items: - description: HTTPRouteFilter defines processing steps + description: GRPCRouteFilter defines processing steps that must be completed during the request or response - lifecycle. HTTPRouteFilters are meant as an extension + lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication @@ -1168,12 +2686,15 @@ spec: extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended - filters. \n Support: Implementation-specific" + filters. \n Support: Implementation-specific \n + This filter can be used multiple times within + the same rule." properties: group: description: Group is the group of the referent. - For example, "networking.k8s.io". When unspecified - (empty string), core API group is inferred. + For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API + group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string @@ -1203,11 +2724,10 @@ spec: description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated - with the header name. \n Input: GET /foo - HTTP/1.1 my-header: foo \n Config: add: - \ - name: \"my-header\" value: \"bar\" - \n Output: GET /foo HTTP/1.1 my-header: - foo my-header: bar" + with the header name. \n Input: GET /foo HTTP/1.1 + my-header: foo \n Config: add: - name: \"my-header\" + value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 + my-header: foo,bar,baz" items: description: HTTPHeader represents an HTTP Header name and value as defined by RFC @@ -1250,22 +2770,22 @@ spec: of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - \n Input: GET /foo HTTP/1.1 my-header1: - foo my-header2: bar my-header3: baz \n - Config: remove: [\"my-header1\", \"my-header3\"] - \n Output: GET /foo HTTP/1.1 my-header2: - bar" + \n Input: GET /foo HTTP/1.1 my-header1: foo + my-header2: bar my-header3: baz \n Config: + remove: [\"my-header1\", \"my-header3\"] \n + Output: GET /foo HTTP/1.1 my-header2: bar" items: type: string maxItems: 16 type: array + x-kubernetes-list-type: set set: description: "Set overwrites the request with the given header (name, value) before the - action. \n Input: GET /foo HTTP/1.1 my-header: - foo \n Config: set: - name: \"my-header\" - \ value: \"bar\" \n Output: GET /foo - HTTP/1.1 my-header: bar" + action. \n Input: GET /foo HTTP/1.1 my-header: + foo \n Config: set: - name: \"my-header\" + value: \"bar\" \n Output: GET /foo HTTP/1.1 + my-header: bar" items: description: HTTPHeader represents an HTTP Header name and value as defined by RFC @@ -1307,42 +2827,61 @@ spec: description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from - that destination are ignored. \n Support: Extended" + that destination are ignored. \n This filter can + be used multiple times within the same rule. Note + that not all implementations will be able to support + mirroring to multiple backends. \n Support: Extended" properties: backendRef: description: "BackendRef references a resource - where mirrored requests are sent. \n If the - referent cannot be found, this BackendRef - is invalid and must be dropped from the Gateway. - The controller must ensure the \"ResolvedRefs\" - condition on the Route status is set to `status: - False` and not configure this backend in the - underlying implementation. \n If there is - a cross-namespace reference to an *existing* - object that is not allowed by a ReferencePolicy, - the controller must ensure the \"ResolvedRefs\" - \ condition on the Route is set to `status: - False`, with the \"RefNotPermitted\" reason - and not configure this backend in the underlying - implementation. \n In either error case, the - Message of the `ResolvedRefs` Condition should - be used to provide more detail about the problem. - \n Support: Extended for Kubernetes Service - Support: Custom for any other resource" + where mirrored requests are sent. \n Mirrored + requests must be sent only to a single destination + endpoint within this BackendRef, irrespective + of how many endpoints are present within this + BackendRef. \n If the referent cannot be found, + this BackendRef is invalid and must be dropped + from the Gateway. The controller must ensure + the \"ResolvedRefs\" condition on the Route + status is set to `status: False` and not configure + this backend in the underlying implementation. + \n If there is a cross-namespace reference + to an *existing* object that is not allowed + by a ReferenceGrant, the controller must ensure + the \"ResolvedRefs\" condition on the Route + is set to `status: False`, with the \"RefNotPermitted\" + reason and not configure this backend in the + underlying implementation. \n In either error + case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about + the problem. \n Support: Extended for Kubernetes + Service \n Support: Implementation-specific + for any other resource" properties: group: default: "" description: Group is the group of the referent. - For example, "networking.k8s.io". When - unspecified (empty string), core API group - is inferred. + For example, "gateway.networking.k8s.io". + When unspecified or empty string, core + API group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Service - description: Kind is kind of the referent. - For example "HTTPRoute" or "Service". + description: "Kind is the Kubernetes resource + kind of the referent. For example \"Service\". + \n Defaults to \"Service\" when not specified. + \n ExternalName services can refer to + CNAME DNS records that may live outside + of the cluster and as such are difficult + to reason about in terms of conformance. + They also may not be safe to forward to + (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with + a type other than ExternalName) \n Support: + Implementation-specific (Services with + type ExternalName)" maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -1356,10 +2895,11 @@ spec: description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that - when a namespace is specified, a ReferencePolicy + when a namespace different than the local + namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept - the reference. See the ReferencePolicy + the reference. See the ReferenceGrant documentation for details. \n Support: Core" maxLength: 63 @@ -1370,7 +2910,9 @@ spec: description: Port specifies the destination port number to use for this resource. Port is required when the referent is - a Kubernetes Service. For other resources, + a Kubernetes Service. In this case, the + port number is the service port number, + not the target port. For other resources, destination port might be derived from the referent resource or this field. format: int32 @@ -1380,99 +2922,217 @@ spec: required: - name type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' required: - backendRef type: object - requestRedirect: - description: "RequestRedirect defines a schema for - a filter that responds to the request with an - HTTP redirection. \n Support: Core" + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema + for a filter that modifies response headers. \n + Support: Extended" properties: - hostname: - description: "Hostname is the hostname to be - used in the value of the `Location` header - in the response. When empty, the hostname - of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - port: - description: "Port is the port to be used in - the value of the `Location` header in the - response. When empty, port (if specified) - of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used - in the value of the `Location` header in the - response. When empty, the scheme of the request - is used. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status - code to be used in response. \n Support: Core" - enum: - - 301 - - 302 - type: integer + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. \n Input: GET /foo HTTP/1.1 + my-header: foo \n Config: add: - name: \"my-header\" + value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 + my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo + my-header2: bar my-header3: baz \n Config: + remove: [\"my-header1\", \"my-header3\"] \n + Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with + the given header (name, value) before the + action. \n Input: GET /foo HTTP/1.1 my-header: + foo \n Config: set: - name: \"my-header\" + value: \"bar\" \n Output: GET /foo HTTP/1.1 + my-header: bar" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map type: object type: description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration - defined by \"Support: Core\" in this package, - e.g. \"RequestHeaderModifier\". All implementations - must support core filters. \n - Extended: Filter - types and their corresponding configuration defined - by \"Support: Extended\" in this package, e.g. - \"RequestMirror\". Implementers are encouraged - to support extended filters. \n - Custom: Filters - that are defined and supported by specific vendors. - \ In the future, filters showing convergence - in behavior across multiple implementations - will be considered for inclusion in extended or - core conformance levels. Filter-specific configuration - for such filters is specified using the ExtensionRef - field. `Type` should be set to \"ExtensionRef\" - for custom filters. \n Implementers are encouraged - to define custom implementation types to extend - the core API with implementation-specific behavior. - \n If a reference to a custom filter type cannot - be resolved, the filter MUST NOT be skipped. Instead, - requests that would have been processed by that - filter MUST receive a HTTP error response." + defined by \"Support: Core\" in this package, + e.g. \"RequestHeaderModifier\". All implementations + supporting GRPCRoute MUST support core filters. + \n - Extended: Filter types and their corresponding + configuration defined by \"Support: Extended\" + in this package, e.g. \"RequestMirror\". Implementers + are encouraged to support extended filters. \n + - Implementation-specific: Filters that are defined + and supported by specific vendors. In the future, + filters showing convergence in behavior across + multiple implementations will be considered for + inclusion in extended or core conformance levels. + Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` + MUST be set to \"ExtensionRef\" for custom filters. + \n Implementers are encouraged to define custom + implementation types to extend the core API with + implementation-specific behavior. \n If a reference + to a custom filter type cannot be resolved, the + filter MUST NOT be skipped. Instead, requests + that would have been processed by that filter + MUST receive a HTTP error response. \n " enum: + - ResponseHeaderModifier - RequestHeaderModifier - RequestMirror - - RequestRedirect - ExtensionRef type: string required: - type type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' maxItems: 16 type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 group: default: "" description: Group is the group of the referent. For example, - "networking.k8s.io". When unspecified (empty string), - core API group is inferred. + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Service - description: Kind is kind of the referent. For example - "HTTPRoute" or "Service". + description: "Kind is the Kubernetes resource kind of + the referent. For example \"Service\". \n Defaults to + \"Service\" when not specified. \n ExternalName services + can refer to CNAME DNS records that may live outside + of the cluster and as such are difficult to reason about + in terms of conformance. They also may not be safe to + forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName Services. + \n Support: Core (Services with a type other than ExternalName) + \n Support: Implementation-specific (Services with type + ExternalName)" maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -1485,11 +3145,11 @@ spec: namespace: description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n - Note that when a namespace is specified, a ReferencePolicy - object is required in the referent namespace to allow - that namespace's owner to accept the reference. See - the ReferencePolicy documentation for details. \n Support: - Core" + Note that when a namespace different than the local + namespace is specified, a ReferenceGrant object is required + in the referent namespace to allow that namespace's + owner to accept the reference. See the ReferenceGrant + documentation for details. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ @@ -1497,9 +3157,10 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required when the - referent is a Kubernetes Service. For other resources, - destination port might be derived from the referent - resource or this field. + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -1526,6 +3187,10 @@ spec: required: - name type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' maxItems: 16 type: array filters: @@ -1535,15 +3200,20 @@ spec: change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by - all implementations. - Implementers are encouraged to support - extended filters. - Implementation-specific custom filters - have no API guarantees across implementations. \n Specifying - a core filter multiple times has unspecified or custom conformance. - \n Support: Core" + all implementations that support GRPCRoute. - Implementers + are encouraged to support extended filters. - Implementation-specific + custom filters have no API guarantees across implementations. + \n Specifying the same filter multiple times is not supported + unless explicitly indicated in the filter. \n If an implementation + can not support a combination of filters, it must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to + be set to status `False`, implementations may use the `IncompatibleFilters` + reason to specify this configuration error. \n Support: Core" items: - description: HTTPRouteFilter defines processing steps that + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. - HTTPRouteFilters are meant as an extension point to express + GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and @@ -1555,12 +3225,13 @@ spec: extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended - filters. \n Support: Implementation-specific" + filters. \n Support: Implementation-specific \n This + filter can be used multiple times within the same rule." properties: group: description: Group is the group of the referent. For - example, "networking.k8s.io". When unspecified (empty - string), core API group is inferred. + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string @@ -1590,10 +3261,9 @@ spec: description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header - name. \n Input: GET /foo HTTP/1.1 my-header: - foo \n Config: add: - name: \"my-header\" value: - \"bar\" \n Output: GET /foo HTTP/1.1 my-header: - foo my-header: bar" + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" items: description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. @@ -1633,20 +3303,21 @@ spec: HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - \n Input: GET /foo HTTP/1.1 my-header1: foo - \ my-header2: bar my-header3: baz \n Config: - \ remove: [\"my-header1\", \"my-header3\"] \n Output: - \ GET /foo HTTP/1.1 my-header2: bar" + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" items: type: string maxItems: 16 type: array + x-kubernetes-list-type: set set: description: "Set overwrites the request with the given header (name, value) before the action. \n - Input: GET /foo HTTP/1.1 my-header: foo \n Config: - \ set: - name: \"my-header\" value: \"bar\" - \n Output: GET /foo HTTP/1.1 my-header: bar" + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" items: description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. @@ -1686,39 +3357,57 @@ spec: description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are - ignored. \n Support: Extended" + ignored. \n This filter can be used multiple times within + the same rule. Note that not all implementations will + be able to support mirroring to multiple backends. \n + Support: Extended" properties: backendRef: description: "BackendRef references a resource where - mirrored requests are sent. \n If the referent cannot - be found, this BackendRef is invalid and must be - dropped from the Gateway. The controller must ensure - the \"ResolvedRefs\" condition on the Route status - is set to `status: False` and not configure this - backend in the underlying implementation. \n If - there is a cross-namespace reference to an *existing* - object that is not allowed by a ReferencePolicy, - the controller must ensure the \"ResolvedRefs\" - \ condition on the Route is set to `status: False`, - with the \"RefNotPermitted\" reason and not configure - this backend in the underlying implementation. \n - In either error case, the Message of the `ResolvedRefs` - Condition should be used to provide more detail - about the problem. \n Support: Extended for Kubernetes - Service Support: Custom for any other resource" + mirrored requests are sent. \n Mirrored requests + must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many + endpoints are present within this BackendRef. \n + If the referent cannot be found, this BackendRef + is invalid and must be dropped from the Gateway. + The controller must ensure the \"ResolvedRefs\" + condition on the Route status is set to `status: + False` and not configure this backend in the underlying + implementation. \n If there is a cross-namespace + reference to an *existing* object that is not allowed + by a ReferenceGrant, the controller must ensure + the \"ResolvedRefs\" condition on the Route is + set to `status: False`, with the \"RefNotPermitted\" + reason and not configure this backend in the underlying + implementation. \n In either error case, the Message + of the `ResolvedRefs` Condition should be used to + provide more detail about the problem. \n Support: + Extended for Kubernetes Service \n Support: Implementation-specific + for any other resource" properties: group: default: "" description: Group is the group of the referent. - For example, "networking.k8s.io". When unspecified - (empty string), core API group is inferred. + For example, "gateway.networking.k8s.io". When + unspecified or empty string, core API group + is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Service - description: Kind is kind of the referent. For - example "HTTPRoute" or "Service". + description: "Kind is the Kubernetes resource + kind of the referent. For example \"Service\". + \n Defaults to \"Service\" when not specified. + \n ExternalName services can refer to CNAME + DNS records that may live outside of the cluster + and as such are difficult to reason about in + terms of conformance. They also may not be safe + to forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with a + type other than ExternalName) \n Support: Implementation-specific + (Services with type ExternalName)" maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -1731,11 +3420,12 @@ spec: namespace: description: "Namespace is the namespace of the backend. When unspecified, the local namespace - is inferred. \n Note that when a namespace is - specified, a ReferencePolicy object is required - in the referent namespace to allow that namespace's - owner to accept the reference. See the ReferencePolicy - documentation for details. \n Support: Core" + is inferred. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept the + reference. See the ReferenceGrant documentation + for details. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ @@ -1743,9 +3433,11 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required - when the referent is a Kubernetes Service. For - other resources, destination port might be derived - from the referent resource or this field. + when the referent is a Kubernetes Service. In + this case, the port number is the service port + number, not the target port. For other resources, + destination port might be derived from the referent + resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -1753,6 +3445,2024 @@ spec: required: - name type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema + for a filter that modifies response headers. \n Support: + Extended" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. + As with other API fields, types are classified into + three conformance levels: \n - Core: Filter types and + their corresponding configuration defined by \"Support: + Core\" in this package, e.g. \"RequestHeaderModifier\". + All implementations supporting GRPCRoute MUST support + core filters. \n - Extended: Filter types and their + corresponding configuration defined by \"Support: Extended\" + in this package, e.g. \"RequestMirror\". Implementers + are encouraged to support extended filters. \n - Implementation-specific: + Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior + across multiple implementations will be considered for + inclusion in extended or core conformance levels. Filter-specific + configuration for such filters is specified using the + ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" + for custom filters. \n Implementers are encouraged to + define custom implementation types to extend the core + API with implementation-specific behavior. \n If a reference + to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have + been processed by that filter MUST receive a HTTP error + response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + matches: + description: "Matches define conditions used for matching the + rule against incoming gRPC requests. Each match is independent, + i.e. this rule will be matched if **any** one of the matches + is satisfied. \n For example, take the following matches configuration: + \n ``` matches: - method: service: foo.bar headers: values: + version: 2 - method: service: foo.bar.v2 ``` \n For a request + to match against this rule, it MUST satisfy EITHER of the + two conditions: \n - service of foo.bar AND contains the header + `version: 2` - service of foo.bar.v2 \n See the documentation + for GRPCRouteMatch on how to specify multiple match conditions + to be ANDed together. \n If no matches are specified, the + implementation MUST match every gRPC request. \n Proxy or + Load Balancer routing configuration generated from GRPCRoutes + MUST prioritize rules based on the following criteria, continuing + on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. + Precedence MUST be given to the rule with the largest number + of: \n * Characters in a matching non-wildcard hostname. * + Characters in a matching hostname. * Characters in a matching + service. * Characters in a matching method. * Header matches. + \n If ties still exist across multiple Routes, matching precedence + MUST be determined in order of the following criteria, continuing + on ties: \n * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by \"{namespace}/{name}\". + \n If ties still exist within the Route that has been given + precedence, matching precedence MUST be granted to the first + matching rule meeting the above criteria." + items: + description: "GRPCRouteMatch defines the predicate used to + match requests to a given action. Multiple match types are + ANDed together, i.e. the match will evaluate to true only + if all conditions are satisfied. \n For example, the match + below will match a gRPC request only if its service is `foo` + AND it contains the `version: v1` header: \n ``` matches: + - method: type: Exact service: \"foo\" headers: - name: + \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies gRPC request header matchers. + Multiple match values are ANDed together, meaning, a + request MUST match all the specified headers to select + the route. + items: + description: GRPCHeaderMatch describes how to select + a gRPC route by matching gRPC request headers. + properties: + name: + description: "Name is the name of the gRPC Header + to be matched. \n If multiple entries specify + equivalent header names, only the first entry + with an equivalent name MUST be considered for + a match. Subsequent entries with an equivalent + header name MUST be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against + the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: Method specifies a gRPC request service/method + matcher. If this field is not specified, all services + and methods will match. + properties: + method: + description: "Value of the method to match against. + If left empty or omitted, will match all services. + \n At least one of Service and Method MUST be a + non-empty string." + maxLength: 1024 + type: string + service: + description: "Value of the service to match against. + If left empty or omitted, will match any service. + \n At least one of Service and Method MUST be a + non-empty string." + maxLength: 1024 + type: string + type: + default: Exact + description: "Type specifies how to match against + the service and/or method. Support: Core (Exact + with service and method specified) \n Support: Implementation-specific + (Exact with method specified but no service specified) + \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - RegularExpression + type: string + type: object + x-kubernetes-validations: + - message: One or both of 'service' or 'method' must be + specified + rule: 'has(self.type) ? has(self.service) || has(self.method) + : true' + - message: service must only contain valid characters + (matching ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.service) ? self.service.matches(r"""^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$"""): + true' + - message: method must only contain valid characters (matching + ^[A-Za-z_][A-Za-z_0-9]*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): + true' + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) + that are associated with the route, and the status of the route + with respect to each parent. When this route attaches to a parent, + the controller that manages the parent must add an entry to this + list when the controller first sees the route and should update + the entry as appropriate when the route or gateway is modified. + \n Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this + API can only populate Route status for the Gateways/parent resources + they are responsible for. \n A maximum of 32 Gateways will be represented + in this list. An empty list means the route has not been attached + to any Gateway." + items: + description: RouteParentStatus describes the status of a route with + respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with + respect to the Gateway. Note that the route's availability + is also subject to the Gateway's own status conditions and + listener status. \n If the Route's ParentRef specifies an + existing Gateway that supports Routes of this kind AND that + Gateway's controller has sufficient access, then that Gateway's + controller MUST set the \"Accepted\" condition on the Route, + to indicate whether the route has been accepted or rejected + by the Gateway, and why. \n A Route MUST be considered \"Accepted\" + if at least one of the Route's rules is implemented by the + Gateway. \n There are a number of cases where the \"Accepted\" + condition may not be set due to lack of controller visibility, + that includes when: \n * The Route refers to a non-existent + parent. * The Route is of a type that the controller does + not support. * The Route is in a namespace the controller + does not have access to." + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, \n type FooStatus struct{ + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: \"Available\", \"Progressing\", + and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields + }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates + the name of the controller that wrote this status. This corresponds + with the controllerName field on GatewayClass. \n Example: + \"example.net/gateway-controller\". \n The format of this + field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec + that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are + two kinds of parent resources with \"Core\" support: \n + * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services + only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n ParentRefs from a Route to a Service in + the same namespace are \"producer\" routes, which apply + default routing rules to inbound connections from any + namespace to the Service. \n ParentRefs from a Route to + a Service in a different namespace are \"consumer\" routes, + and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for + which the intended destination of the connections are + a Service targeted as a ParentRef of the Route. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n When the parent resource is + a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are + specified, the name and port of the selected port must + match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within + the target resource. In the following resources, SectionName + is interpreted as the following: \n * Gateway: Listener + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. * Service: Port Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match + both specified values. Note that attaching Routes to Services + as Parents is part of experimental Mesh support and is + not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes + the capability to match requests by hostname, path, header, or query param. + Filters can be used to specify additional processing steps. Backends specify + where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames that should match + against the HTTP Host header to select a HTTPRoute used to process + the request. Implementations MUST ignore any port value specified + in the HTTP Host header while performing a match and (absent of + any applicable header modification configuration) MUST forward this + header unmodified to the backend. \n Valid values for Hostnames + are determined by RFC 1123 definition of a hostname with 2 notable + exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed + with a wildcard label (`*.`). The wildcard label must appear by + itself as the first label. \n If a hostname is specified by both + the Listener and HTTPRoute, there must be at least one intersecting + hostname for the HTTPRoute to be attached to the Listener. For example: + \n * A Listener with `test.example.com` as the hostname matches + HTTPRoutes that have either not specified any hostnames, or have + specified at least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at + least one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` + would all match. On the other hand, `example.com` and `test.example.net` + would not match. \n Hostnames that are prefixed with a wildcard + label (`*.`) are interpreted as a suffix match. That means that + a match for `*.example.com` would match both `test.example.com`, + and `foo.test.example.com`, but not `example.com`. \n If both the + Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames + that do not match the Listener hostname MUST be ignored. For example, + if a Listener specified `*.example.com`, and the HTTPRoute specified + `test.example.com` and `test.example.net`, `test.example.net` must + not be considered for a match. \n If both the Listener and HTTPRoute + have specified hostnames, and none match with the criteria above, + then the HTTPRoute is not accepted. The implementation must raise + an 'Accepted' Condition with a status of `False` in the corresponding + RouteParentStatus. \n In the event that multiple HTTPRoutes specify + intersecting hostnames (e.g. overlapping wildcard matching and exact + matching hostnames), precedence must be given to rules from the + HTTPRoute with the largest number of: \n * Characters in a matching + non-wildcard hostname. * Characters in a matching hostname. \n If + ties exist across multiple Routes, the matching precedence rules + for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network + host. This matches the RFC 1123 definition of a hostname with + 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname + may be prefixed with a wildcard label (`*.`). The wildcard label + must appear by itself as the first label. \n Hostname can be \"precise\" + which is a domain name without the terminating dot of a network + host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain + name prefixed with a single wildcard label (e.g. `*.example.com`). + \n Note that as per RFC1035 and RFC1123, a *label* must consist + of lower case alphanumeric characters or '-', and must start and + end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) + that a Route wants to be attached to. Note that the referenced parent + resource needs to allow this for the attachment to be complete. + For Gateways, that means the Gateway needs to allow attachment from + Routes of this kind and namespace. For Services, that means the + Service must either be in the same namespace for a \"producer\" + route, or the mesh implementation must support and allow \"consumer\" + routes for the referenced Service. ReferenceGrant is not applicable + for governing ParentRefs to Services - it is not possible to create + a \"producer\" route for a Service in a different namespace from + the Route. \n There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services only) This + API may be extended in the future to support additional kinds of + parent resources. \n ParentRefs must be _distinct_. This means either + that: \n * They select different objects. If this is the case, + then parentRef entries are distinct. In terms of fields, this means + that the multi-part key defined by `group`, `kind`, `namespace`, + and `name` must be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field + used, each ParentRef that selects the same object must set the same + set of optional fields to different values. If one ParentRef sets + a combination of optional fields, all must set the same combination. + \n Some examples: \n * If one ParentRef sets `sectionName`, all + ParentRefs referencing the same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. * If one ParentRef sets `sectionName` + and `port`, all ParentRefs referencing the same object must also + set `sectionName` and `port`. \n It is possible to separately reference + multiple distinct objects that may be collapsed by an implementation. + For example, some implementations may choose to merge compatible + Gateway Listeners together. If that is the case, the list of routes + attached to those resources should also be merged. \n Note that + for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For + example, Gateway has the AllowedRoutes field, and ReferenceGrant + provides a generic way to enable other kinds of cross-namespace + reference. \n ParentRefs from a Route to a Service in the same + namespace are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. \n ParentRefs + from a Route to a Service in a different namespace are \"consumer\" + routes, and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for which the + intended destination of the connections are a Service targeted as + a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service + (Mesh conformance profile, experimental, ClusterIP Services only) + \n This API may be extended in the future to support additional + kinds of parent resources. \n The API object must be valid in + the cluster; the Group and Kind must be registered in the cluster + for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: + Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two + kinds of parent resources with \"Core\" support: \n * Gateway + (Gateway conformance profile) * Service (Mesh conformance + profile, experimental, ClusterIP Services only) \n Support + for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n ParentRefs from a Route to a Service in the same namespace + are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. + \n ParentRefs from a Route to a Service in a different namespace + are \"consumer\" routes, and these routing rules are only + applied to outbound connections originating from the same + namespace as the Route, for which the intended destination + of the connections are a Service targeted as a ParentRef of + the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n When the parent resource + is a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified + values. \n Implementations MAY choose to support other parent + resources. Implementations supporting other types of parent + resources MUST clearly document how/if Port is interpreted. + \n For the purpose of status, an attachment is considered + successful as long as the parent resource accepts it partially. + For example, Gateway listeners can restrict which Routes can + attach to them by Route kind, namespace, or hostname. If 1 + of 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. \n + Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. * Service: Port Name. When both Port (experimental) + and SectionName are specified, the name and port of the selected + listener must match both specified values. Note that attaching + Routes to Services as Parents is part of experimental Mesh + support and is not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this will + reference the entire resource. For the purpose of status, + an attachment is considered successful if at least one section + in the parent resource accepts it. For example, Gateway listeners + can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept + attachment from the referencing Route, the Route MUST be considered + successfully attached. If no Gateway listeners accept attachment + from this Route, the Route MUST be considered detached from + the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP + request based on conditions (matches), processing it (filters), + and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching + requests should be sent. \n Failure behavior here depends + on how many BackendRefs are specified and how many are invalid. + \n If *all* entries in BackendRefs are invalid, and there + are also no filters specified in this route rule, *all* traffic + which matches this rule MUST receive a 500 status code. \n + See the HTTPBackendRef definition for the rules about what + makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef + is invalid, 500 status codes MUST be returned for requests + that would have otherwise been routed to an invalid backend. + If multiple backends are specified, and some are invalid, + the proportion of requests that would otherwise have been + routed to an invalid backend MUST receive a 500 status code. + \n For example, if two backends are specified with equal weights, + and one is invalid, 50 percent of traffic must receive a 500. + Implementations may choose how that 50 percent is determined. + \n Support: Core for Kubernetes Service \n Support: Extended + for Kubernetes ServiceImport \n Support: Implementation-specific + for any other resource \n Support for weight: Core" + items: + description: "HTTPBackendRef defines how a HTTPRoute forwards + a HTTP request. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. \n + \n When the BackendRef points to a Kubernetes Service, implementations + SHOULD honor the appProtocol field if it is set for the + target Service Port. \n Implementations supporting appProtocol + SHOULD recognize the Kubernetes Standard Application Protocols + defined in KEP-3726. \n If a Service appProtocol isn't specified, + an implementation MAY infer the backend protocol through + its own means. Implementations MAY infer the protocol from + the Route type referring to the backend Service. \n If a + Route is not able to send traffic to the backend using the + specified protocol then the backend is considered invalid. + Implementations MUST set the \"ResolvedRefs\" condition + to \"False\" with the \"UnsupportedProtocol\" reason. \n + " + properties: + filters: + description: "Filters defined at this level should be + executed if and only if the request is being forwarded + to the backend defined here. \n Support: Implementation-specific + (For broader support of filters, use the Filters field + in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps + that must be completed during the request or response + lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway + implementations. Some examples include request or + response modification, implementing authentication + strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type + of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n This filter can be used multiple times + within the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API + group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema + for a filter that modifies request headers. \n + Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. \n Input: GET /foo HTTP/1.1 + my-header: foo \n Config: add: - name: \"my-header\" + value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 + my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo + my-header2: bar my-header3: baz \n Config: + remove: [\"my-header1\", \"my-header3\"] \n + Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with + the given header (name, value) before the + action. \n Input: GET /foo HTTP/1.1 my-header: + foo \n Config: set: - name: \"my-header\" + value: \"bar\" \n Output: GET /foo HTTP/1.1 + my-header: bar" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for + a filter that mirrors requests. Requests are sent + to the specified destination, but responses from + that destination are ignored. \n This filter can + be used multiple times within the same rule. Note + that not all implementations will be able to support + mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource + where mirrored requests are sent. \n Mirrored + requests must be sent only to a single destination + endpoint within this BackendRef, irrespective + of how many endpoints are present within this + BackendRef. \n If the referent cannot be found, + this BackendRef is invalid and must be dropped + from the Gateway. The controller must ensure + the \"ResolvedRefs\" condition on the Route + status is set to `status: False` and not configure + this backend in the underlying implementation. + \n If there is a cross-namespace reference + to an *existing* object that is not allowed + by a ReferenceGrant, the controller must ensure + the \"ResolvedRefs\" condition on the Route + is set to `status: False`, with the \"RefNotPermitted\" + reason and not configure this backend in the + underlying implementation. \n In either error + case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about + the problem. \n Support: Extended for Kubernetes + Service \n Support: Implementation-specific + for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. + For example, "gateway.networking.k8s.io". + When unspecified or empty string, core + API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource + kind of the referent. For example \"Service\". + \n Defaults to \"Service\" when not specified. + \n ExternalName services can refer to + CNAME DNS records that may live outside + of the cluster and as such are difficult + to reason about in terms of conformance. + They also may not be safe to forward to + (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with + a type other than ExternalName) \n Support: + Implementation-specific (Services with + type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace + of the backend. When unspecified, the + local namespace is inferred. \n Note that + when a namespace different than the local + namespace is specified, a ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept + the reference. See the ReferenceGrant + documentation for details. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination + port number to use for this resource. + Port is required when the referent is + a Kubernetes Service. In this case, the + port number is the service port number, + not the target port. For other resources, + destination port might be derived from + the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for + a filter that responds to the request with an + HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be + used in the value of the `Location` header + in the response. When empty, the hostname + in the `Host` header of the request is used. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to + modify the path of the incoming request. The + modified path is then used to construct the + `Location` header. When empty, the request + path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the + value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies + the value with which to replace the prefix + match of a request during a rewrite or + redirect. For example, a request to \"/foo/bar\" + with a prefix match of \"/foo\" and a + ReplacePrefixMatch of \"/xyz\" would be + modified to \"/xyz/bar\". \n Note that + this matches the behavior of the PathPrefix + match type. This matches full path elements. + A path element refers to the list of labels + in the path split by the `/` separator. + When specified, a trailing `/` is ignored. + For example, the paths `/abc`, `/abc/`, + and `/abc/def` would all match the prefix + `/abc`, but the path `/abcd` would not. + \n ReplacePrefixMatch is only compatible + with a `PathPrefix` HTTPRouteMatch. Using + any other HTTPRouteMatch type on the same + HTTPRouteRule will result in the implementation + setting the Accepted Condition for the + Route to `status: False`. \n Request Path + | Prefix Match | Replace Prefix | Modified + Path -------------|--------------|----------------|---------- + /foo/bar | /foo | /xyz | + /xyz/bar /foo/bar | /foo | + /xyz/ | /xyz/bar /foo/bar | + /foo/ | /xyz | /xyz/bar + /foo/bar | /foo/ | /xyz/ | + /xyz/bar /foo | /foo | + /xyz | /xyz /foo/ | /foo + \ | /xyz | /xyz/ /foo/bar + \ | /foo | | + /bar /foo/ | /foo | | / /foo | /foo | + | / /foo/ | /foo + \ | / | / /foo | + /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path + modifier. Additional types may be added + in a future release of the API. \n Note + that values may be added to this enum, + implementations must ensure that unknown + values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the + Route to `status: False`, with a Reason + of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in + the value of the `Location` header in the + response. \n If no port is specified, the + redirect port MUST be derived using the following + rules: \n * If redirect scheme is not-empty, + the redirect port MUST be the well-known port + associated with the redirect scheme. Specifically + \"http\" to port 80 and \"https\" to port + 443. If the redirect scheme does not have + a well-known port, the listener port of the + Gateway SHOULD be used. * If redirect scheme + is empty, the redirect port MUST be the Gateway + Listener port. \n Implementations SHOULD NOT + add the port number in the 'Location' header + in the following cases: \n * A Location header + that will use HTTP (whether that is determined + via the Listener protocol or the Scheme field) + _and_ use port 80. * A Location header that + will use HTTPS (whether that is determined + via the Listener protocol or the Scheme field) + _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used + in the value of the `Location` header in the + response. When empty, the scheme of the request + is used. \n Scheme redirects can affect the + port of the redirect, for more information, + refer to the documentation for the port field + of this filter. \n Note that values may be + added to this enum, implementations must ensure + that unknown values will not cause a crash. + \n Unknown values here must result in the + implementation setting the Accepted Condition + for the Route to `status: False`, with a Reason + of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status + code to be used in response. \n Note that + values may be added to this enum, implementations + must ensure that unknown values will not cause + a crash. \n Unknown values here must result + in the implementation setting the Accepted + Condition for the Route to `status: False`, + with a Reason of `UnsupportedValue`. \n Support: + Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema + for a filter that modifies response headers. \n + Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. \n Input: GET /foo HTTP/1.1 + my-header: foo \n Config: add: - name: \"my-header\" + value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 + my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo + my-header2: bar my-header3: baz \n Config: + remove: [\"my-header1\", \"my-header3\"] \n + Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with + the given header (name, value) before the + action. \n Input: GET /foo HTTP/1.1 my-header: + foo \n Config: set: - name: \"my-header\" + value: \"bar\" \n Output: GET /foo HTTP/1.1 + my-header: bar" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter + to apply. As with other API fields, types are + classified into three conformance levels: \n - + Core: Filter types and their corresponding configuration + defined by \"Support: Core\" in this package, + e.g. \"RequestHeaderModifier\". All implementations + must support core filters. \n - Extended: Filter + types and their corresponding configuration defined + by \"Support: Extended\" in this package, e.g. + \"RequestMirror\". Implementers are encouraged + to support extended filters. \n - Implementation-specific: + Filters that are defined and supported by specific + vendors. In the future, filters showing convergence + in behavior across multiple implementations will + be considered for inclusion in extended or core + conformance levels. Filter-specific configuration + for such filters is specified using the ExtensionRef + field. `Type` should be set to \"ExtensionRef\" + for custom filters. \n Implementers are encouraged + to define custom implementation types to extend + the core API with implementation-specific behavior. + \n If a reference to a custom filter type cannot + be resolved, the filter MUST NOT be skipped. Instead, + requests that would have been processed by that + filter MUST receive a HTTP error response. \n + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause + a crash. \n Unknown values here must result in + the implementation setting the Accepted Condition + for the Route to `status: False`, with a Reason + of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a + filter that modifies a request during forwarding. + \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used + to replace the Host header value during forwarding. + \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n + Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the + value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies + the value with which to replace the prefix + match of a request during a rewrite or + redirect. For example, a request to \"/foo/bar\" + with a prefix match of \"/foo\" and a + ReplacePrefixMatch of \"/xyz\" would be + modified to \"/xyz/bar\". \n Note that + this matches the behavior of the PathPrefix + match type. This matches full path elements. + A path element refers to the list of labels + in the path split by the `/` separator. + When specified, a trailing `/` is ignored. + For example, the paths `/abc`, `/abc/`, + and `/abc/def` would all match the prefix + `/abc`, but the path `/abcd` would not. + \n ReplacePrefixMatch is only compatible + with a `PathPrefix` HTTPRouteMatch. Using + any other HTTPRouteMatch type on the same + HTTPRouteRule will result in the implementation + setting the Accepted Condition for the + Route to `status: False`. \n Request Path + | Prefix Match | Replace Prefix | Modified + Path -------------|--------------|----------------|---------- + /foo/bar | /foo | /xyz | + /xyz/bar /foo/bar | /foo | + /xyz/ | /xyz/bar /foo/bar | + /foo/ | /xyz | /xyz/bar + /foo/bar | /foo/ | /xyz/ | + /xyz/bar /foo | /foo | + /xyz | /xyz /foo/ | /foo + \ | /xyz | /xyz/ /foo/bar + \ | /foo | | + /bar /foo/ | /foo | | / /foo | /foo | + | / /foo/ | /foo + \ | / | / /foo | + /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path + modifier. Additional types may be added + in a future release of the API. \n Note + that values may be added to this enum, + implementations must ensure that unknown + values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the + Route to `status: False`, with a Reason + of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of + the referent. For example \"Service\". \n Defaults to + \"Service\" when not specified. \n ExternalName services + can refer to CNAME DNS records that may live outside + of the cluster and as such are difficult to reason about + in terms of conformance. They also may not be safe to + forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName Services. + \n Support: Core (Services with a type other than ExternalName) + \n Support: Implementation-specific (Services with type + ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. + When unspecified, the local namespace is inferred. \n + Note that when a namespace different than the local + namespace is specified, a ReferenceGrant object is required + in the referent namespace to allow that namespace's + owner to accept the reference. See the ReferenceGrant + documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number + to use for this resource. Port is required when the + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1. \n Support for this field varies based + on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to + requests that match this rule. \n The effects of ordering + of multiple behaviors are currently unspecified. This can + change in the future based on feedback during the alpha stage. + \n Conformance-levels at this level are defined based on the + type of filter: \n - ALL core filters MUST be supported by + all implementations. - Implementers are encouraged to support + extended filters. - Implementation-specific custom filters + have no API guarantees across implementations. \n Specifying + the same filter multiple times is not supported unless explicitly + indicated in the filter. \n All filters are expected to be + compatible with each other except for the URLRewrite and RequestRedirect + filters, which may not be combined. If an implementation can + not support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to + be set to status `False`, implementations may use the `IncompatibleFilters` + reason to specify this configuration error. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that + must be completed during the request or response lifecycle. + HTTPRouteFilters are meant as an extension point to express + processing that may be done in Gateway implementations. + Some examples include request or response modification, + implementing authentication strategies, rate-limiting, and + traffic shaping. API guarantee/conformance is defined based + on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n This filter can be used multiple times within + the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for + a filter that modifies request headers. \n Support: + Core" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter + that mirrors requests. Requests are sent to the specified + destination, but responses from that destination are + ignored. \n This filter can be used multiple times within + the same rule. Note that not all implementations will + be able to support mirroring to multiple backends. \n + Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where + mirrored requests are sent. \n Mirrored requests + must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many + endpoints are present within this BackendRef. \n + If the referent cannot be found, this BackendRef + is invalid and must be dropped from the Gateway. + The controller must ensure the \"ResolvedRefs\" + condition on the Route status is set to `status: + False` and not configure this backend in the underlying + implementation. \n If there is a cross-namespace + reference to an *existing* object that is not allowed + by a ReferenceGrant, the controller must ensure + the \"ResolvedRefs\" condition on the Route is + set to `status: False`, with the \"RefNotPermitted\" + reason and not configure this backend in the underlying + implementation. \n In either error case, the Message + of the `ResolvedRefs` Condition should be used to + provide more detail about the problem. \n Support: + Extended for Kubernetes Service \n Support: Implementation-specific + for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. + For example, "gateway.networking.k8s.io". When + unspecified or empty string, core API group + is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource + kind of the referent. For example \"Service\". + \n Defaults to \"Service\" when not specified. + \n ExternalName services can refer to CNAME + DNS records that may live outside of the cluster + and as such are difficult to reason about in + terms of conformance. They also may not be safe + to forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with a + type other than ExternalName) \n Support: Implementation-specific + (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the + backend. When unspecified, the local namespace + is inferred. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept the + reference. See the ReferenceGrant documentation + for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port + number to use for this resource. Port is required + when the referent is a Kubernetes Service. In + this case, the port number is the service port + number, not the target port. For other resources, + destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' required: - backendRef type: object @@ -1764,17 +5474,114 @@ spec: hostname: description: "Hostname is the hostname to be used in the value of the `Location` header in the response. - When empty, the hostname of the request is used. - \n Support: Core" + When empty, the hostname in the `Host` header of + the request is used. \n Support: Core" maxLength: 253 minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + path: + description: "Path defines parameters used to modify + the path of the incoming request. The modified path + is then used to construct the `Location` header. + When empty, the request path is used as-is. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" and a ReplacePrefixMatch of + \"/xyz\" would be modified to \"/xyz/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not. \n ReplacePrefixMatch is + only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same + HTTPRouteRule will result in the implementation + setting the Accepted Condition for the Route + to `status: False`. \n Request Path | Prefix + Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- + /foo/bar | /foo | /xyz | + /xyz/bar /foo/bar | /foo | /xyz/ + \ | /xyz/bar /foo/bar | /foo/ | + /xyz | /xyz/bar /foo/bar | /foo/ + \ | /xyz/ | /xyz/bar /foo | + /foo | /xyz | /xyz /foo/ | + /foo | /xyz | /xyz/ /foo/bar + \ | /foo | | /bar + /foo/ | /foo | + | / /foo | /foo | + | / /foo/ | /foo | / | + / /foo | /foo | / | + /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' port: description: "Port is the port to be used in the value - of the `Location` header in the response. When empty, - port (if specified) of the request is used. \n Support: - Extended" + of the `Location` header in the response. \n If + no port is specified, the redirect port MUST be + derived using the following rules: \n * If redirect + scheme is not-empty, the redirect port MUST be the + well-known port associated with the redirect scheme. + Specifically \"http\" to port 80 and \"https\" to + port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway + SHOULD be used. * If redirect scheme is empty, the + redirect port MUST be the Gateway Listener port. + \n Implementations SHOULD NOT add the port number + in the 'Location' header in the following cases: + \n * A Location header that will use HTTP (whether + that is determined via the Listener protocol or + the Scheme field) _and_ use port 80. * A Location + header that will use HTTPS (whether that is determined + via the Listener protocol or the Scheme field) _and_ + use port 443. \n Support: Extended" format: int32 maximum: 65535 minimum: 1 @@ -1783,7 +5590,15 @@ spec: description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n - Support: Extended" + Scheme redirects can affect the port of the redirect, + for more information, refer to the documentation + for the port field of this filter. \n Note that + values may be added to this enum, implementations + must ensure that unknown values will not cause a + crash. \n Unknown values here must result in the + implementation setting the Accepted Condition for + the Route to `status: False`, with a Reason of `UnsupportedValue`. + \n Support: Extended" enum: - http - https @@ -1791,47 +5606,310 @@ spec: statusCode: default: 302 description: "StatusCode is the HTTP status code to - be used in response. \n Support: Core" + be used in response. \n Note that values may be + added to this enum, implementations must ensure + that unknown values will not cause a crash. \n Unknown + values here must result in the implementation setting + the Accepted Condition for the Route to `status: + False`, with a Reason of `UnsupportedValue`. \n + Support: Core" enum: - 301 - 302 type: integer type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema + for a filter that modifies response headers. \n Support: + Extended" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object type: description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and - their corresponding configuration defined by \"Support: + their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". - All implementations must support core filters. \n - - Extended: Filter types and their corresponding configuration - defined by \"Support: Extended\" in this package, - e.g. \"RequestMirror\". Implementers are encouraged - to support extended filters. \n - Custom: Filters that - are defined and supported by specific vendors. In - the future, filters showing convergence in behavior - across multiple implementations will be considered - for inclusion in extended or core conformance levels. - Filter-specific configuration for such filters is - specified using the ExtensionRef field. `Type` should - be set to \"ExtensionRef\" for custom filters. \n - Implementers are encouraged to define custom implementation - types to extend the core API with implementation-specific - behavior. \n If a reference to a custom filter type - cannot be resolved, the filter MUST NOT be skipped. - Instead, requests that would have been processed by - that filter MUST receive a HTTP error response." + All implementations must support core filters. \n - + Extended: Filter types and their corresponding configuration + defined by \"Support: Extended\" in this package, e.g. + \"RequestMirror\". Implementers are encouraged to support + extended filters. \n - Implementation-specific: Filters + that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior + across multiple implementations will be considered for + inclusion in extended or core conformance levels. Filter-specific + configuration for such filters is specified using the + ExtensionRef field. `Type` should be set to \"ExtensionRef\" + for custom filters. \n Implementers are encouraged to + define custom implementation types to extend the core + API with implementation-specific behavior. \n If a reference + to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have + been processed by that filter MUST receive a HTTP error + response. \n Note that values may be added to this enum, + implementations must ensure that unknown values will + not cause a crash. \n Unknown values here must result + in the implementation setting the Accepted Condition + for the Route to `status: False`, with a Reason of `UnsupportedValue`." enum: - RequestHeaderModifier + - ResponseHeaderModifier - RequestMirror - RequestRedirect + - URLRewrite - ExtensionRef type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter + that modifies a request during forwarding. \n Support: + Extended" + properties: + hostname: + description: "Hostname is the value to be used to + replace the Host header value during forwarding. + \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" and a ReplacePrefixMatch of + \"/xyz\" would be modified to \"/xyz/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not. \n ReplacePrefixMatch is + only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same + HTTPRouteRule will result in the implementation + setting the Accepted Condition for the Route + to `status: False`. \n Request Path | Prefix + Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- + /foo/bar | /foo | /xyz | + /xyz/bar /foo/bar | /foo | /xyz/ + \ | /xyz/bar /foo/bar | /foo/ | + /xyz | /xyz/bar /foo/bar | /foo/ + \ | /xyz/ | /xyz/bar /foo | + /foo | /xyz | /xyz /foo/ | + /foo | /xyz | /xyz/ /foo/bar + \ | /foo | | /bar + /foo/ | /foo | + | / /foo | /foo | + | / /foo/ | /foo | / | + / /foo | /foo | / | + /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object required: - type type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' maxItems: 16 type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 matches: default: - path: @@ -1841,29 +5919,33 @@ spec: rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: - \n ``` matches: - path: value: \"/foo\" headers: - - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" - ``` \n For a request to match against this rule, a request - must satisfy EITHER of the two conditions: \n - path prefixed - with `/foo` AND contains the header `version: v2` - path prefix - of `/v2/foo` \n See the documentation for HTTPRouteMatch on - how to specify multiple match conditions that should be ANDed - together. \n If no matches are specified, the default is a - prefix path match on \"/\", which has the effect of matching - every HTTP request. \n Proxy or Load Balancer routing configuration - generated from HTTPRoutes MUST prioritize rules based on the - following criteria, continuing on ties. Precedence must be - given to the the Rule with the largest number of: \n * Characters - in a matching non-wildcard hostname. * Characters in a matching - hostname. * Characters in a matching path. * Header matches. - * Query param matches. \n If ties still exist across multiple + \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" + value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request + to match against this rule, a request must satisfy EITHER + of the two conditions: \n - path prefixed with `/foo` AND + contains the header `version: v2` - path prefix of `/v2/foo` + \n See the documentation for HTTPRouteMatch on how to specify + multiple match conditions that should be ANDed together. \n + If no matches are specified, the default is a prefix path + match on \"/\", which has the effect of matching every HTTP + request. \n Proxy or Load Balancer routing configuration generated + from HTTPRoutes MUST prioritize matches based on the following + criteria, continuing on ties. Across all rules specified on + applicable Routes, precedence must be given to the match having: + \n * \"Exact\" path match. * \"Prefix\" path match with largest + number of characters. * Method match. * Largest number of + header matches. * Largest number of query param matches. \n + Note: The precedence of RegularExpression path matches are + implementation-specific. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first - in alphabetical order by \"/\". \n If ties - still exist within the Route that has been given precedence, - matching precedence MUST be granted to the first matching - rule meeting the above criteria." + in alphabetical order by \"{namespace}/{name}\". \n If ties + still exist within an HTTPRoute, matching precedence MUST + be granted to the FIRST matching rule (in list order) with + a match meeting the above criteria. \n When no rules matching + a request have been successfully attached to the parent a + request is coming from, a HTTP 404 status code MUST be returned." items: description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are @@ -1871,8 +5953,8 @@ spec: if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n - ``` match: path: value: \"/foo\" headers: - name: - \"version\" value \"v1\" ```" + ``` match: \n path: value: \"/foo\" headers: - name: \"version\" + value \"v1\" \n ```" properties: headers: description: Headers specifies HTTP request header matchers. @@ -1907,12 +5989,12 @@ spec: default: Exact description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) - \n Support: Custom (RegularExpression) \n Since - RegularExpression HeaderMatchType has custom conformance, - implementations can support POSIX, PCRE or any - other dialects of regular expressions. Please - read the implementation's documentation to determine - the supported dialect." + \n Support: Implementation-specific (RegularExpression) + \n Since RegularExpression HeaderMatchType has + implementation-specific conformance, implementations + can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's + documentation to determine the supported dialect." enum: - Exact - RegularExpression @@ -1959,7 +6041,7 @@ spec: default: PathPrefix description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) - \n Support: Custom (RegularExpression)" + \n Support: Implementation-specific (RegularExpression)" enum: - Exact - PathPrefix @@ -1971,30 +6053,93 @@ spec: maxLength: 1024 type: string type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' queryParams: - description: QueryParams specifies HTTP query parameter + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query - parameters to select the route. + parameters to select the route. \n Support: Extended" items: description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. properties: name: - description: Name is the name of the HTTP query + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). + \n If multiple entries specify equivalent query + param names, only the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST + be ignored. \n If a query param is repeated in + an HTTP request, the behavior is purposely left + undefined, since different data planes have different + capabilities. However, it is *recommended* that + implementations should match against the first + value of the param if the data plane supports + it, as this behavior is expected in other load + balancing contexts outside of the Gateway API. + \n Users SHOULD NOT route traffic based on repeated + query params to guard themselves against potential + differences in the implementations." maxLength: 256 minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string type: default: Exact description: "Type specifies how to match against the value of the query parameter. \n Support: - Extended (Exact) \n Support: Custom (RegularExpression) - \n Since RegularExpression QueryParamMatchType - has custom conformance, implementations can support - POSIX, PCRE or any other dialects of regular expressions. + Extended (Exact) \n Support: Implementation-specific + (RegularExpression) \n Since RegularExpression + QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, + PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." enum: @@ -2019,7 +6164,89 @@ spec: type: object maxItems: 8 type: array + timeouts: + description: "Timeouts defines the timeouts that can be configured + for an HTTP request. \n Support: Extended \n " + properties: + backendRequest: + description: "BackendRequest specifies a timeout for an + individual request from the gateway to a backend. This + covers the time from when the request first starts being + sent from the gateway to when the full response has been + received from the backend. \n An entire client HTTP transaction + with a gateway, covered by the Request timeout, may result + in more than one call from the gateway to the destination + backend, for example, if automatic retries are supported. + \n Because the Request timeout encompasses the BackendRequest + timeout, the value of BackendRequest must be <= the value + of Request timeout. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: "Request specifies the maximum duration for + a gateway to respond to an HTTP request. If the gateway + has not been able to respond before this deadline is met, + the gateway MUST return a timeout error. \n For example, + setting the `rules.timeouts.request` field to the value + `10s` in an `HTTPRoute` will cause a timeout if a client + request is taking longer than 10 seconds to complete. + \n This timeout is intended to cover as close to the whole + request-response transaction as possible although an implementation + MAY choose to start the timeout after the entire request + stream has been received instead of immediately after + the transaction is initiated by the client. \n When this + field is unspecified, request timeout behavior is implementation-specific. + \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' maxItems: 16 type: array type: object @@ -2058,20 +6285,20 @@ spec: condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does - not support. * The Route is in a namespace the the controller + not support. * The Route is in a namespace the controller does not have access to." items: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + .status.conditions. For example, \n type FooStatus struct{ + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: \"Available\", \"Progressing\", + and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields + }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition @@ -2143,7 +6370,11 @@ spec: with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid - Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names)." + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ @@ -2154,15 +6385,21 @@ spec: properties: group: default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: - Core" + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Gateway - description: "Kind is kind of the referent. \n Support: - Core (Gateway) Support: Custom (Other Resources)" + description: "Kind is kind of the referent. \n There are + two kinds of parent resources with \"Core\" support: \n + * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services + only) \n Support for other resources is Implementation-Specific." maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -2175,29 +6412,2550 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. - When unspecified (or empty string), this refers to the - local namespace of the Route. \n Support: Core" + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n ParentRefs from a Route to a Service in + the same namespace are \"producer\" routes, which apply + default routing rules to inbound connections from any + namespace to the Service. \n ParentRefs from a Route to + a Service in a different namespace are \"consumer\" routes, + and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for + which the intended destination of the connections are + a Service targeted as a ParentRef of the Route. \n Support: + Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n When the parent resource is + a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are + specified, the name and port of the selected port must + match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer sectionName: description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener - Name \n Implementations MAY choose to support attaching - Routes to other resources. If that is the case, they MUST - clearly document how SectionName is interpreted. \n When - unspecified (empty string), this will reference the entire - resource. For the purpose of status, an attachment is - considered successful if at least one section in the parent - resource accepts it. For example, Gateway listeners can - restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept - attachment from the referencing Route, the Route MUST - be considered successfully attached. If no Gateway listeners - accept attachment from this Route, the Route MUST be considered - detached from the Gateway. \n Support: Core" + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. * Service: Port Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match + both specified values. Note that attaching Routes to Services + as Parents is part of experimental Mesh support and is + not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes + the capability to match requests by hostname, path, header, or query param. + Filters can be used to specify additional processing steps. Backends specify + where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames that should match + against the HTTP Host header to select a HTTPRoute used to process + the request. Implementations MUST ignore any port value specified + in the HTTP Host header while performing a match and (absent of + any applicable header modification configuration) MUST forward this + header unmodified to the backend. \n Valid values for Hostnames + are determined by RFC 1123 definition of a hostname with 2 notable + exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed + with a wildcard label (`*.`). The wildcard label must appear by + itself as the first label. \n If a hostname is specified by both + the Listener and HTTPRoute, there must be at least one intersecting + hostname for the HTTPRoute to be attached to the Listener. For example: + \n * A Listener with `test.example.com` as the hostname matches + HTTPRoutes that have either not specified any hostnames, or have + specified at least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at + least one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` + would all match. On the other hand, `example.com` and `test.example.net` + would not match. \n Hostnames that are prefixed with a wildcard + label (`*.`) are interpreted as a suffix match. That means that + a match for `*.example.com` would match both `test.example.com`, + and `foo.test.example.com`, but not `example.com`. \n If both the + Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames + that do not match the Listener hostname MUST be ignored. For example, + if a Listener specified `*.example.com`, and the HTTPRoute specified + `test.example.com` and `test.example.net`, `test.example.net` must + not be considered for a match. \n If both the Listener and HTTPRoute + have specified hostnames, and none match with the criteria above, + then the HTTPRoute is not accepted. The implementation must raise + an 'Accepted' Condition with a status of `False` in the corresponding + RouteParentStatus. \n In the event that multiple HTTPRoutes specify + intersecting hostnames (e.g. overlapping wildcard matching and exact + matching hostnames), precedence must be given to rules from the + HTTPRoute with the largest number of: \n * Characters in a matching + non-wildcard hostname. * Characters in a matching hostname. \n If + ties exist across multiple Routes, the matching precedence rules + for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network + host. This matches the RFC 1123 definition of a hostname with + 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname + may be prefixed with a wildcard label (`*.`). The wildcard label + must appear by itself as the first label. \n Hostname can be \"precise\" + which is a domain name without the terminating dot of a network + host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain + name prefixed with a single wildcard label (e.g. `*.example.com`). + \n Note that as per RFC1035 and RFC1123, a *label* must consist + of lower case alphanumeric characters or '-', and must start and + end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) + that a Route wants to be attached to. Note that the referenced parent + resource needs to allow this for the attachment to be complete. + For Gateways, that means the Gateway needs to allow attachment from + Routes of this kind and namespace. For Services, that means the + Service must either be in the same namespace for a \"producer\" + route, or the mesh implementation must support and allow \"consumer\" + routes for the referenced Service. ReferenceGrant is not applicable + for governing ParentRefs to Services - it is not possible to create + a \"producer\" route for a Service in a different namespace from + the Route. \n There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services only) This + API may be extended in the future to support additional kinds of + parent resources. \n ParentRefs must be _distinct_. This means either + that: \n * They select different objects. If this is the case, + then parentRef entries are distinct. In terms of fields, this means + that the multi-part key defined by `group`, `kind`, `namespace`, + and `name` must be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field + used, each ParentRef that selects the same object must set the same + set of optional fields to different values. If one ParentRef sets + a combination of optional fields, all must set the same combination. + \n Some examples: \n * If one ParentRef sets `sectionName`, all + ParentRefs referencing the same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. * If one ParentRef sets `sectionName` + and `port`, all ParentRefs referencing the same object must also + set `sectionName` and `port`. \n It is possible to separately reference + multiple distinct objects that may be collapsed by an implementation. + For example, some implementations may choose to merge compatible + Gateway Listeners together. If that is the case, the list of routes + attached to those resources should also be merged. \n Note that + for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For + example, Gateway has the AllowedRoutes field, and ReferenceGrant + provides a generic way to enable other kinds of cross-namespace + reference. \n ParentRefs from a Route to a Service in the same + namespace are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. \n ParentRefs + from a Route to a Service in a different namespace are \"consumer\" + routes, and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for which the + intended destination of the connections are a Service targeted as + a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service + (Mesh conformance profile, experimental, ClusterIP Services only) + \n This API may be extended in the future to support additional + kinds of parent resources. \n The API object must be valid in + the cluster; the Group and Kind must be registered in the cluster + for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: + Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two + kinds of parent resources with \"Core\" support: \n * Gateway + (Gateway conformance profile) * Service (Mesh conformance + profile, experimental, ClusterIP Services only) \n Support + for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n ParentRefs from a Route to a Service in the same namespace + are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. + \n ParentRefs from a Route to a Service in a different namespace + are \"consumer\" routes, and these routing rules are only + applied to outbound connections originating from the same + namespace as the Route, for which the intended destination + of the connections are a Service targeted as a ParentRef of + the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n When the parent resource + is a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified + values. \n Implementations MAY choose to support other parent + resources. Implementations supporting other types of parent + resources MUST clearly document how/if Port is interpreted. + \n For the purpose of status, an attachment is considered + successful as long as the parent resource accepts it partially. + For example, Gateway listeners can restrict which Routes can + attach to them by Route kind, namespace, or hostname. If 1 + of 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. \n + Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. * Service: Port Name. When both Port (experimental) + and SectionName are specified, the name and port of the selected + listener must match both specified values. Note that attaching + Routes to Services as Parents is part of experimental Mesh + support and is not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this will + reference the entire resource. For the purpose of status, + an attachment is considered successful if at least one section + in the parent resource accepts it. For example, Gateway listeners + can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept + attachment from the referencing Route, the Route MUST be considered + successfully attached. If no Gateway listeners accept attachment + from this Route, the Route MUST be considered detached from + the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP + request based on conditions (matches), processing it (filters), + and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching + requests should be sent. \n Failure behavior here depends + on how many BackendRefs are specified and how many are invalid. + \n If *all* entries in BackendRefs are invalid, and there + are also no filters specified in this route rule, *all* traffic + which matches this rule MUST receive a 500 status code. \n + See the HTTPBackendRef definition for the rules about what + makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef + is invalid, 500 status codes MUST be returned for requests + that would have otherwise been routed to an invalid backend. + If multiple backends are specified, and some are invalid, + the proportion of requests that would otherwise have been + routed to an invalid backend MUST receive a 500 status code. + \n For example, if two backends are specified with equal weights, + and one is invalid, 50 percent of traffic must receive a 500. + Implementations may choose how that 50 percent is determined. + \n Support: Core for Kubernetes Service \n Support: Extended + for Kubernetes ServiceImport \n Support: Implementation-specific + for any other resource \n Support for weight: Core" + items: + description: "HTTPBackendRef defines how a HTTPRoute forwards + a HTTP request. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. \n + \n When the BackendRef points to a Kubernetes Service, implementations + SHOULD honor the appProtocol field if it is set for the + target Service Port. \n Implementations supporting appProtocol + SHOULD recognize the Kubernetes Standard Application Protocols + defined in KEP-3726. \n If a Service appProtocol isn't specified, + an implementation MAY infer the backend protocol through + its own means. Implementations MAY infer the protocol from + the Route type referring to the backend Service. \n If a + Route is not able to send traffic to the backend using the + specified protocol then the backend is considered invalid. + Implementations MUST set the \"ResolvedRefs\" condition + to \"False\" with the \"UnsupportedProtocol\" reason. \n + " + properties: + filters: + description: "Filters defined at this level should be + executed if and only if the request is being forwarded + to the backend defined here. \n Support: Implementation-specific + (For broader support of filters, use the Filters field + in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps + that must be completed during the request or response + lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway + implementations. Some examples include request or + response modification, implementing authentication + strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type + of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n This filter can be used multiple times + within the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API + group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema + for a filter that modifies request headers. \n + Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. \n Input: GET /foo HTTP/1.1 + my-header: foo \n Config: add: - name: \"my-header\" + value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 + my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo + my-header2: bar my-header3: baz \n Config: + remove: [\"my-header1\", \"my-header3\"] \n + Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with + the given header (name, value) before the + action. \n Input: GET /foo HTTP/1.1 my-header: + foo \n Config: set: - name: \"my-header\" + value: \"bar\" \n Output: GET /foo HTTP/1.1 + my-header: bar" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for + a filter that mirrors requests. Requests are sent + to the specified destination, but responses from + that destination are ignored. \n This filter can + be used multiple times within the same rule. Note + that not all implementations will be able to support + mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource + where mirrored requests are sent. \n Mirrored + requests must be sent only to a single destination + endpoint within this BackendRef, irrespective + of how many endpoints are present within this + BackendRef. \n If the referent cannot be found, + this BackendRef is invalid and must be dropped + from the Gateway. The controller must ensure + the \"ResolvedRefs\" condition on the Route + status is set to `status: False` and not configure + this backend in the underlying implementation. + \n If there is a cross-namespace reference + to an *existing* object that is not allowed + by a ReferenceGrant, the controller must ensure + the \"ResolvedRefs\" condition on the Route + is set to `status: False`, with the \"RefNotPermitted\" + reason and not configure this backend in the + underlying implementation. \n In either error + case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about + the problem. \n Support: Extended for Kubernetes + Service \n Support: Implementation-specific + for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. + For example, "gateway.networking.k8s.io". + When unspecified or empty string, core + API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource + kind of the referent. For example \"Service\". + \n Defaults to \"Service\" when not specified. + \n ExternalName services can refer to + CNAME DNS records that may live outside + of the cluster and as such are difficult + to reason about in terms of conformance. + They also may not be safe to forward to + (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with + a type other than ExternalName) \n Support: + Implementation-specific (Services with + type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace + of the backend. When unspecified, the + local namespace is inferred. \n Note that + when a namespace different than the local + namespace is specified, a ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept + the reference. See the ReferenceGrant + documentation for details. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination + port number to use for this resource. + Port is required when the referent is + a Kubernetes Service. In this case, the + port number is the service port number, + not the target port. For other resources, + destination port might be derived from + the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for + a filter that responds to the request with an + HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be + used in the value of the `Location` header + in the response. When empty, the hostname + in the `Host` header of the request is used. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to + modify the path of the incoming request. The + modified path is then used to construct the + `Location` header. When empty, the request + path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the + value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies + the value with which to replace the prefix + match of a request during a rewrite or + redirect. For example, a request to \"/foo/bar\" + with a prefix match of \"/foo\" and a + ReplacePrefixMatch of \"/xyz\" would be + modified to \"/xyz/bar\". \n Note that + this matches the behavior of the PathPrefix + match type. This matches full path elements. + A path element refers to the list of labels + in the path split by the `/` separator. + When specified, a trailing `/` is ignored. + For example, the paths `/abc`, `/abc/`, + and `/abc/def` would all match the prefix + `/abc`, but the path `/abcd` would not. + \n ReplacePrefixMatch is only compatible + with a `PathPrefix` HTTPRouteMatch. Using + any other HTTPRouteMatch type on the same + HTTPRouteRule will result in the implementation + setting the Accepted Condition for the + Route to `status: False`. \n Request Path + | Prefix Match | Replace Prefix | Modified + Path -------------|--------------|----------------|---------- + /foo/bar | /foo | /xyz | + /xyz/bar /foo/bar | /foo | + /xyz/ | /xyz/bar /foo/bar | + /foo/ | /xyz | /xyz/bar + /foo/bar | /foo/ | /xyz/ | + /xyz/bar /foo | /foo | + /xyz | /xyz /foo/ | /foo + \ | /xyz | /xyz/ /foo/bar + \ | /foo | | + /bar /foo/ | /foo | | / /foo | /foo | + | / /foo/ | /foo + \ | / | / /foo | + /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path + modifier. Additional types may be added + in a future release of the API. \n Note + that values may be added to this enum, + implementations must ensure that unknown + values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the + Route to `status: False`, with a Reason + of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in + the value of the `Location` header in the + response. \n If no port is specified, the + redirect port MUST be derived using the following + rules: \n * If redirect scheme is not-empty, + the redirect port MUST be the well-known port + associated with the redirect scheme. Specifically + \"http\" to port 80 and \"https\" to port + 443. If the redirect scheme does not have + a well-known port, the listener port of the + Gateway SHOULD be used. * If redirect scheme + is empty, the redirect port MUST be the Gateway + Listener port. \n Implementations SHOULD NOT + add the port number in the 'Location' header + in the following cases: \n * A Location header + that will use HTTP (whether that is determined + via the Listener protocol or the Scheme field) + _and_ use port 80. * A Location header that + will use HTTPS (whether that is determined + via the Listener protocol or the Scheme field) + _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used + in the value of the `Location` header in the + response. When empty, the scheme of the request + is used. \n Scheme redirects can affect the + port of the redirect, for more information, + refer to the documentation for the port field + of this filter. \n Note that values may be + added to this enum, implementations must ensure + that unknown values will not cause a crash. + \n Unknown values here must result in the + implementation setting the Accepted Condition + for the Route to `status: False`, with a Reason + of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status + code to be used in response. \n Note that + values may be added to this enum, implementations + must ensure that unknown values will not cause + a crash. \n Unknown values here must result + in the implementation setting the Accepted + Condition for the Route to `status: False`, + with a Reason of `UnsupportedValue`. \n Support: + Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema + for a filter that modifies response headers. \n + Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It + appends to any existing values associated + with the header name. \n Input: GET /foo HTTP/1.1 + my-header: foo \n Config: add: - name: \"my-header\" + value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 + my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from + the HTTP request before the action. The value + of Remove is a list of HTTP header names. + Note that the header names are case-insensitive + (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo + my-header2: bar my-header3: baz \n Config: + remove: [\"my-header1\", \"my-header3\"] \n + Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with + the given header (name, value) before the + action. \n Input: GET /foo HTTP/1.1 my-header: + foo \n Config: set: - name: \"my-header\" + value: \"bar\" \n Output: GET /foo HTTP/1.1 + my-header: bar" + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: "Name is the name of the + HTTP Header to be matched. Name matching + MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an + equivalent name MUST be considered for + a match. Subsequent entries with an + equivalent header name MUST be ignored. + Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter + to apply. As with other API fields, types are + classified into three conformance levels: \n - + Core: Filter types and their corresponding configuration + defined by \"Support: Core\" in this package, + e.g. \"RequestHeaderModifier\". All implementations + must support core filters. \n - Extended: Filter + types and their corresponding configuration defined + by \"Support: Extended\" in this package, e.g. + \"RequestMirror\". Implementers are encouraged + to support extended filters. \n - Implementation-specific: + Filters that are defined and supported by specific + vendors. In the future, filters showing convergence + in behavior across multiple implementations will + be considered for inclusion in extended or core + conformance levels. Filter-specific configuration + for such filters is specified using the ExtensionRef + field. `Type` should be set to \"ExtensionRef\" + for custom filters. \n Implementers are encouraged + to define custom implementation types to extend + the core API with implementation-specific behavior. + \n If a reference to a custom filter type cannot + be resolved, the filter MUST NOT be skipped. Instead, + requests that would have been processed by that + filter MUST receive a HTTP error response. \n + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause + a crash. \n Unknown values here must result in + the implementation setting the Accepted Condition + for the Route to `status: False`, with a Reason + of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a + filter that modifies a request during forwarding. + \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used + to replace the Host header value during forwarding. + \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n + Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the + value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies + the value with which to replace the prefix + match of a request during a rewrite or + redirect. For example, a request to \"/foo/bar\" + with a prefix match of \"/foo\" and a + ReplacePrefixMatch of \"/xyz\" would be + modified to \"/xyz/bar\". \n Note that + this matches the behavior of the PathPrefix + match type. This matches full path elements. + A path element refers to the list of labels + in the path split by the `/` separator. + When specified, a trailing `/` is ignored. + For example, the paths `/abc`, `/abc/`, + and `/abc/def` would all match the prefix + `/abc`, but the path `/abcd` would not. + \n ReplacePrefixMatch is only compatible + with a `PathPrefix` HTTPRouteMatch. Using + any other HTTPRouteMatch type on the same + HTTPRouteRule will result in the implementation + setting the Accepted Condition for the + Route to `status: False`. \n Request Path + | Prefix Match | Replace Prefix | Modified + Path -------------|--------------|----------------|---------- + /foo/bar | /foo | /xyz | + /xyz/bar /foo/bar | /foo | + /xyz/ | /xyz/bar /foo/bar | + /foo/ | /xyz | /xyz/bar + /foo/bar | /foo/ | /xyz/ | + /xyz/bar /foo | /foo | + /xyz | /xyz /foo/ | /foo + \ | /xyz | /xyz/ /foo/bar + \ | /foo | | + /bar /foo/ | /foo | | / /foo | /foo | + | / /foo/ | /foo + \ | / | / /foo | + /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path + modifier. Additional types may be added + in a future release of the API. \n Note + that values may be added to this enum, + implementations must ensure that unknown + values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the + Route to `status: False`, with a Reason + of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of + the referent. For example \"Service\". \n Defaults to + \"Service\" when not specified. \n ExternalName services + can refer to CNAME DNS records that may live outside + of the cluster and as such are difficult to reason about + in terms of conformance. They also may not be safe to + forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName Services. + \n Support: Core (Services with a type other than ExternalName) + \n Support: Implementation-specific (Services with type + ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. + When unspecified, the local namespace is inferred. \n + Note that when a namespace different than the local + namespace is specified, a ReferenceGrant object is required + in the referent namespace to allow that namespace's + owner to accept the reference. See the ReferenceGrant + documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number + to use for this resource. Port is required when the + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1. \n Support for this field varies based + on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to + requests that match this rule. \n The effects of ordering + of multiple behaviors are currently unspecified. This can + change in the future based on feedback during the alpha stage. + \n Conformance-levels at this level are defined based on the + type of filter: \n - ALL core filters MUST be supported by + all implementations. - Implementers are encouraged to support + extended filters. - Implementation-specific custom filters + have no API guarantees across implementations. \n Specifying + the same filter multiple times is not supported unless explicitly + indicated in the filter. \n All filters are expected to be + compatible with each other except for the URLRewrite and RequestRedirect + filters, which may not be combined. If an implementation can + not support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to + be set to status `False`, implementations may use the `IncompatibleFilters` + reason to specify this configuration error. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that + must be completed during the request or response lifecycle. + HTTPRouteFilters are meant as an extension point to express + processing that may be done in Gateway implementations. + Some examples include request or response modification, + implementing authentication strategies, rate-limiting, and + traffic shaping. API guarantee/conformance is defined based + on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific + extension to the \"filter\" behavior. For example, + resource \"myroutefilter\" in group \"networking.example.net\"). + ExtensionRef MUST NOT be used for core and extended + filters. \n This filter can be used multiple times within + the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For + example, "gateway.networking.k8s.io". When unspecified + or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for + a filter that modifies request headers. \n Support: + Core" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter + that mirrors requests. Requests are sent to the specified + destination, but responses from that destination are + ignored. \n This filter can be used multiple times within + the same rule. Note that not all implementations will + be able to support mirroring to multiple backends. \n + Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where + mirrored requests are sent. \n Mirrored requests + must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many + endpoints are present within this BackendRef. \n + If the referent cannot be found, this BackendRef + is invalid and must be dropped from the Gateway. + The controller must ensure the \"ResolvedRefs\" + condition on the Route status is set to `status: + False` and not configure this backend in the underlying + implementation. \n If there is a cross-namespace + reference to an *existing* object that is not allowed + by a ReferenceGrant, the controller must ensure + the \"ResolvedRefs\" condition on the Route is + set to `status: False`, with the \"RefNotPermitted\" + reason and not configure this backend in the underlying + implementation. \n In either error case, the Message + of the `ResolvedRefs` Condition should be used to + provide more detail about the problem. \n Support: + Extended for Kubernetes Service \n Support: Implementation-specific + for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. + For example, "gateway.networking.k8s.io". When + unspecified or empty string, core API group + is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource + kind of the referent. For example \"Service\". + \n Defaults to \"Service\" when not specified. + \n ExternalName services can refer to CNAME + DNS records that may live outside of the cluster + and as such are difficult to reason about in + terms of conformance. They also may not be safe + to forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName + Services. \n Support: Core (Services with a + type other than ExternalName) \n Support: Implementation-specific + (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the + backend. When unspecified, the local namespace + is inferred. \n Note that when a namespace different + than the local namespace is specified, a ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept the + reference. See the ReferenceGrant documentation + for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port + number to use for this resource. Port is required + when the referent is a Kubernetes Service. In + this case, the port number is the service port + number, not the target port. For other resources, + destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter + that responds to the request with an HTTP redirection. + \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used + in the value of the `Location` header in the response. + When empty, the hostname in the `Host` header of + the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify + the path of the incoming request. The modified path + is then used to construct the `Location` header. + When empty, the request path is used as-is. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" and a ReplacePrefixMatch of + \"/xyz\" would be modified to \"/xyz/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not. \n ReplacePrefixMatch is + only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same + HTTPRouteRule will result in the implementation + setting the Accepted Condition for the Route + to `status: False`. \n Request Path | Prefix + Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- + /foo/bar | /foo | /xyz | + /xyz/bar /foo/bar | /foo | /xyz/ + \ | /xyz/bar /foo/bar | /foo/ | + /xyz | /xyz/bar /foo/bar | /foo/ + \ | /xyz/ | /xyz/bar /foo | + /foo | /xyz | /xyz /foo/ | + /foo | /xyz | /xyz/ /foo/bar + \ | /foo | | /bar + /foo/ | /foo | + | / /foo | /foo | + | / /foo/ | /foo | / | + / /foo | /foo | / | + /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in the value + of the `Location` header in the response. \n If + no port is specified, the redirect port MUST be + derived using the following rules: \n * If redirect + scheme is not-empty, the redirect port MUST be the + well-known port associated with the redirect scheme. + Specifically \"http\" to port 80 and \"https\" to + port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway + SHOULD be used. * If redirect scheme is empty, the + redirect port MUST be the Gateway Listener port. + \n Implementations SHOULD NOT add the port number + in the 'Location' header in the following cases: + \n * A Location header that will use HTTP (whether + that is determined via the Listener protocol or + the Scheme field) _and_ use port 80. * A Location + header that will use HTTPS (whether that is determined + via the Listener protocol or the Scheme field) _and_ + use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the + value of the `Location` header in the response. + When empty, the scheme of the request is used. \n + Scheme redirects can affect the port of the redirect, + for more information, refer to the documentation + for the port field of this filter. \n Note that + values may be added to this enum, implementations + must ensure that unknown values will not cause a + crash. \n Unknown values here must result in the + implementation setting the Accepted Condition for + the Route to `status: False`, with a Reason of `UnsupportedValue`. + \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to + be used in response. \n Note that values may be + added to this enum, implementations must ensure + that unknown values will not cause a crash. \n Unknown + values here must result in the implementation setting + the Accepted Condition for the Route to `status: + False`, with a Reason of `UnsupportedValue`. \n + Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema + for a filter that modifies response headers. \n Support: + Extended" + properties: + add: + description: "Add adds the given header(s) (name, + value) to the request before the action. It appends + to any existing values associated with the header + name. \n Input: GET /foo HTTP/1.1 my-header: foo + \n Config: add: - name: \"my-header\" value: \"bar,baz\" + \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the + HTTP request before the action. The value of Remove + is a list of HTTP header names. Note that the header + names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: + bar my-header3: baz \n Config: remove: [\"my-header1\", + \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: + bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the + given header (name, value) before the action. \n + Input: GET /foo HTTP/1.1 my-header: foo \n Config: + set: - name: \"my-header\" value: \"bar\" \n Output: + GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case + insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST + be ignored. Due to the case-insensitivity + of header names, \"foo\" and \"Foo\" are considered + equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. + As with other API fields, types are classified into + three conformance levels: \n - Core: Filter types and + their corresponding configuration defined by \"Support: + Core\" in this package, e.g. \"RequestHeaderModifier\". + All implementations must support core filters. \n - + Extended: Filter types and their corresponding configuration + defined by \"Support: Extended\" in this package, e.g. + \"RequestMirror\". Implementers are encouraged to support + extended filters. \n - Implementation-specific: Filters + that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior + across multiple implementations will be considered for + inclusion in extended or core conformance levels. Filter-specific + configuration for such filters is specified using the + ExtensionRef field. `Type` should be set to \"ExtensionRef\" + for custom filters. \n Implementers are encouraged to + define custom implementation types to extend the core + API with implementation-specific behavior. \n If a reference + to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have + been processed by that filter MUST receive a HTTP error + response. \n Note that values may be added to this enum, + implementations must ensure that unknown values will + not cause a crash. \n Unknown values here must result + in the implementation setting the Accepted Condition + for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter + that modifies a request during forwarding. \n Support: + Extended" + properties: + hostname: + description: "Hostname is the value to be used to + replace the Host header value during forwarding. + \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: + Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value + with which to replace the full path of a request + during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the + value with which to replace the prefix match + of a request during a rewrite or redirect. For + example, a request to \"/foo/bar\" with a prefix + match of \"/foo\" and a ReplacePrefixMatch of + \"/xyz\" would be modified to \"/xyz/bar\". + \n Note that this matches the behavior of the + PathPrefix match type. This matches full path + elements. A path element refers to the list + of labels in the path split by the `/` separator. + When specified, a trailing `/` is ignored. For + example, the paths `/abc`, `/abc/`, and `/abc/def` + would all match the prefix `/abc`, but the path + `/abcd` would not. \n ReplacePrefixMatch is + only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same + HTTPRouteRule will result in the implementation + setting the Accepted Condition for the Route + to `status: False`. \n Request Path | Prefix + Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- + /foo/bar | /foo | /xyz | + /xyz/bar /foo/bar | /foo | /xyz/ + \ | /xyz/bar /foo/bar | /foo/ | + /xyz | /xyz/bar /foo/bar | /foo/ + \ | /xyz/ | /xyz/bar /foo | + /foo | /xyz | /xyz /foo/ | + /foo | /xyz | /xyz/ /foo/bar + \ | /foo | | /bar + /foo/ | /foo | + | / /foo | /foo | + | / /foo/ | /foo | / | + / /foo | /foo | / | + /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. + Additional types may be added in a future release + of the API. \n Note that values may be added + to this enum, implementations must ensure that + unknown values will not cause a crash. \n Unknown + values here must result in the implementation + setting the Accepted Condition for the Route + to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the + rule against incoming HTTP requests. Each match is independent, + i.e. this rule will be matched if **any** one of the matches + is satisfied. \n For example, take the following matches configuration: + \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" + value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request + to match against this rule, a request must satisfy EITHER + of the two conditions: \n - path prefixed with `/foo` AND + contains the header `version: v2` - path prefix of `/v2/foo` + \n See the documentation for HTTPRouteMatch on how to specify + multiple match conditions that should be ANDed together. \n + If no matches are specified, the default is a prefix path + match on \"/\", which has the effect of matching every HTTP + request. \n Proxy or Load Balancer routing configuration generated + from HTTPRoutes MUST prioritize matches based on the following + criteria, continuing on ties. Across all rules specified on + applicable Routes, precedence must be given to the match having: + \n * \"Exact\" path match. * \"Prefix\" path match with largest + number of characters. * Method match. * Largest number of + header matches. * Largest number of query param matches. \n + Note: The precedence of RegularExpression path matches are + implementation-specific. \n If ties still exist across multiple + Routes, matching precedence MUST be determined in order of + the following criteria, continuing on ties: \n * The oldest + Route based on creation timestamp. * The Route appearing first + in alphabetical order by \"{namespace}/{name}\". \n If ties + still exist within an HTTPRoute, matching precedence MUST + be granted to the FIRST matching rule (in list order) with + a match meeting the above criteria. \n When no rules matching + a request have been successfully attached to the parent a + request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given action. Multiple match types are + ANDed together, i.e. the match will evaluate to true only + if all conditions are satisfied. \n For example, the match + below will match a HTTP request only if its path starts + with `/foo` AND it contains the `version: v1` header: \n + ``` match: \n path: value: \"/foo\" headers: - name: \"version\" + value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. + Multiple match values are ANDed together, meaning, a + request must match all the specified headers to select + the route. + items: + description: HTTPHeaderMatch describes how to select + a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case insensitive. + (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent header + names, only the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be + ignored. Due to the case-insensitivity of header + names, \"foo\" and \"Foo\" are considered equivalent. + \n When a header is repeated in an HTTP request, + it is implementation-specific behavior as to how + this is represented. Generally, proxies should + follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 + regarding processing a repeated header, with special + handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against + the value of the header. \n Support: Core (Exact) + \n Support: Implementation-specific (RegularExpression) + \n Since RegularExpression HeaderMatchType has + implementation-specific conformance, implementations + can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's + documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When + specified, this route will be matched only if the request + has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. + If this field is not specified, a default prefix match + on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against + the path Value. \n Support: Core (Exact, PathPrefix) + \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: "QueryParams specifies HTTP query parameter + matchers. Multiple match values are ANDed together, + meaning, a request must match all the specified query + parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select + a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query + param to be matched. This must be an exact string + match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). + \n If multiple entries specify equivalent query + param names, only the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST + be ignored. \n If a query param is repeated in + an HTTP request, the behavior is purposely left + undefined, since different data planes have different + capabilities. However, it is *recommended* that + implementations should match against the first + value of the param if the data plane supports + it, as this behavior is expected in other load + balancing contexts outside of the Gateway API. + \n Users SHOULD NOT route traffic based on repeated + query params to guard themselves against potential + differences in the implementations." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against + the value of the query parameter. \n Support: + Extended (Exact) \n Support: Implementation-specific + (RegularExpression) \n Since RegularExpression + QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, + PCRE or any other dialects of regular expressions. + Please read the implementation's documentation + to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + timeouts: + description: "Timeouts defines the timeouts that can be configured + for an HTTP request. \n Support: Extended \n " + properties: + backendRequest: + description: "BackendRequest specifies a timeout for an + individual request from the gateway to a backend. This + covers the time from when the request first starts being + sent from the gateway to when the full response has been + received from the backend. \n An entire client HTTP transaction + with a gateway, covered by the Request timeout, may result + in more than one call from the gateway to the destination + backend, for example, if automatic retries are supported. + \n Because the Request timeout encompasses the BackendRequest + timeout, the value of BackendRequest must be <= the value + of Request timeout. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: "Request specifies the maximum duration for + a gateway to respond to an HTTP request. If the gateway + has not been able to respond before this deadline is met, + the gateway MUST return a timeout error. \n For example, + setting the `rules.timeouts.request` field to the value + `10s` in an `HTTPRoute` will cause a timeout if a client + request is taking longer than 10 seconds to complete. + \n This timeout is intended to cover as close to the whole + request-response transaction as possible although an implementation + MAY choose to start the timeout after the entire request + stream has been received instead of immediately after + the transaction is initiated by the client. \n When this + field is unspecified, request timeout behavior is implementation-specific. + \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) + that are associated with the route, and the status of the route + with respect to each parent. When this route attaches to a parent, + the controller that manages the parent must add an entry to this + list when the controller first sees the route and should update + the entry as appropriate when the route or gateway is modified. + \n Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this + API can only populate Route status for the Gateways/parent resources + they are responsible for. \n A maximum of 32 Gateways will be represented + in this list. An empty list means the route has not been attached + to any Gateway." + items: + description: RouteParentStatus describes the status of a route with + respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with + respect to the Gateway. Note that the route's availability + is also subject to the Gateway's own status conditions and + listener status. \n If the Route's ParentRef specifies an + existing Gateway that supports Routes of this kind AND that + Gateway's controller has sufficient access, then that Gateway's + controller MUST set the \"Accepted\" condition on the Route, + to indicate whether the route has been accepted or rejected + by the Gateway, and why. \n A Route MUST be considered \"Accepted\" + if at least one of the Route's rules is implemented by the + Gateway. \n There are a number of cases where the \"Accepted\" + condition may not be set due to lack of controller visibility, + that includes when: \n * The Route refers to a non-existent + parent. * The Route is of a type that the controller does + not support. * The Route is in a namespace the controller + does not have access to." + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, \n type FooStatus struct{ + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: \"Available\", \"Progressing\", + and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields + }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates + the name of the controller that wrote this status. This corresponds + with the controllerName field on GatewayClass. \n Example: + \"example.net/gateway-controller\". \n The format of this + field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec + that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are + two kinds of parent resources with \"Core\" support: \n + * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services + only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n ParentRefs from a Route to a Service in + the same namespace are \"producer\" routes, which apply + default routing rules to inbound connections from any + namespace to the Service. \n ParentRefs from a Route to + a Service in a different namespace are \"consumer\" routes, + and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for + which the intended destination of the connections are + a Service targeted as a ParentRef of the Route. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n When the parent resource is + a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are + specified, the name and port of the selected port must + match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within + the target resource. In the following resources, SectionName + is interpreted as the following: \n * Gateway: Listener + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. * Service: Port Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match + both specified values. Note that attaching Routes to Services + as Parents is part of experimental Mesh support and is + not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -2225,15 +8983,302 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] - + conditions: null + storedVersions: null --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: The v1alpha2 version of ReferenceGrant has been deprecated + and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces + that are trusted to reference the specified kinds of resources in the same + namespace as the policy. \n Each ReferenceGrant can be used to represent + a unique trust relationship. Additional Reference Grants can be used to + add to the set of trusted sources of inbound references for the namespace + they are defined within. \n A ReferenceGrant is required for all cross-namespace + references in Gateway API (with the exception of cross-namespace Route-Gateway + attachment, which is governed by the AllowedRoutes configuration on the + Gateway, and cross-namespace Service ParentRefs on a \"consumer\" mesh Route, + which defines routing rules applicable only to workloads in the Route namespace). + ReferenceGrants allowing a reference from a Route to a Service are only + applicable to BackendRefs. \n ReferenceGrant is a form of runtime verification + allowing users to assert which cross-namespace object references are permitted. + Implementations that support ReferenceGrant MUST NOT permit cross-namespace + references which have no grant, and MUST respond to the removal of a grant + by revoking the access that the grant allowed." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that + can reference the resources described in \"To\". Each entry in this + list MUST be considered to be an additional place that references + can be valid from, or to put this another way, entries MUST be combined + using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and + kinds. + properties: + group: + description: "Group is the group of the referent. When empty, + the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations + may support additional resources, the following types are + part of the \"Core\" support level for this field. \n When + used to permit a SecretObjectReference: \n * Gateway \n When + used to permit a BackendObjectReference: \n * GRPCRoute * + HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n + Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by + the resources described in \"From\". Each entry in this list MUST + be considered to be an additional place that references can be valid + to, or to put this another way, entries MUST be combined using OR. + \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as + targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, + the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations + may support additional resources, the following types are + part of the \"Core\" support level for this field: \n * Secret + when used to permit a SecretObjectReference * Service when + used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, + this policy refers to all resources of the specified Group + and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: false + subresources: {} + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces + that are trusted to reference the specified kinds of resources in the same + namespace as the policy. \n Each ReferenceGrant can be used to represent + a unique trust relationship. Additional Reference Grants can be used to + add to the set of trusted sources of inbound references for the namespace + they are defined within. \n All cross-namespace references in Gateway API + (with the exception of cross-namespace Gateway-route attachment) require + a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing + users to assert which cross-namespace object references are permitted. Implementations + that support ReferenceGrant MUST NOT permit cross-namespace references which + have no grant, and MUST respond to the removal of a grant by revoking the + access that the grant allowed." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that + can reference the resources described in \"To\". Each entry in this + list MUST be considered to be an additional place that references + can be valid from, or to put this another way, entries MUST be combined + using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and + kinds. + properties: + group: + description: "Group is the group of the referent. When empty, + the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations + may support additional resources, the following types are + part of the \"Core\" support level for this field. \n When + used to permit a SecretObjectReference: \n * Gateway \n When + used to permit a BackendObjectReference: \n * GRPCRoute * + HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n + Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by + the resources described in \"From\". Each entry in this list MUST + be considered to be an additional place that references can be valid + to, or to put this another way, entries MUST be combined using OR. + \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as + targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, + the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations + may support additional resources, the following types are + part of the \"Core\" support level for this field: \n * Secret + when used to permit a SecretObjectReference * Service when + used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, + this policy refers to all resources of the specified Group + and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: tcproutes.gateway.networking.k8s.io spec: @@ -2278,40 +9323,76 @@ spec: that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from - Routes of this kind and namespace. \n The only kind of parent resource - with \"Core\" support is Gateway. This API may be extended in the - future to support additional kinds of parent resources such as one - of the route kinds. \n It is invalid to reference an identical parent - more than once. It is valid to reference multiple distinct sections - within the same parent resource, such as 2 Listeners within a Gateway. - \n It is possible to separately reference multiple distinct objects - that may be collapsed by an implementation. For example, some implementations - may choose to merge compatible Gateway Listeners together. If that - is the case, the list of routes attached to those resources should - also be merged." + Routes of this kind and namespace. For Services, that means the + Service must either be in the same namespace for a \"producer\" + route, or the mesh implementation must support and allow \"consumer\" + routes for the referenced Service. ReferenceGrant is not applicable + for governing ParentRefs to Services - it is not possible to create + a \"producer\" route for a Service in a different namespace from + the Route. \n There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services only) This + API may be extended in the future to support additional kinds of + parent resources. \n ParentRefs must be _distinct_. This means either + that: \n * They select different objects. If this is the case, + then parentRef entries are distinct. In terms of fields, this means + that the multi-part key defined by `group`, `kind`, `namespace`, + and `name` must be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field + used, each ParentRef that selects the same object must set the same + set of optional fields to different values. If one ParentRef sets + a combination of optional fields, all must set the same combination. + \n Some examples: \n * If one ParentRef sets `sectionName`, all + ParentRefs referencing the same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. * If one ParentRef sets `sectionName` + and `port`, all ParentRefs referencing the same object must also + set `sectionName` and `port`. \n It is possible to separately reference + multiple distinct objects that may be collapsed by an implementation. + For example, some implementations may choose to merge compatible + Gateway Listeners together. If that is the case, the list of routes + attached to those resources should also be merged. \n Note that + for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For + example, Gateway has the AllowedRoutes field, and ReferenceGrant + provides a generic way to enable other kinds of cross-namespace + reference. \n ParentRefs from a Route to a Service in the same + namespace are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. \n ParentRefs + from a Route to a Service in a different namespace are \"consumer\" + routes, and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for which the + intended destination of the connections are a Service targeted as + a ParentRef of the Route. \n " items: - description: "ParentRef identifies an API object (usually a Gateway) - that can be considered a parent of this resource (usually a route). - The only kind of parent resource with \"Core\" support is Gateway. - This API may be extended in the future to support additional kinds - of parent resources, such as HTTPRoute. \n The API object must - be valid in the cluster; the Group and Kind must be registered - in the cluster for this reference to be valid. \n References to - objects with invalid Group and Kind are not valid, and must be - rejected by the implementation, with appropriate Conditions set - on the containing object." + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service + (Mesh conformance profile, experimental, ClusterIP Services only) + \n This API may be extended in the future to support additional + kinds of parent resources. \n The API object must be valid in + the cluster; the Group and Kind must be registered in the cluster + for this reference to be valid." properties: group: default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: Core" maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Gateway - description: "Kind is kind of the referent. \n Support: Core - (Gateway) Support: Custom (Other Resources)" + description: "Kind is kind of the referent. \n There are two + kinds of parent resources with \"Core\" support: \n * Gateway + (Gateway conformance profile) * Service (Mesh conformance + profile, experimental, ClusterIP Services only) \n Support + for other resources is Implementation-Specific." maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -2324,29 +9405,79 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. When - unspecified (or empty string), this refers to the local namespace - of the Route. \n Support: Core" + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n ParentRefs from a Route to a Service in the same namespace + are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. + \n ParentRefs from a Route to a Service in a different namespace + are \"consumer\" routes, and these routing rules are only + applied to outbound connections originating from the same + namespace as the Route, for which the intended destination + of the connections are a Service targeted as a ParentRef of + the Route. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string - sectionName: - description: "SectionName is the name of a section within the - target resource. In the following resources, SectionName is - interpreted as the following: \n * Gateway: Listener Name - \n Implementations MAY choose to support attaching Routes - to other resources. If that is the case, they MUST clearly - document how SectionName is interpreted. \n When unspecified - (empty string), this will reference the entire resource. For - the purpose of status, an attachment is considered successful - if at least one section in the parent resource accepts it. + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n When the parent resource + is a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified + values. \n Implementations MAY choose to support other parent + resources. Implementations supporting other types of parent + resources MUST clearly document how/if Port is interpreted. + \n For the purpose of status, an attachment is considered + successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n - Support: Core" + Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. * Service: Port Name. When both Port (experimental) + and SectionName are specified, the name and port of the selected + listener must match both specified values. Note that attaching + Routes to Services as Parents is part of experimental Mesh + support and is not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this will + reference the entire resource. For the purpose of status, + an attachment is considered successful if at least one section + in the parent resource accepts it. For example, Gateway listeners + can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept + attachment from the referencing Route, the Route MUST be considered + successfully attached. If no Gateway listeners accept attachment + from this Route, the Route MUST be considered detached from + the Gateway. \n Support: Core" maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -2356,6 +9487,29 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) rules: description: Rules are a list of TCP matchers and actions. items: @@ -2369,28 +9523,55 @@ spec: attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. - \n Support: Core for Kubernetes Service Support: Custom for - any other resource \n Support for weight: Extended" + \n Support: Core for Kubernetes Service \n Support: Extended + for Kubernetes ServiceImport \n Support: Implementation-specific + for any other resource \n Support for weight: Extended" items: description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a - namespace is specified, a ReferencePolicy object is required - in the referent namespace to allow that namespace's owner - to accept the reference. See the ReferencePolicy documentation - for details." + namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace + to allow that namespace's owner to accept the reference. + See the ReferenceGrant documentation for details. \n + \n When the BackendRef points to a Kubernetes Service, implementations + SHOULD honor the appProtocol field if it is set for the + target Service Port. \n Implementations supporting appProtocol + SHOULD recognize the Kubernetes Standard Application Protocols + defined in KEP-3726. \n If a Service appProtocol isn't specified, + an implementation MAY infer the backend protocol through + its own means. Implementations MAY infer the protocol from + the Route type referring to the backend Service. \n If a + Route is not able to send traffic to the backend using the + specified protocol then the backend is considered invalid. + Implementations MUST set the \"ResolvedRefs\" condition + to \"False\" with the \"UnsupportedProtocol\" reason. \n + \n Note that when the + BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. + See the fields where this struct is used for more information + about the exact behavior." properties: group: default: "" description: Group is the group of the referent. For example, - "networking.k8s.io". When unspecified (empty string), - core API group is inferred. + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Service - description: Kind is kind of the referent. For example - "HTTPRoute" or "Service". + description: "Kind is the Kubernetes resource kind of + the referent. For example \"Service\". \n Defaults to + \"Service\" when not specified. \n ExternalName services + can refer to CNAME DNS records that may live outside + of the cluster and as such are difficult to reason about + in terms of conformance. They also may not be safe to + forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName Services. + \n Support: Core (Services with a type other than ExternalName) + \n Support: Implementation-specific (Services with type + ExternalName)" maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -2403,11 +9584,11 @@ spec: namespace: description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n - Note that when a namespace is specified, a ReferencePolicy - object is required in the referent namespace to allow - that namespace's owner to accept the reference. See - the ReferencePolicy documentation for details. \n Support: - Core" + Note that when a namespace different than the local + namespace is specified, a ReferenceGrant object is required + in the referent namespace to allow that namespace's + owner to accept the reference. See the ReferenceGrant + documentation for details. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ @@ -2415,9 +9596,10 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required when the - referent is a Kubernetes Service. For other resources, - destination port might be derived from the referent - resource or this field. + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -2444,6 +9626,10 @@ spec: required: - name type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' maxItems: 16 minItems: 1 type: array @@ -2489,20 +9675,20 @@ spec: condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does - not support. * The Route is in a namespace the the controller + not support. * The Route is in a namespace the controller does not have access to." items: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + .status.conditions. For example, \n type FooStatus struct{ + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: \"Available\", \"Progressing\", + and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields + }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition @@ -2574,7 +9760,11 @@ spec: with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid - Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names)." + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ @@ -2585,15 +9775,21 @@ spec: properties: group: default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: - Core" + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Gateway - description: "Kind is kind of the referent. \n Support: - Core (Gateway) Support: Custom (Other Resources)" + description: "Kind is kind of the referent. \n There are + two kinds of parent resources with \"Core\" support: \n + * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services + only) \n Support for other resources is Implementation-Specific." maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -2606,29 +9802,84 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. - When unspecified (or empty string), this refers to the - local namespace of the Route. \n Support: Core" + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n ParentRefs from a Route to a Service in + the same namespace are \"producer\" routes, which apply + default routing rules to inbound connections from any + namespace to the Service. \n ParentRefs from a Route to + a Service in a different namespace are \"consumer\" routes, + and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for + which the intended destination of the connections are + a Service targeted as a ParentRef of the Route. \n Support: + Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n When the parent resource is + a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are + specified, the name and port of the selected port must + match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer sectionName: description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener - Name \n Implementations MAY choose to support attaching - Routes to other resources. If that is the case, they MUST - clearly document how SectionName is interpreted. \n When - unspecified (empty string), this will reference the entire - resource. For the purpose of status, an attachment is - considered successful if at least one section in the parent - resource accepts it. For example, Gateway listeners can - restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept - attachment from the referencing Route, the Route MUST - be considered successfully attached. If no Gateway listeners - accept attachment from this Route, the Route MUST be considered - detached from the Gateway. \n Support: Core" + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. * Service: Port Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match + both specified values. Note that attaching Routes to Services + as Parents is part of experimental Mesh support and is + not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -2656,15 +9907,16 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] - + conditions: null + storedVersions: null --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/891 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: tlsroutes.gateway.networking.k8s.io spec: @@ -2712,20 +9964,20 @@ spec: This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed in SNI names per RFC 6066. 2. A hostname may be prefixed with a wildcard label (`*.`). The - wildcard label must appear by itself as the first label. \n If - a hostname is specified by both the Listener and TLSRoute, there - must be at least one intersecting hostname for the TLSRoute to be - attached to the Listener. For example: \n * A Listener with `test.example.com` - as the hostname matches TLSRoutes that have either not specified - any hostnames, or have specified at least one of `test.example.com` + wildcard label must appear by itself as the first label. \n If a + hostname is specified by both the Listener and TLSRoute, there must + be at least one intersecting hostname for the TLSRoute to be attached + to the Listener. For example: \n * A Listener with `test.example.com` + as the hostname matches TLSRoutes that have either not specified + any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname - matches TLSRoutes that have either not specified any hostnames - or have specified at least one hostname that matches the Listener - hostname. For example, `test.example.com` and `*.example.com` - would both match. On the other hand, `example.com` and `test.example.net` - would not match. \n If both the Listener and TLSRoute have specified - hostnames, any TLSRoute hostnames that do not match the Listener - hostname MUST be ignored. For example, if a Listener specified `*.example.com`, + matches TLSRoutes that have either not specified any hostnames or + have specified at least one hostname that matches the Listener hostname. + For example, `test.example.com` and `*.example.com` would both match. + On the other hand, `example.com` and `test.example.net` would not + match. \n If both the Listener and TLSRoute have specified hostnames, + any TLSRoute hostnames that do not match the Listener hostname MUST + be ignored. For example, if a Listener specified `*.example.com`, and the TLSRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and TLSRoute have specified hostnames, and none match @@ -2736,7 +9988,7 @@ spec: description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname - may be prefixed with a wildcard label (`*.`). The wildcard label + may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain @@ -2755,40 +10007,76 @@ spec: that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from - Routes of this kind and namespace. \n The only kind of parent resource - with \"Core\" support is Gateway. This API may be extended in the - future to support additional kinds of parent resources such as one - of the route kinds. \n It is invalid to reference an identical parent - more than once. It is valid to reference multiple distinct sections - within the same parent resource, such as 2 Listeners within a Gateway. - \n It is possible to separately reference multiple distinct objects - that may be collapsed by an implementation. For example, some implementations - may choose to merge compatible Gateway Listeners together. If that - is the case, the list of routes attached to those resources should - also be merged." + Routes of this kind and namespace. For Services, that means the + Service must either be in the same namespace for a \"producer\" + route, or the mesh implementation must support and allow \"consumer\" + routes for the referenced Service. ReferenceGrant is not applicable + for governing ParentRefs to Services - it is not possible to create + a \"producer\" route for a Service in a different namespace from + the Route. \n There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services only) This + API may be extended in the future to support additional kinds of + parent resources. \n ParentRefs must be _distinct_. This means either + that: \n * They select different objects. If this is the case, + then parentRef entries are distinct. In terms of fields, this means + that the multi-part key defined by `group`, `kind`, `namespace`, + and `name` must be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field + used, each ParentRef that selects the same object must set the same + set of optional fields to different values. If one ParentRef sets + a combination of optional fields, all must set the same combination. + \n Some examples: \n * If one ParentRef sets `sectionName`, all + ParentRefs referencing the same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. * If one ParentRef sets `sectionName` + and `port`, all ParentRefs referencing the same object must also + set `sectionName` and `port`. \n It is possible to separately reference + multiple distinct objects that may be collapsed by an implementation. + For example, some implementations may choose to merge compatible + Gateway Listeners together. If that is the case, the list of routes + attached to those resources should also be merged. \n Note that + for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For + example, Gateway has the AllowedRoutes field, and ReferenceGrant + provides a generic way to enable other kinds of cross-namespace + reference. \n ParentRefs from a Route to a Service in the same + namespace are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. \n ParentRefs + from a Route to a Service in a different namespace are \"consumer\" + routes, and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for which the + intended destination of the connections are a Service targeted as + a ParentRef of the Route. \n " items: - description: "ParentRef identifies an API object (usually a Gateway) - that can be considered a parent of this resource (usually a route). - The only kind of parent resource with \"Core\" support is Gateway. - This API may be extended in the future to support additional kinds - of parent resources, such as HTTPRoute. \n The API object must - be valid in the cluster; the Group and Kind must be registered - in the cluster for this reference to be valid. \n References to - objects with invalid Group and Kind are not valid, and must be - rejected by the implementation, with appropriate Conditions set - on the containing object." + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service + (Mesh conformance profile, experimental, ClusterIP Services only) + \n This API may be extended in the future to support additional + kinds of parent resources. \n The API object must be valid in + the cluster; the Group and Kind must be registered in the cluster + for this reference to be valid." properties: group: default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: Core" maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Gateway - description: "Kind is kind of the referent. \n Support: Core - (Gateway) Support: Custom (Other Resources)" + description: "Kind is kind of the referent. \n There are two + kinds of parent resources with \"Core\" support: \n * Gateway + (Gateway conformance profile) * Service (Mesh conformance + profile, experimental, ClusterIP Services only) \n Support + for other resources is Implementation-Specific." maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -2801,29 +10089,79 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. When - unspecified (or empty string), this refers to the local namespace - of the Route. \n Support: Core" + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n ParentRefs from a Route to a Service in the same namespace + are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. + \n ParentRefs from a Route to a Service in a different namespace + are \"consumer\" routes, and these routing rules are only + applied to outbound connections originating from the same + namespace as the Route, for which the intended destination + of the connections are a Service targeted as a ParentRef of + the Route. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string - sectionName: - description: "SectionName is the name of a section within the - target resource. In the following resources, SectionName is - interpreted as the following: \n * Gateway: Listener Name - \n Implementations MAY choose to support attaching Routes - to other resources. If that is the case, they MUST clearly - document how SectionName is interpreted. \n When unspecified - (empty string), this will reference the entire resource. For - the purpose of status, an attachment is considered successful - if at least one section in the parent resource accepts it. + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n When the parent resource + is a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified + values. \n Implementations MAY choose to support other parent + resources. Implementations supporting other types of parent + resources MUST clearly document how/if Port is interpreted. + \n For the purpose of status, an attachment is considered + successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n - Support: Core" + Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. * Service: Port Name. When both Port (experimental) + and SectionName are specified, the name and port of the selected + listener must match both specified values. Note that attaching + Routes to Services as Parents is part of experimental Mesh + support and is not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this will + reference the entire resource. For the purpose of status, + an attachment is considered successful if at least one section + in the parent resource accepts it. For example, Gateway listeners + can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept + attachment from the referencing Route, the Route MUST be considered + successfully attached. If no Gateway listeners accept attachment + from this Route, the Route MUST be considered detached from + the Gateway. \n Support: Core" maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -2833,6 +10171,29 @@ spec: type: object maxItems: 32 type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) rules: description: Rules are a list of TLS matchers and actions. items: @@ -2845,32 +10206,59 @@ spec: the rule performs no forwarding; if no filters are specified that would result in a response being sent, the underlying implementation must actively reject request attempts to this - backend, by rejecting the connection or returning a 503 status + backend, by rejecting the connection or returning a 500 status code. Request rejections must respect weight; if an invalid backend is requested to have 80% of requests, then 80% of requests must be rejected instead. \n Support: Core for Kubernetes - Service Support: Custom for any other resource \n Support - for weight: Extended" + Service \n Support: Extended for Kubernetes ServiceImport + \n Support: Implementation-specific for any other resource + \n Support for weight: Extended" items: description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a - namespace is specified, a ReferencePolicy object is required - in the referent namespace to allow that namespace's owner - to accept the reference. See the ReferencePolicy documentation - for details." + namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace + to allow that namespace's owner to accept the reference. + See the ReferenceGrant documentation for details. \n + \n When the BackendRef points to a Kubernetes Service, implementations + SHOULD honor the appProtocol field if it is set for the + target Service Port. \n Implementations supporting appProtocol + SHOULD recognize the Kubernetes Standard Application Protocols + defined in KEP-3726. \n If a Service appProtocol isn't specified, + an implementation MAY infer the backend protocol through + its own means. Implementations MAY infer the protocol from + the Route type referring to the backend Service. \n If a + Route is not able to send traffic to the backend using the + specified protocol then the backend is considered invalid. + Implementations MUST set the \"ResolvedRefs\" condition + to \"False\" with the \"UnsupportedProtocol\" reason. \n + \n Note that when the + BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. + See the fields where this struct is used for more information + about the exact behavior." properties: group: default: "" description: Group is the group of the referent. For example, - "networking.k8s.io". When unspecified (empty string), - core API group is inferred. + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Service - description: Kind is kind of the referent. For example - "HTTPRoute" or "Service". + description: "Kind is the Kubernetes resource kind of + the referent. For example \"Service\". \n Defaults to + \"Service\" when not specified. \n ExternalName services + can refer to CNAME DNS records that may live outside + of the cluster and as such are difficult to reason about + in terms of conformance. They also may not be safe to + forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName Services. + \n Support: Core (Services with a type other than ExternalName) + \n Support: Implementation-specific (Services with type + ExternalName)" maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -2883,11 +10271,11 @@ spec: namespace: description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n - Note that when a namespace is specified, a ReferencePolicy - object is required in the referent namespace to allow - that namespace's owner to accept the reference. See - the ReferencePolicy documentation for details. \n Support: - Core" + Note that when a namespace different than the local + namespace is specified, a ReferenceGrant object is required + in the referent namespace to allow that namespace's + owner to accept the reference. See the ReferenceGrant + documentation for details. \n Support: Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ @@ -2895,9 +10283,10 @@ spec: port: description: Port specifies the destination port number to use for this resource. Port is required when the - referent is a Kubernetes Service. For other resources, - destination port might be derived from the referent - resource or this field. + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. format: int32 maximum: 65535 minimum: 1 @@ -2924,6 +10313,10 @@ spec: required: - name type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' maxItems: 16 minItems: 1 type: array @@ -2969,20 +10362,20 @@ spec: condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does - not support. * The Route is in a namespace the the controller + not support. * The Route is in a namespace the controller does not have access to." items: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path - .status.conditions. For example, type FooStatus struct{ - \ // Represents the observations of a foo's current state. - \ // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // - +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + .status.conditions. For example, \n type FooStatus struct{ + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: \"Available\", \"Progressing\", + and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields + }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition @@ -3054,7 +10447,11 @@ spec: with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid - Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names)." + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ @@ -3065,15 +10462,21 @@ spec: properties: group: default: gateway.networking.k8s.io - description: "Group is the group of the referent. \n Support: - Core" + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: default: Gateway - description: "Kind is kind of the referent. \n Support: - Core (Gateway) Support: Custom (Other Resources)" + description: "Kind is kind of the referent. \n There are + two kinds of parent resources with \"Core\" support: \n + * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services + only) \n Support for other resources is Implementation-Specific." maxLength: 63 minLength: 1 pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ @@ -3086,29 +10489,84 @@ spec: type: string namespace: description: "Namespace is the namespace of the referent. - When unspecified (or empty string), this refers to the - local namespace of the Route. \n Support: Core" + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n ParentRefs from a Route to a Service in + the same namespace are \"producer\" routes, which apply + default routing rules to inbound connections from any + namespace to the Service. \n ParentRefs from a Route to + a Service in a different namespace are \"consumer\" routes, + and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for + which the intended destination of the connections are + a Service targeted as a ParentRef of the Route. \n Support: + Core" maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n When the parent resource is + a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are + specified, the name and port of the selected port must + match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer sectionName: description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener - Name \n Implementations MAY choose to support attaching - Routes to other resources. If that is the case, they MUST - clearly document how SectionName is interpreted. \n When - unspecified (empty string), this will reference the entire - resource. For the purpose of status, an attachment is - considered successful if at least one section in the parent - resource accepts it. For example, Gateway listeners can - restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept - attachment from the referencing Route, the Route MUST - be considered successfully attached. If no Gateway listeners - accept attachment from this Route, the Route MUST be considered - detached from the Gateway. \n Support: Core" + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. * Service: Port Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match + both specified values. Note that attaching Routes to Services + as Parents is part of experimental Mesh support and is + not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -3136,5 +10594,643 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: udproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: UDPRoute + listKind: UDPRouteList + plural: udproutes + singular: udproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: UDPRoute provides a way to route UDP traffic. When combined with + a Gateway listener, it can be used to forward traffic on the port specified + by the listener to a set of backends specified by the UDPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of UDPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) + that a Route wants to be attached to. Note that the referenced parent + resource needs to allow this for the attachment to be complete. + For Gateways, that means the Gateway needs to allow attachment from + Routes of this kind and namespace. For Services, that means the + Service must either be in the same namespace for a \"producer\" + route, or the mesh implementation must support and allow \"consumer\" + routes for the referenced Service. ReferenceGrant is not applicable + for governing ParentRefs to Services - it is not possible to create + a \"producer\" route for a Service in a different namespace from + the Route. \n There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services only) This + API may be extended in the future to support additional kinds of + parent resources. \n ParentRefs must be _distinct_. This means either + that: \n * They select different objects. If this is the case, + then parentRef entries are distinct. In terms of fields, this means + that the multi-part key defined by `group`, `kind`, `namespace`, + and `name` must be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field + used, each ParentRef that selects the same object must set the same + set of optional fields to different values. If one ParentRef sets + a combination of optional fields, all must set the same combination. + \n Some examples: \n * If one ParentRef sets `sectionName`, all + ParentRefs referencing the same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. * If one ParentRef sets `sectionName` + and `port`, all ParentRefs referencing the same object must also + set `sectionName` and `port`. \n It is possible to separately reference + multiple distinct objects that may be collapsed by an implementation. + For example, some implementations may choose to merge compatible + Gateway Listeners together. If that is the case, the list of routes + attached to those resources should also be merged. \n Note that + for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For + example, Gateway has the AllowedRoutes field, and ReferenceGrant + provides a generic way to enable other kinds of cross-namespace + reference. \n ParentRefs from a Route to a Service in the same + namespace are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. \n ParentRefs + from a Route to a Service in a different namespace are \"consumer\" + routes, and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for which the + intended destination of the connections are a Service targeted as + a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). There are two kinds of parent resources with \"Core\" + support: \n * Gateway (Gateway conformance profile) * Service + (Mesh conformance profile, experimental, ClusterIP Services only) + \n This API may be extended in the future to support additional + kinds of parent resources. \n The API object must be valid in + the cluster; the Group and Kind must be registered in the cluster + for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: + Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two + kinds of parent resources with \"Core\" support: \n * Gateway + (Gateway conformance profile) * Service (Mesh conformance + profile, experimental, ClusterIP Services only) \n Support + for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n ParentRefs from a Route to a Service in the same namespace + are \"producer\" routes, which apply default routing rules + to inbound connections from any namespace to the Service. + \n ParentRefs from a Route to a Service in a different namespace + are \"consumer\" routes, and these routing rules are only + applied to outbound connections originating from the same + namespace as the Route, for which the intended destination + of the connections are a Service targeted as a ParentRef of + the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n When the parent resource + is a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified + values. \n Implementations MAY choose to support other parent + resources. Implementations supporting other types of parent + resources MUST clearly document how/if Port is interpreted. + \n For the purpose of status, an attachment is considered + successful as long as the parent resource accepts it partially. + For example, Gateway listeners can restrict which Routes can + attach to them by Route kind, namespace, or hostname. If 1 + of 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. \n + Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. * Service: Port Name. When both Port (experimental) + and SectionName are specified, the name and port of the selected + listener must match both specified values. Note that attaching + Routes to Services as Parents is part of experimental Mesh + support and is not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this will + reference the entire resource. For the purpose of status, + an attachment is considered successful if at least one section + in the parent resource accepts it. For example, Gateway listeners + can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept + attachment from the referencing Route, the Route MUST be considered + successfully attached. If no Gateway listeners accept attachment + from this Route, the Route MUST be considered detached from + the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of UDP matchers and actions. + items: + description: UDPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching + requests should be sent. If unspecified or invalid (refers + to a non-existent resource or a Service with no endpoints), + the underlying implementation MUST actively reject connection + attempts to this backend. Packet drops must respect weight; + if an invalid backend is requested to have 80% of the packets, + then 80% of packets must be dropped instead. \n Support: Core + for Kubernetes Service \n Support: Extended for Kubernetes + ServiceImport \n Support: Implementation-specific for any + other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward + a request to a Kubernetes resource. \n Note that when a + namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace + to allow that namespace's owner to accept the reference. + See the ReferenceGrant documentation for details. \n + \n When the BackendRef points to a Kubernetes Service, implementations + SHOULD honor the appProtocol field if it is set for the + target Service Port. \n Implementations supporting appProtocol + SHOULD recognize the Kubernetes Standard Application Protocols + defined in KEP-3726. \n If a Service appProtocol isn't specified, + an implementation MAY infer the backend protocol through + its own means. Implementations MAY infer the protocol from + the Route type referring to the backend Service. \n If a + Route is not able to send traffic to the backend using the + specified protocol then the backend is considered invalid. + Implementations MUST set the \"ResolvedRefs\" condition + to \"False\" with the \"UnsupportedProtocol\" reason. \n + \n Note that when the + BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. + See the fields where this struct is used for more information + about the exact behavior." + properties: + group: + default: "" + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of + the referent. For example \"Service\". \n Defaults to + \"Service\" when not specified. \n ExternalName services + can refer to CNAME DNS records that may live outside + of the cluster and as such are difficult to reason about + in terms of conformance. They also may not be safe to + forward to (see CVE-2021-25740 for more information). + Implementations SHOULD NOT support ExternalName Services. + \n Support: Core (Services with a type other than ExternalName) + \n Support: Implementation-specific (Services with type + ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. + When unspecified, the local namespace is inferred. \n + Note that when a namespace different than the local + namespace is specified, a ReferenceGrant object is required + in the referent namespace to allow that namespace's + owner to accept the reference. See the ReferenceGrant + documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number + to use for this resource. Port is required when the + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1. \n Support for this field varies based + on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of UDPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) + that are associated with the route, and the status of the route + with respect to each parent. When this route attaches to a parent, + the controller that manages the parent must add an entry to this + list when the controller first sees the route and should update + the entry as appropriate when the route or gateway is modified. + \n Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this + API can only populate Route status for the Gateways/parent resources + they are responsible for. \n A maximum of 32 Gateways will be represented + in this list. An empty list means the route has not been attached + to any Gateway." + items: + description: RouteParentStatus describes the status of a route with + respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with + respect to the Gateway. Note that the route's availability + is also subject to the Gateway's own status conditions and + listener status. \n If the Route's ParentRef specifies an + existing Gateway that supports Routes of this kind AND that + Gateway's controller has sufficient access, then that Gateway's + controller MUST set the \"Accepted\" condition on the Route, + to indicate whether the route has been accepted or rejected + by the Gateway, and why. \n A Route MUST be considered \"Accepted\" + if at least one of the Route's rules is implemented by the + Gateway. \n There are a number of cases where the \"Accepted\" + condition may not be set due to lack of controller visibility, + that includes when: \n * The Route refers to a non-existent + parent. * The Route is of a type that the controller does + not support. * The Route is in a namespace the controller + does not have access to." + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, \n type FooStatus struct{ + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: \"Available\", \"Progressing\", + and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields + }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates + the name of the controller that wrote this status. This corresponds + with the controllerName field on GatewayClass. \n Example: + \"example.net/gateway-controller\". \n The format of this + field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec + that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are + two kinds of parent resources with \"Core\" support: \n + * Gateway (Gateway conformance profile) * Service (Mesh + conformance profile, experimental, ClusterIP Services + only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n ParentRefs from a Route to a Service in + the same namespace are \"producer\" routes, which apply + default routing rules to inbound connections from any + namespace to the Service. \n ParentRefs from a Route to + a Service in a different namespace are \"consumer\" routes, + and these routing rules are only applied to outbound connections + originating from the same namespace as the Route, for + which the intended destination of the connections are + a Service targeted as a ParentRef of the Route. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n When the parent resource is + a Service, this targets a specific port in the Service + spec. When both Port (experimental) and SectionName are + specified, the name and port of the selected port must + match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within + the target resource. In the following resources, SectionName + is interpreted as the following: \n * Gateway: Listener + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. * Service: Port Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match + both specified values. Note that attaching Routes to Services + as Parents is part of experimental Mesh support and is + not supported for any other purpose. \n Implementations + MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 37849dedc..5a4c63c3c 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -1,11 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: ingressroutes.traefik.io spec: group: traefik.io @@ -162,6 +160,12 @@ spec: can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When + set to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -267,20 +271,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: ingressroutetcps.traefik.io spec: group: traefik.io @@ -486,20 +482,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: ingressrouteudps.traefik.io spec: group: traefik.io @@ -591,20 +579,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: middlewares.traefik.io spec: group: traefik.io @@ -776,6 +756,13 @@ spec: items: type: string type: array + includedContentTypes: + description: IncludedContentTypes defines the list of content + types to compare the Content-Type header of the responses before + compressing. + items: + type: string + type: array minResponseBodyBytes: description: 'MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed. Default: @@ -896,6 +883,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -938,6 +931,12 @@ spec: This middleware delegates the request authentication to a Service. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/' properties: + addAuthCookiesToResponse: + description: AddAuthCookiesToResponse defines the list of cookies + to copy from the authentication server response to the response. + items: + type: string + type: array address: description: Address defines the authentication server address. type: string @@ -1190,6 +1189,36 @@ spec: description: 'IPAllowList holds the IP allowlist middleware configuration. This middleware accepts / refuses requests based on the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/' + properties: + ipStrategy: + description: 'IPStrategy holds the IP strategy configuration used + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position (starting + from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the X-Forwarded-For + header and select the first IP not in the list. + items: + type: string + type: array + type: object + rejectStatusCode: + description: RejectStatusCode defines the HTTP status code used + for refused requests. If not set, the default is 403 (Forbidden). + type: integer + sourceRange: + description: SourceRange defines the set of allowed IPs (or ranges + of allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object + ipWhiteList: + description: 'Deprecated: please use IPAllowList instead.' properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration used @@ -1493,20 +1522,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: middlewaretcps.traefik.io spec: group: traefik.io @@ -1558,6 +1579,17 @@ spec: type: string type: array type: object + ipWhiteList: + description: 'IPWhiteList defines the IPWhiteList middleware configuration. + Deprecated: please use IPAllowList instead.' + properties: + sourceRange: + description: SourceRange defines the allowed IPs (or ranges of + allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object type: object required: - metadata @@ -1565,20 +1597,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: serverstransports.traefik.io spec: group: traefik.io @@ -1706,20 +1730,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: serverstransporttcps.traefik.io spec: group: traefik.io @@ -1828,20 +1844,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: tlsoptions.traefik.io spec: group: traefik.io @@ -1935,20 +1943,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: tlsstores.traefik.io spec: group: traefik.io @@ -2034,20 +2034,12 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.13.0 name: traefikservices.traefik.io spec: group: traefik.io @@ -2178,6 +2170,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2269,6 +2267,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2376,6 +2380,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds + until the cookie expires. When set to a negative + number, the cookie expires immediately. When set + to zero, the cookie never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2415,6 +2425,12 @@ spec: description: HTTPOnly defines whether the cookie can be accessed by client-side APIs, such as JavaScript. type: boolean + maxAge: + description: MaxAge indicates the number of seconds until + the cookie expires. When set to a negative number, the + cookie expires immediately. When set to zero, the cookie + never expires. + type: integer name: description: Name defines the Cookie name. type: string @@ -2436,9 +2452,3 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/integration/fixtures/k8s/03-gateway.yml b/integration/fixtures/k8s/03-gateway.yml index c52f7c166..d985b00c1 100644 --- a/integration/fixtures/k8s/03-gateway.yml +++ b/integration/fixtures/k8s/03-gateway.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tcp-gateway namespace: default @@ -51,7 +51,7 @@ data: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tls-gateway namespace: default @@ -83,7 +83,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-https-gateway namespace: default @@ -104,7 +104,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/integration/fixtures/proxy-protocol/without.toml b/integration/fixtures/proxy-protocol/proxy-protocol.toml similarity index 70% rename from integration/fixtures/proxy-protocol/without.toml rename to integration/fixtures/proxy-protocol/proxy-protocol.toml index 4346c9926..4945a9099 100644 --- a/integration/fixtures/proxy-protocol/without.toml +++ b/integration/fixtures/proxy-protocol/proxy-protocol.toml @@ -7,10 +7,14 @@ noColor = true [entryPoints] - [entryPoints.web] + [entryPoints.trust] address = ":8000" - [entryPoints.web.proxyProtocol] - trustedIPs = ["1.2.3.4"] + [entryPoints.trust.proxyProtocol] + trustedIPs = ["127.0.0.1"] + [entryPoints.nottrust] + address = ":9000" + [entryPoints.nottrust.proxyProtocol] + trustedIPs = ["1.2.3.4"] [api] insecure = true diff --git a/integration/fixtures/redis/sentinel.toml b/integration/fixtures/redis/sentinel.toml new file mode 100644 index 000000000..3d5a59ec4 --- /dev/null +++ b/integration/fixtures/redis/sentinel.toml @@ -0,0 +1,19 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[entryPoints.web] + address = ":8000" + +[api] + insecure = true + +[providers.redis] + rootKey = "traefik" + endpoints = ["{{ .RedisAddress }}"] + +[providers.redis.sentinel] + masterName = "mymaster" diff --git a/integration/fixtures/proxy-protocol/with.toml b/integration/fixtures/retry/strip_prefix.toml similarity index 58% rename from integration/fixtures/proxy-protocol/with.toml rename to integration/fixtures/retry/strip_prefix.toml index 0c51207e0..afd57f26e 100644 --- a/integration/fixtures/proxy-protocol/with.toml +++ b/integration/fixtures/retry/strip_prefix.toml @@ -4,13 +4,10 @@ [log] level = "DEBUG" - noColor = true [entryPoints] [entryPoints.web] address = ":8000" - [entryPoints.web.proxyProtocol] - trustedIPs = ["{{.HaproxyIP}}"] [api] insecure = true @@ -23,11 +20,21 @@ [http.routers] [http.routers.router1] service = "service1" - rule = "Path(`/whoami`)" + middlewares = [ "retry", "strip-prefix" ] + rule = "PathPrefix(`/`)" + +[http.middlewares.retry.retry] + attempts = 3 + +[http.middlewares.strip-prefix.stripPrefix] + prefixes = [ "/test" ] [http.services] [http.services.service1] [http.services.service1.loadBalancer] [[http.services.service1.loadBalancer.servers]] - url = "http://{{.WhoamiIP}}" + url = "http://{{ .WhoamiIP }}:8080" + + [[http.services.service1.loadBalancer.servers]] + url = "http://{{ .WhoamiIP }}:80" diff --git a/integration/fixtures/simple_whitelist.toml b/integration/fixtures/simple_whitelist.toml new file mode 100644 index 000000000..03fa451e4 --- /dev/null +++ b/integration/fixtures/simple_whitelist.toml @@ -0,0 +1,18 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[entryPoints] + [entryPoints.web] + address = ":8000" + [entryPoints.web.ForwardedHeaders] + insecure = true + +[api] + insecure = true + +[providers] + [providers.docker] diff --git a/integration/fixtures/tcp/ipallowlist.toml b/integration/fixtures/tcp/ip-allowlist.toml similarity index 72% rename from integration/fixtures/tcp/ipallowlist.toml rename to integration/fixtures/tcp/ip-allowlist.toml index 2ef8c13f4..786971f52 100644 --- a/integration/fixtures/tcp/ipallowlist.toml +++ b/integration/fixtures/tcp/ip-allowlist.toml @@ -4,6 +4,8 @@ [log] level = "DEBUG" + noColor = true + [entryPoints] [entryPoints.tcp] @@ -24,16 +26,16 @@ rule = "HostSNI(`whoami-a.test`)" service = "whoami-a" middlewares = ["blocking-ipallowlist"] - [tcp.routers.to-whoami-a.tls] - passthrough = true + [tcp.routers.to-whoami-a.tls] + passthrough = true [tcp.routers.to-whoami-b] entryPoints = ["tcp"] rule = "HostSNI(`whoami-b.test`)" service = "whoami-b" middlewares = ["allowing-ipallowlist"] - [tcp.routers.to-whoami-b.tls] - passthrough = true + [tcp.routers.to-whoami-b.tls] + passthrough = true [tcp.services] [tcp.services.whoami-a.loadBalancer] @@ -44,8 +46,8 @@ [[tcp.services.whoami-b.loadBalancer.servers]] address = "{{ .WhoamiB }}" - [tcp.middlewares] - [tcp.middlewares.allowing-ipallowlist.ipAllowList] - sourceRange = ["127.0.0.1/32"] - [tcp.middlewares.blocking-ipallowlist.ipAllowList] - sourceRange = ["127.127.127.127/32"] +[tcp.middlewares] + [tcp.middlewares.allowing-ipallowlist.ipAllowList] + sourceRange = ["127.0.0.1/32"] + [tcp.middlewares.blocking-ipallowlist.ipAllowList] + sourceRange = ["127.127.127.127/32"] diff --git a/integration/fixtures/tcp/ip-whitelist.toml b/integration/fixtures/tcp/ip-whitelist.toml new file mode 100644 index 000000000..b9c6cc998 --- /dev/null +++ b/integration/fixtures/tcp/ip-whitelist.toml @@ -0,0 +1,52 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + noColor = true + +[entryPoints] + [entryPoints.tcp] + address = ":8093" + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[tcp] + [tcp.routers] + [tcp.routers.to-whoami-a] + entryPoints = ["tcp"] + rule = "HostSNI(`whoami-a.test`)" + service = "whoami-a" + middlewares = ["blocking-ipwhitelist"] + [tcp.routers.to-whoami-a.tls] + passthrough = true + + [tcp.routers.to-whoami-b] + entryPoints = ["tcp"] + rule = "HostSNI(`whoami-b.test`)" + service = "whoami-b" + middlewares = ["allowing-ipwhitelist"] + [tcp.routers.to-whoami-b.tls] + passthrough = true + + [tcp.services] + [tcp.services.whoami-a.loadBalancer] + [[tcp.services.whoami-a.loadBalancer.servers]] + address = "{{ .WhoamiA }}" + + [tcp.services.whoami-b.loadBalancer] + [[tcp.services.whoami-b.loadBalancer.servers]] + address = "{{ .WhoamiB }}" + +[tcp.middlewares] + [tcp.middlewares.allowing-ipwhitelist.ipWhiteList] + sourceRange = ["127.0.0.1/32"] + [tcp.middlewares.blocking-ipwhitelist.ipWhiteList] + sourceRange = ["127.127.127.127/32"] diff --git a/integration/fixtures/tracing/otel-collector-config.yaml b/integration/fixtures/tracing/otel-collector-config.yaml new file mode 100644 index 000000000..4f51aec4b --- /dev/null +++ b/integration/fixtures/tracing/otel-collector-config.yaml @@ -0,0 +1,21 @@ +receivers: + otlp: + protocols: + grpc: + http: +exporters: + otlp/tempo: + endpoint: http://tempo:4317 + tls: + insecure: true +service: + telemetry: + logs: + level: "error" + extensions: [ health_check ] + pipelines: + traces: + receivers: [otlp] + exporters: [otlp/tempo] +extensions: + health_check: \ No newline at end of file diff --git a/integration/fixtures/tracing/simple-jaeger.toml b/integration/fixtures/tracing/simple-jaeger.toml deleted file mode 100644 index bf02ced6a..000000000 --- a/integration/fixtures/tracing/simple-jaeger.toml +++ /dev/null @@ -1,70 +0,0 @@ -[global] - checkNewVersion = false - sendAnonymousUsage = false - -[log] - level = "DEBUG" - noColor = true - -[api] - insecure = true - -[entryPoints] - [entryPoints.web] - address = ":8000" - -[tracing] - servicename = "tracing" - [tracing.jaeger] - samplingType = "const" - samplingParam = 1.0 - samplingServerURL = "http://{{.IP}}:5778/sampling" - localAgentHostPort = "{{.IP}}:6831" - traceContextHeaderName = "{{.TraceContextHeaderName}}" - -[providers.file] - filename = "{{ .SelfFilename }}" - -## dynamic configuration ## - -[http.routers] - [http.routers.router1] - Service = "service1" - Middlewares = ["retry", "ratelimit-1"] - Rule = "Path(`/ratelimit`)" - [http.routers.router2] - Service = "service2" - Middlewares = ["retry"] - Rule = "Path(`/retry`)" - [http.routers.router3] - Service = "service3" - Middlewares = ["retry", "basic-auth"] - Rule = "Path(`/auth`)" - -[http.middlewares] - [http.middlewares.retry.retry] - attempts = 3 - [http.middlewares.basic-auth.basicAuth] - users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] - [http.middlewares.ratelimit-1.rateLimit] - average = 1 - burst = 2 - -[http.services] - [http.services.service1] - [http.services.service1.loadBalancer] - passHostHeader = true - [[http.services.service1.loadBalancer.servers]] - url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}" - - [http.services.service2] - [http.services.service2.loadBalancer] - passHostHeader = true - [[http.services.service2.loadBalancer.servers]] - url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}" - - [http.services.service3] - [http.services.service3.loadBalancer] - passHostHeader = true - [[http.services.service3.loadBalancer.servers]] - url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}" diff --git a/integration/fixtures/tracing/simple-jaeger-collector.toml b/integration/fixtures/tracing/simple-opentelemetry.toml similarity index 76% rename from integration/fixtures/tracing/simple-jaeger-collector.toml rename to integration/fixtures/tracing/simple-opentelemetry.toml index e858e2a57..77d779800 100644 --- a/integration/fixtures/tracing/simple-jaeger-collector.toml +++ b/integration/fixtures/tracing/simple-opentelemetry.toml @@ -15,12 +15,15 @@ [tracing] servicename = "tracing" - [tracing.jaeger] - samplingType = "const" - samplingParam = 1.0 - samplingServerURL = "http://{{.IP}}:5778/sampling" - [tracing.jaeger.collector] - endpoint = "http://{{.IP}}:14268/api/traces?format=jaeger.thrift" + sampleRate = 1.0 +{{if .IsHTTP}} + [tracing.otlp.http] + endpoint = "http://{{.IP}}:4318" +{{else}} + [tracing.otlp.grpc] + endpoint = "{{.IP}}:4317" + insecure = true +{{end}} [providers.file] filename = "{{ .SelfFilename }}" @@ -28,6 +31,10 @@ ## dynamic configuration ## [http.routers] + [http.routers.router0] + Service = "service0" + Middlewares = [] + Rule = "Path(`/basic`)" [http.routers.router1] Service = "service1" Middlewares = ["retry", "ratelimit-1"] @@ -50,8 +57,13 @@ average = 1 burst = 2 - [http.services] + [http.services.service0] + [http.services.service0.loadBalancer] + passHostHeader = true + [[http.services.service0.loadBalancer.servers]] + url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}" + [http.services.service1] [http.services.service1.loadBalancer] passHostHeader = true diff --git a/integration/fixtures/tracing/simple-zipkin.toml b/integration/fixtures/tracing/simple-zipkin.toml deleted file mode 100644 index ef935fa28..000000000 --- a/integration/fixtures/tracing/simple-zipkin.toml +++ /dev/null @@ -1,66 +0,0 @@ -[global] - checkNewVersion = false - sendAnonymousUsage = false - -[log] - level = "DEBUG" - noColor = true - -[api] - insecure = true - -[entryPoints] - [entryPoints.web] - address = ":8000" - -[tracing] - servicename = "tracing" - [tracing.zipkin] - httpEndpoint = "http://{{.IP}}:9411/api/v2/spans" - -[providers.file] - filename = "{{ .SelfFilename }}" - -## dynamic configuration ## - -[http.routers] - [http.routers.router1] - service = "service1" - middlewares = ["retry", "ratelimit-1"] - rule = "Path(`/ratelimit`)" - [http.routers.router2] - service = "service2" - middlewares = ["retry"] - rule = "Path(`/retry`)" - [http.routers.router3] - service = "service3" - middlewares = ["retry", "basic-auth"] - rule = "Path(`/auth`)" - -[http.middlewares] - [http.middlewares.retry.retry] - attempts = 3 - [http.middlewares.basic-auth.basicAuth] - users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] - [http.middlewares.ratelimit-1.rateLimit] - average = 1 - burst = 2 - -[http.services] - [http.services.service1] - [http.services.service1.loadBalancer] - passHostHeader = true - [[http.services.service1.loadBalancer.servers]] - url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}" - - [http.services.service2] - [http.services.service2.loadBalancer] - passHostHeader = true - [[http.services.service2.loadBalancer.servers]] - url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}" - - [http.services.service3] - [http.services.service3.loadBalancer] - passHostHeader = true - [[http.services.service3.loadBalancer.servers]] - url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}" diff --git a/integration/fixtures/tracing/tempo.yaml b/integration/fixtures/tracing/tempo.yaml new file mode 100644 index 000000000..0a115bfaa --- /dev/null +++ b/integration/fixtures/tracing/tempo.yaml @@ -0,0 +1,56 @@ +server: + http_listen_port: 3200 + +query_frontend: + search: + duration_slo: 5s + throughput_bytes_slo: 1.073741824e+09 + trace_by_id: + duration_slo: 5s + +distributor: + receivers: + otlp: + protocols: + grpc: + +ingester: + lifecycler: + address: 127.0.0.1 + ring: + kvstore: + store: inmemory + replication_factor: 1 + final_sleep: 0s + trace_idle_period: 1s + max_block_duration: 1m + complete_block_timeout: 5s + flush_check_period: 1s +compactor: + compaction: + block_retention: 1h + +metrics_generator: + registry: + external_labels: + source: tempo + cluster: docker-compose + storage: + path: /tmp/tempo/generator/wal + remote_write: + - url: http://prometheus:9090/api/v1/write + send_exemplars: true + +storage: + trace: + backend: local # backend configuration to use + wal: + path: /tmp/tempo/wal # where to store the wal locally + local: + path: /tmp/tempo/blocks + +overrides: + defaults: + metrics_generator: + processors: [service-graphs, span-metrics] # enables metrics generator + diff --git a/integration/grpc_test.go b/integration/grpc_test.go index bf96e1ece..6dce232d7 100644 --- a/integration/grpc_test.go +++ b/integration/grpc_test.go @@ -8,10 +8,12 @@ import ( "math/rand" "net" "os" + "testing" "time" - "github.com/go-check/check" "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/helloworld" "github.com/traefik/traefik/v3/integration/try" "google.golang.org/grpc" @@ -29,20 +31,24 @@ const randCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567 // GRPCSuite tests suite. type GRPCSuite struct{ BaseSuite } +func TestGRPCSuite(t *testing.T) { + suite.Run(t, new(GRPCSuite)) +} + type myserver struct { stopStreamExample chan bool } -func (s *GRPCSuite) SetUpSuite(c *check.C) { +func (s *GRPCSuite) SetupSuite() { var err error LocalhostCert, err = os.ReadFile("./resources/tls/local.cert") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) LocalhostKey, err = os.ReadFile("./resources/tls/local.key") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) } func (s *myserver) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) { - return &helloworld.HelloReply{Message: "Hello " + in.Name}, nil + return &helloworld.HelloReply{Message: "Hello " + in.GetName()}, nil } func (s *myserver) StreamExample(in *helloworld.StreamExampleRequest, server helloworld.Greeter_StreamExampleServer) error { @@ -121,7 +127,7 @@ func callHelloClientGRPC(name string, secure bool) (string, error) { if err != nil { return "", err } - return r.Message, nil + return r.GetMessage(), nil } func callStreamExampleClientGRPC() (helloworld.Greeter_StreamExampleClient, func() error, error) { @@ -137,19 +143,18 @@ func callStreamExampleClientGRPC() (helloworld.Greeter_StreamExampleClient, func return t, closer, nil } -func (s *GRPCSuite) TestGRPC(c *check.C) { +func (s *GRPCSuite) TestGRPC() { lis, err := net.Listen("tcp", ":0") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) _, port, err := net.SplitHostPort(lis.Addr().String()) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) go func() { err := startGRPCServer(lis, &myserver{}) - c.Log(err) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) }() - file := s.adaptFile(c, "fixtures/grpc/config.toml", struct { + file := s.adaptFile("fixtures/grpc/config.toml", struct { CertContent string KeyContent string GRPCServerPort string @@ -159,79 +164,65 @@ func (s *GRPCSuite) TestGRPC(c *check.C) { GRPCServerPort: port, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err = cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for Traefik err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) var response string err = try.Do(1*time.Second, func() error { response, err = callHelloClientGRPC("World", true) return err }) - c.Assert(err, check.IsNil) - c.Assert(response, check.Equals, "Hello World") + assert.NoError(s.T(), err) + assert.Equal(s.T(), "Hello World", response) } -func (s *GRPCSuite) TestGRPCh2c(c *check.C) { +func (s *GRPCSuite) TestGRPCh2c() { lis, err := net.Listen("tcp", ":0") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) _, port, err := net.SplitHostPort(lis.Addr().String()) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) go func() { err := starth2cGRPCServer(lis, &myserver{}) - c.Log(err) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) }() - file := s.adaptFile(c, "fixtures/grpc/config_h2c.toml", struct { + file := s.adaptFile("fixtures/grpc/config_h2c.toml", struct { GRPCServerPort string }{ GRPCServerPort: port, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err = cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for Traefik err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) var response string err = try.Do(1*time.Second, func() error { response, err = callHelloClientGRPC("World", false) return err }) - c.Assert(err, check.IsNil) - c.Assert(response, check.Equals, "Hello World") + assert.NoError(s.T(), err) + assert.Equal(s.T(), "Hello World", response) } -func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) { +func (s *GRPCSuite) TestGRPCh2cTermination() { lis, err := net.Listen("tcp", ":0") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) _, port, err := net.SplitHostPort(lis.Addr().String()) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) go func() { err := starth2cGRPCServer(lis, &myserver{}) - c.Log(err) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) }() - file := s.adaptFile(c, "fixtures/grpc/config_h2c_termination.toml", struct { + file := s.adaptFile("fixtures/grpc/config_h2c_termination.toml", struct { CertContent string KeyContent string GRPCServerPort string @@ -241,40 +232,33 @@ func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) { GRPCServerPort: port, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err = cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for Traefik err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) var response string err = try.Do(1*time.Second, func() error { response, err = callHelloClientGRPC("World", true) return err }) - c.Assert(err, check.IsNil) - c.Assert(response, check.Equals, "Hello World") + assert.NoError(s.T(), err) + assert.Equal(s.T(), "Hello World", response) } -func (s *GRPCSuite) TestGRPCInsecure(c *check.C) { +func (s *GRPCSuite) TestGRPCInsecure() { lis, err := net.Listen("tcp", ":0") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) _, port, err := net.SplitHostPort(lis.Addr().String()) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) go func() { err := startGRPCServer(lis, &myserver{}) - c.Log(err) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) }() - file := s.adaptFile(c, "fixtures/grpc/config_insecure.toml", struct { + file := s.adaptFile("fixtures/grpc/config_insecure.toml", struct { CertContent string KeyContent string GRPCServerPort string @@ -284,44 +268,37 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) { GRPCServerPort: port, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err = cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for Traefik err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) var response string err = try.Do(1*time.Second, func() error { response, err = callHelloClientGRPC("World", true) return err }) - c.Assert(err, check.IsNil) - c.Assert(response, check.Equals, "Hello World") + assert.NoError(s.T(), err) + assert.Equal(s.T(), "Hello World", response) } -func (s *GRPCSuite) TestGRPCBuffer(c *check.C) { +func (s *GRPCSuite) TestGRPCBuffer() { stopStreamExample := make(chan bool) defer func() { stopStreamExample <- true }() lis, err := net.Listen("tcp", ":0") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) _, port, err := net.SplitHostPort(lis.Addr().String()) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) go func() { err := startGRPCServer(lis, &myserver{ stopStreamExample: stopStreamExample, }) - c.Log(err) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) }() - file := s.adaptFile(c, "fixtures/grpc/config.toml", struct { + file := s.adaptFile("fixtures/grpc/config.toml", struct { CertContent string KeyContent string GRPCServerPort string @@ -331,27 +308,21 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) { GRPCServerPort: port, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err = cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for Traefik err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) var client helloworld.Greeter_StreamExampleClient client, closer, err := callStreamExampleClientGRPC() defer func() { _ = closer() }() - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) received := make(chan bool) go func() { tr, err := client.Recv() - c.Assert(err, check.IsNil) - c.Assert(len(tr.Data), check.Equals, 512) + assert.NoError(s.T(), err) + assert.Len(s.T(), tr.GetData(), 512) received <- true }() @@ -363,25 +334,24 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) { return errors.New("failed to receive stream data") } }) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) } -func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) { +func (s *GRPCSuite) TestGRPCBufferWithFlushInterval() { stopStreamExample := make(chan bool) lis, err := net.Listen("tcp", ":0") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) _, port, err := net.SplitHostPort(lis.Addr().String()) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) go func() { err := startGRPCServer(lis, &myserver{ stopStreamExample: stopStreamExample, }) - c.Log(err) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) }() - file := s.adaptFile(c, "fixtures/grpc/config.toml", struct { + file := s.adaptFile("fixtures/grpc/config.toml", struct { CertContent string KeyContent string GRPCServerPort string @@ -390,17 +360,11 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) { KeyContent: string(LocalhostKey), GRPCServerPort: port, }) - defer os.Remove(file) - - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for Traefik err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) var client helloworld.Greeter_StreamExampleClient client, closer, err := callStreamExampleClientGRPC() @@ -408,13 +372,13 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) { _ = closer() stopStreamExample <- true }() - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) received := make(chan bool) go func() { tr, err := client.Recv() - c.Assert(err, check.IsNil) - c.Assert(len(tr.Data), check.Equals, 512) + assert.NoError(s.T(), err) + assert.Len(s.T(), tr.GetData(), 512) received <- true }() @@ -426,22 +390,21 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) { return errors.New("failed to receive stream data") } }) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) } -func (s *GRPCSuite) TestGRPCWithRetry(c *check.C) { +func (s *GRPCSuite) TestGRPCWithRetry() { lis, err := net.Listen("tcp", ":0") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) _, port, err := net.SplitHostPort(lis.Addr().String()) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) go func() { err := startGRPCServer(lis, &myserver{}) - c.Log(err) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) }() - file := s.adaptFile(c, "fixtures/grpc/config_retry.toml", struct { + file := s.adaptFile("fixtures/grpc/config_retry.toml", struct { CertContent string KeyContent string GRPCServerPort string @@ -451,23 +414,17 @@ func (s *GRPCSuite) TestGRPCWithRetry(c *check.C) { GRPCServerPort: port, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err = cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for Traefik err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) var response string err = try.Do(1*time.Second, func() error { response, err = callHelloClientGRPC("World", true) return err }) - c.Assert(err, check.IsNil) - c.Assert(response, check.Equals, "Hello World") + assert.NoError(s.T(), err) + assert.Equal(s.T(), "Hello World", response) } diff --git a/integration/headers_test.go b/integration/headers_test.go index 60fd59578..e47fe1d73 100644 --- a/integration/headers_test.go +++ b/integration/headers_test.go @@ -1,45 +1,76 @@ package integration import ( + "net" "net/http" - "os" + "net/http/httptest" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) // Headers tests suite. type HeadersSuite struct{ BaseSuite } -func (s *HeadersSuite) TestSimpleConfiguration(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/headers/basic.toml")) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - // Expected a 404 as we did not configure anything - err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) +func TestHeadersSuite(t *testing.T) { + suite.Run(t, new(HeadersSuite)) } -func (s *HeadersSuite) TestCorsResponses(c *check.C) { - file := s.adaptFile(c, "fixtures/headers/cors.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) +func (s *HeadersSuite) TestSimpleConfiguration() { + s.traefikCmd(withConfigFile("fixtures/headers/basic.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + // Expected a 404 as we did not configure anything + err := try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) +} + +func (s *HeadersSuite) TestReverseProxyHeaderRemoved() { + file := s.adaptFile("fixtures/headers/remove_reverseproxy_headers.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, found := r.Header["X-Forwarded-Host"] + assert.True(s.T(), found) + _, found = r.Header["Foo"] + assert.False(s.T(), found) + _, found = r.Header["X-Forwarded-For"] + assert.False(s.T(), found) + }) + + listener, err := net.Listen("tcp", "127.0.0.1:9000") + require.NoError(s.T(), err) + + ts := &httptest.Server{ + Listener: listener, + Config: &http.Server{Handler: handler}, + } + ts.Start() + defer ts.Close() + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + require.NoError(s.T(), err) + req.Host = "test.localhost" + req.Header = http.Header{ + "Foo": {"bar"}, + } + + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) +} + +func (s *HeadersSuite) TestCorsResponses() { + file := s.adaptFile("fixtures/headers/cors.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) backend := startTestServer("9000", http.StatusOK, "") defer backend.Close() - err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + err := try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) testCase := []struct { desc string @@ -105,30 +136,24 @@ func (s *HeadersSuite) TestCorsResponses(c *check.C) { for _, test := range testCase { req, err := http.NewRequest(test.method, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = test.reqHost req.Header = test.requestHeaders err = try.Request(req, 500*time.Millisecond, try.HasHeaderStruct(test.expected)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } } -func (s *HeadersSuite) TestSecureHeadersResponses(c *check.C) { - file := s.adaptFile(c, "fixtures/headers/secure.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *HeadersSuite) TestSecureHeadersResponses() { + file := s.adaptFile("fixtures/headers/secure.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) backend := startTestServer("9000", http.StatusOK, "") defer backend.Close() - err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + err := try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) testCase := []struct { desc string @@ -148,36 +173,30 @@ func (s *HeadersSuite) TestSecureHeadersResponses(c *check.C) { for _, test := range testCase { req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = test.reqHost err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasHeaderStruct(test.expected)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/api/rawdata", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = test.internalReqHost err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasHeaderStruct(test.expected)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } } -func (s *HeadersSuite) TestMultipleSecureHeadersResponses(c *check.C) { - file := s.adaptFile(c, "fixtures/headers/secure_multiple.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *HeadersSuite) TestMultipleSecureHeadersResponses() { + file := s.adaptFile("fixtures/headers/secure_multiple.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) backend := startTestServer("9000", http.StatusOK, "") defer backend.Close() - err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + err := try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) testCase := []struct { desc string @@ -196,10 +215,10 @@ func (s *HeadersSuite) TestMultipleSecureHeadersResponses(c *check.C) { for _, test := range testCase { req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = test.reqHost err = try.Request(req, 500*time.Millisecond, try.HasHeaderStruct(test.expected)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } } diff --git a/integration/healthcheck_test.go b/integration/healthcheck_test.go index 79262fc89..1cfabb176 100644 --- a/integration/healthcheck_test.go +++ b/integration/healthcheck_test.go @@ -7,11 +7,13 @@ import ( "net/http" "os" "strings" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) // HealthCheck test suites. @@ -23,287 +25,272 @@ type HealthCheckSuite struct { whoami4IP string } -func (s *HealthCheckSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "healthcheck") - s.composeUp(c) - - s.whoami1IP = s.getComposeServiceIP(c, "whoami1") - s.whoami2IP = s.getComposeServiceIP(c, "whoami2") - s.whoami3IP = s.getComposeServiceIP(c, "whoami3") - s.whoami4IP = s.getComposeServiceIP(c, "whoami4") +func TestHealthCheckSuite(t *testing.T) { + suite.Run(t, new(HealthCheckSuite)) } -func (s *HealthCheckSuite) TestSimpleConfiguration(c *check.C) { - file := s.adaptFile(c, "fixtures/healthcheck/simple.toml", struct { +func (s *HealthCheckSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("healthcheck") + s.composeUp() + + s.whoami1IP = s.getComposeServiceIP("whoami1") + s.whoami2IP = s.getComposeServiceIP("whoami2") + s.whoami3IP = s.getComposeServiceIP("whoami3") + s.whoami4IP = s.getComposeServiceIP("whoami4") +} + +func (s *HealthCheckSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *HealthCheckSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/healthcheck/simple.toml", struct { Server1 string Server2 string }{s.whoami1IP, s.whoami2IP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) + require.NoError(s.T(), err) frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendHealthReq.Host = "test.localhost" err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Fix all whoami health to 500 client := &http.Client{} whoamiHosts := []string{s.whoami1IP, s.whoami2IP} for _, whoami := range whoamiHosts { statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusInternalServerErrorReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } // Verify no backend service is available due to failing health checks err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Change one whoami health to 200 statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusOKReq1) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify frontend health : after err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendReq.Host = "test.localhost" // Check if whoami1 responds err = try.Request(frontendReq, 500*time.Millisecond, try.BodyContains(s.whoami1IP)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Check if the service with bad health check (whoami2) never respond. err = try.Request(frontendReq, 2*time.Second, try.BodyContains(s.whoami2IP)) - c.Assert(err, checker.NotNil) + assert.Error(s.T(), err) // TODO validate : run on 80 resp, err := http.Get("http://127.0.0.1:8000/") // Expected a 404 as we did not configure anything - c.Assert(err, checker.IsNil) - c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusNotFound, resp.StatusCode) } -func (s *HealthCheckSuite) TestMultipleEntrypoints(c *check.C) { - file := s.adaptFile(c, "fixtures/healthcheck/multiple-entrypoints.toml", struct { +func (s *HealthCheckSuite) TestMultipleEntrypoints() { + file := s.adaptFile("fixtures/healthcheck/multiple-entrypoints.toml", struct { Server1 string Server2 string }{s.whoami1IP, s.whoami2IP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // Wait for traefik - err = try.GetRequest("http://localhost:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) + require.NoError(s.T(), err) // Check entrypoint http1 frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendHealthReq.Host = "test.localhost" - err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + err = try.Request(frontendHealthReq, 5*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) // Check entrypoint http2 frontendHealthReq, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendHealthReq.Host = "test.localhost" - err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + err = try.Request(frontendHealthReq, 5*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) // Set the both whoami health to 500 client := &http.Client{} whoamiHosts := []string{s.whoami1IP, s.whoami2IP} for _, whoami := range whoamiHosts { statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusInternalServerErrorReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } // Verify no backend service is available due to failing health checks err = try.Request(frontendHealthReq, 5*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // reactivate the whoami2 statusInternalServerOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami2IP+"/health", bytes.NewBufferString("200")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusInternalServerOkReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontend1Req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontend1Req.Host = "test.localhost" frontend2Req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontend2Req.Host = "test.localhost" // Check if whoami1 never responds err = try.Request(frontend2Req, 2*time.Second, try.BodyContains(s.whoami1IP)) - c.Assert(err, checker.NotNil) + assert.Error(s.T(), err) // Check if whoami1 never responds err = try.Request(frontend1Req, 2*time.Second, try.BodyContains(s.whoami1IP)) - c.Assert(err, checker.NotNil) + assert.Error(s.T(), err) } -func (s *HealthCheckSuite) TestPortOverload(c *check.C) { +func (s *HealthCheckSuite) TestPortOverload() { // Set one whoami health to 200 client := &http.Client{} statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusInternalServerErrorReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - file := s.adaptFile(c, "fixtures/healthcheck/port_overload.toml", struct { + file := s.adaptFile("fixtures/healthcheck/port_overload.toml", struct { Server1 string }{s.whoami1IP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("Host(`test.localhost`)")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) frontendHealthReq.Host = "test.localhost" // We test bad gateway because we use an invalid port for the backend err = try.Request(frontendHealthReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Set one whoami health to 500 statusInternalServerErrorReq, err = http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("500")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusInternalServerErrorReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify no backend service is available due to failing health checks err = try.Request(frontendHealthReq, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } // Checks if all the loadbalancers created will correctly update the server status. -func (s *HealthCheckSuite) TestMultipleRoutersOnSameService(c *check.C) { - file := s.adaptFile(c, "fixtures/healthcheck/multiple-routers-one-same-service.toml", struct { +func (s *HealthCheckSuite) TestMultipleRoutersOnSameService() { + file := s.adaptFile("fixtures/healthcheck/multiple-routers-one-same-service.toml", struct { Server1 string }{s.whoami1IP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) + require.NoError(s.T(), err) // Set whoami health to 200 to be sure to start with the wanted status client := &http.Client{} statusOkReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusOkReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // check healthcheck on web1 entrypoint healthReqWeb1, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) healthReqWeb1.Host = "test.localhost" err = try.Request(healthReqWeb1, 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // check healthcheck on web2 entrypoint healthReqWeb2, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:9000/health", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) healthReqWeb2.Host = "test.localhost" err = try.Request(healthReqWeb2, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Set whoami health to 500 statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("500")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusInternalServerErrorReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify no backend service is available due to failing health checks err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusServiceUnavailable)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Change one whoami health to 200 statusOKReq1, err := http.NewRequest(http.MethodPost, "http://"+s.whoami1IP+"/health", bytes.NewBufferString("200")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusOKReq1) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify health check err = try.Request(healthReqWeb1, 3*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Request(healthReqWeb2, 3*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *HealthCheckSuite) TestPropagate(c *check.C) { - file := s.adaptFile(c, "fixtures/healthcheck/propagate.toml", struct { +func (s *HealthCheckSuite) TestPropagate() { + file := s.adaptFile("fixtures/healthcheck/propagate.toml", struct { Server1 string Server2 string Server3 string Server4 string }{s.whoami1IP, s.whoami2IP, s.whoami3IP, s.whoami4IP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`root.localhost`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`root.localhost`)")) + require.NoError(s.T(), err) rootReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) rootReq.Host = "root.localhost" err = try.Request(rootReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Bring whoami1 and whoami3 down client := http.Client{ @@ -313,9 +300,9 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) { whoamiHosts := []string{s.whoami1IP, s.whoami3IP} for _, whoami := range whoamiHosts { statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusInternalServerErrorReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } try.Sleep(time.Second) @@ -327,19 +314,19 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) { reachedServers := make(map[string]int) for i := 0; i < 4; i++ { resp, err := client.Do(rootReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if reachedServers[s.whoami4IP] > reachedServers[s.whoami2IP] { - c.Assert(string(body), checker.Contains, want2) + assert.Contains(s.T(), string(body), want2) reachedServers[s.whoami2IP]++ continue } if reachedServers[s.whoami2IP] > reachedServers[s.whoami4IP] { - c.Assert(string(body), checker.Contains, want4) + assert.Contains(s.T(), string(body), want4) reachedServers[s.whoami4IP]++ continue } @@ -356,48 +343,48 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) { } } - c.Assert(reachedServers[s.whoami2IP], checker.Equals, 2) - c.Assert(reachedServers[s.whoami4IP], checker.Equals, 2) + assert.Equal(s.T(), 2, reachedServers[s.whoami2IP]) + assert.Equal(s.T(), 2, reachedServers[s.whoami4IP]) fooReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) fooReq.Host = "foo.localhost" // Verify load-balancing on foo still works, and that we're getting wsp2, wsp2, wsp2, wsp2, etc. want := `IP: ` + s.whoami2IP for i := 0; i < 4; i++ { resp, err := client.Do(fooReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - c.Assert(string(body), checker.Contains, want) + assert.Contains(s.T(), string(body), want) } barReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) barReq.Host = "bar.localhost" // Verify load-balancing on bar still works, and that we're getting wsp2, wsp2, wsp2, wsp2, etc. want = `IP: ` + s.whoami2IP for i := 0; i < 4; i++ { resp, err := client.Do(barReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - c.Assert(string(body), checker.Contains, want) + assert.Contains(s.T(), string(body), want) } // Bring whoami2 and whoami4 down whoamiHosts = []string{s.whoami2IP, s.whoami4IP} for _, whoami := range whoamiHosts { statusInternalServerErrorReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("500")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusInternalServerErrorReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } try.Sleep(time.Second) @@ -405,25 +392,25 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) { // Verify that everything is down, and that we get 503s everywhere. for i := 0; i < 2; i++ { resp, err := client.Do(rootReq) - c.Assert(err, checker.IsNil) - c.Assert(resp.StatusCode, checker.Equals, http.StatusServiceUnavailable) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusServiceUnavailable, resp.StatusCode) resp, err = client.Do(fooReq) - c.Assert(err, checker.IsNil) - c.Assert(resp.StatusCode, checker.Equals, http.StatusServiceUnavailable) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusServiceUnavailable, resp.StatusCode) resp, err = client.Do(barReq) - c.Assert(err, checker.IsNil) - c.Assert(resp.StatusCode, checker.Equals, http.StatusServiceUnavailable) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusServiceUnavailable, resp.StatusCode) } // Bring everything back up. whoamiHosts = []string{s.whoami1IP, s.whoami2IP, s.whoami3IP, s.whoami4IP} for _, whoami := range whoamiHosts { statusOKReq, err := http.NewRequest(http.MethodPost, "http://"+whoami+"/health", bytes.NewBufferString("200")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusOKReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } try.Sleep(time.Second) @@ -432,10 +419,10 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) { reachedServers = make(map[string]int) for i := 0; i < 4; i++ { resp, err := client.Do(rootReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if strings.Contains(string(body), `IP: `+s.whoami1IP) { reachedServers[s.whoami1IP]++ @@ -458,19 +445,19 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) { } } - c.Assert(reachedServers[s.whoami1IP], checker.Equals, 1) - c.Assert(reachedServers[s.whoami2IP], checker.Equals, 1) - c.Assert(reachedServers[s.whoami3IP], checker.Equals, 1) - c.Assert(reachedServers[s.whoami4IP], checker.Equals, 1) + assert.Equal(s.T(), 1, reachedServers[s.whoami1IP]) + assert.Equal(s.T(), 1, reachedServers[s.whoami2IP]) + assert.Equal(s.T(), 1, reachedServers[s.whoami3IP]) + assert.Equal(s.T(), 1, reachedServers[s.whoami4IP]) // Verify everything is up on foo router. reachedServers = make(map[string]int) for i := 0; i < 4; i++ { resp, err := client.Do(fooReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if strings.Contains(string(body), `IP: `+s.whoami1IP) { reachedServers[s.whoami1IP]++ @@ -493,19 +480,19 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) { } } - c.Assert(reachedServers[s.whoami1IP], checker.Equals, 2) - c.Assert(reachedServers[s.whoami2IP], checker.Equals, 1) - c.Assert(reachedServers[s.whoami3IP], checker.Equals, 1) - c.Assert(reachedServers[s.whoami4IP], checker.Equals, 0) + assert.Equal(s.T(), 2, reachedServers[s.whoami1IP]) + assert.Equal(s.T(), 1, reachedServers[s.whoami2IP]) + assert.Equal(s.T(), 1, reachedServers[s.whoami3IP]) + assert.Equal(s.T(), 0, reachedServers[s.whoami4IP]) // Verify everything is up on bar router. reachedServers = make(map[string]int) for i := 0; i < 4; i++ { resp, err := client.Do(barReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if strings.Contains(string(body), `IP: `+s.whoami1IP) { reachedServers[s.whoami1IP]++ @@ -528,102 +515,87 @@ func (s *HealthCheckSuite) TestPropagate(c *check.C) { } } - c.Assert(reachedServers[s.whoami1IP], checker.Equals, 2) - c.Assert(reachedServers[s.whoami2IP], checker.Equals, 1) - c.Assert(reachedServers[s.whoami3IP], checker.Equals, 1) - c.Assert(reachedServers[s.whoami4IP], checker.Equals, 0) + assert.Equal(s.T(), 2, reachedServers[s.whoami1IP]) + assert.Equal(s.T(), 1, reachedServers[s.whoami2IP]) + assert.Equal(s.T(), 1, reachedServers[s.whoami3IP]) + assert.Equal(s.T(), 0, reachedServers[s.whoami4IP]) } -func (s *HealthCheckSuite) TestPropagateNoHealthCheck(c *check.C) { - file := s.adaptFile(c, "fixtures/healthcheck/propagate_no_healthcheck.toml", struct { +func (s *HealthCheckSuite) TestPropagateNoHealthCheck() { + file := s.adaptFile("fixtures/healthcheck/propagate_no_healthcheck.toml", struct { Server1 string }{s.whoami1IP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`noop.localhost`)"), try.BodyNotContains("Host(`root.localhost`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`noop.localhost`)"), try.BodyNotContains("Host(`root.localhost`)")) + require.NoError(s.T(), err) rootReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) rootReq.Host = "root.localhost" err = try.Request(rootReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *HealthCheckSuite) TestPropagateReload(c *check.C) { +func (s *HealthCheckSuite) TestPropagateReload() { // Setup a WSP service without the healthcheck enabled (wsp-service1) - withoutHealthCheck := s.adaptFile(c, "fixtures/healthcheck/reload_without_healthcheck.toml", struct { + withoutHealthCheck := s.adaptFile("fixtures/healthcheck/reload_without_healthcheck.toml", struct { Server1 string Server2 string }{s.whoami1IP, s.whoami2IP}) - defer os.Remove(withoutHealthCheck) - withHealthCheck := s.adaptFile(c, "fixtures/healthcheck/reload_with_healthcheck.toml", struct { + withHealthCheck := s.adaptFile("fixtures/healthcheck/reload_with_healthcheck.toml", struct { Server1 string Server2 string }{s.whoami1IP, s.whoami2IP}) - defer os.Remove(withHealthCheck) - cmd, display := s.traefikCmd(withConfigFile(withoutHealthCheck)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(withoutHealthCheck)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`root.localhost`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`root.localhost`)")) + require.NoError(s.T(), err) // Allow one of the underlying services on it to fail all servers HC (whoami2) client := http.Client{ Timeout: 10 * time.Second, } statusOKReq, err := http.NewRequest(http.MethodPost, "http://"+s.whoami2IP+"/health", bytes.NewBufferString("500")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = client.Do(statusOKReq) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) rootReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) rootReq.Host = "root.localhost" // Check the failed service (whoami2) is getting requests, but answer 500 err = try.Request(rootReq, 500*time.Millisecond, try.StatusCodeIs(http.StatusServiceUnavailable)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Enable the healthcheck on the root WSP (wsp-service1) and let Traefik reload the config fr1, err := os.OpenFile(withoutHealthCheck, os.O_APPEND|os.O_WRONLY, 0o644) - c.Assert(fr1, checker.NotNil) - c.Assert(err, checker.IsNil) + assert.NotNil(s.T(), fr1) + require.NoError(s.T(), err) err = fr1.Truncate(0) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) fr2, err := os.ReadFile(withHealthCheck) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = fmt.Fprint(fr1, string(fr2)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = fr1.Close() - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - try.Sleep(1 * time.Second) + // Waiting for the reflected change. + err = try.Request(rootReq, 5*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) // Check the failed service (whoami2) is not getting requests wantIPs := []string{s.whoami1IP, s.whoami1IP, s.whoami1IP, s.whoami1IP} for _, ip := range wantIPs { - want := "IP: " + ip - resp, err := client.Do(rootReq) - c.Assert(err, checker.IsNil) - - body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) - - c.Assert(string(body), checker.Contains, want) + err = try.Request(rootReq, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("IP: "+ip)) + require.NoError(s.T(), err) } } diff --git a/integration/hostresolver_test.go b/integration/hostresolver_test.go index e69e33dd9..6d6e3965a 100644 --- a/integration/hostresolver_test.go +++ b/integration/hostresolver_test.go @@ -2,27 +2,33 @@ package integration import ( "net/http" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type HostResolverSuite struct{ BaseSuite } -func (s *HostResolverSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "hostresolver") - s.composeUp(c) +func TestHostResolverSuite(t *testing.T) { + suite.Run(t, new(HostResolverSuite)) } -func (s *HostResolverSuite) TestSimpleConfig(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/simple_hostresolver.toml")) - defer display(c) +func (s *HostResolverSuite) SetupSuite() { + s.BaseSuite.SetupSuite() - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.createComposeProject("hostresolver") + s.composeUp() +} + +func (s *HostResolverSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *HostResolverSuite) TestSimpleConfig() { + s.traefikCmd(withConfigFile("fixtures/simple_hostresolver.toml")) testCase := []struct { desc string @@ -43,10 +49,10 @@ func (s *HostResolverSuite) TestSimpleConfig(c *check.C) { for _, test := range testCase { req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = test.host - err = try.Request(req, 1*time.Second, try.StatusCodeIs(test.status), try.HasBody()) - c.Assert(err, checker.IsNil) + err = try.Request(req, 5*time.Second, try.StatusCodeIs(test.status), try.HasBody()) + require.NoError(s.T(), err) } } diff --git a/integration/http_test.go b/integration/http_test.go index 6e28fa2d7..5d9bff4dc 100644 --- a/integration/http_test.go +++ b/integration/http_test.go @@ -5,28 +5,27 @@ import ( "net" "net/http" "net/http/httptest" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/config/dynamic" - checker "github.com/vdemeester/shakers" ) type HTTPSuite struct{ BaseSuite } -func (s *HTTPSuite) TestSimpleConfiguration(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/http/simple.toml")) - defer display(c) +func TestHTTPSuite(t *testing.T) { + suite.Run(t, new(HTTPSuite)) +} - err := cmd.Start() - c.Assert(err, checker.IsNil) - - defer s.killCmd(cmd) +func (s *HTTPSuite) TestSimpleConfiguration() { + s.traefikCmd(withConfigFile("fixtures/http/simple.toml")) // Expect a 404 as we configured nothing. - err = try.GetRequest("http://127.0.0.1:8000/", time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/", time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) // Provide a configuration, fetched by Traefik provider. configuration := &dynamic.Configuration{ @@ -55,14 +54,14 @@ func (s *HTTPSuite) TestSimpleConfiguration(c *check.C) { } configData, err := json.Marshal(configuration) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) server := startTestServerWithResponse(configData) defer server.Close() // Expect configuration to be applied. err = try.GetRequest("http://127.0.0.1:9090/api/rawdata", 3*time.Second, try.BodyContains("routerHTTP@http", "serviceHTTP@http", "http://bacon:80")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } func startTestServerWithResponse(response []byte) (ts *httptest.Server) { diff --git a/integration/https_test.go b/integration/https_test.go index 89a88c880..e4a68dfb8 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -8,34 +8,36 @@ import ( "net/http" "net/http/httptest" "os" + "testing" "time" "github.com/BurntSushi/toml" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v3/pkg/tls" - checker "github.com/vdemeester/shakers" + "github.com/traefik/traefik/v3/pkg/types" ) // HTTPSSuite tests suite. type HTTPSSuite struct{ BaseSuite } +func TestHTTPSSuite(t *testing.T) { + suite.Run(t, &HTTPSSuite{}) +} + // TestWithSNIConfigHandshake involves a client sending a SNI hostname of // "snitest.com", which happens to match the CN of 'snitest.com.crt'. The test // verifies that traefik presents the correct certificate. -func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_sni.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *HTTPSSuite) TestWithSNIConfigHandshake() { + file := s.adaptFile("fixtures/https/https_sni.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)")) + require.NoError(s.T(), err) tlsConfig := &tls.Config{ InsecureSkipVerify: true, @@ -43,35 +45,30 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) { NextProtos: []string{"h2", "http/1.1"}, } conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) + assert.NoError(s.T(), err, "failed to connect to server") defer conn.Close() err = conn.Handshake() - c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error")) + assert.NoError(s.T(), err, "TLS handshake error") cs := conn.ConnectionState() err = cs.PeerCertificates[0].VerifyHostname("snitest.com") - c.Assert(err, checker.IsNil, check.Commentf("certificate did not match SNI servername")) + assert.NoError(s.T(), err, "certificate did not match SNI servername") proto := conn.ConnectionState().NegotiatedProtocol - c.Assert(proto, checker.Equals, "h2") + assert.Equal(s.T(), "h2", proto) } // TestWithSNIConfigRoute involves a client sending HTTPS requests with // SNI hostnames of "snitest.org" and "snitest.com". The test verifies // that traefik routes the requests to the expected backends. -func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_sni.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *HTTPSSuite) TestWithSNIConfigRoute() { + file := s.adaptFile("fixtures/https/https_sni.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) + require.NoError(s.T(), err) backend1 := startTestServer("9010", http.StatusNoContent, "") backend2 := startTestServer("9020", http.StatusResetContent, "") @@ -79,9 +76,9 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { defer backend2.Close() err = try.GetRequest(backend1.URL, 1*time.Second, try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest(backend2.URL, 1*time.Second, try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tr1 := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -97,37 +94,33 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { } req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } // TestWithTLSOptions verifies that traefik routes the requests with the associated tls options. -func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_tls_options.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + +func (s *HTTPSSuite) TestWithTLSOptions() { + file := s.adaptFile("fixtures/https/https_tls_options.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) + require.NoError(s.T(), err) backend1 := startTestServer("9010", http.StatusNoContent, "") backend2 := startTestServer("9020", http.StatusResetContent, "") @@ -135,9 +128,9 @@ func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) { defer backend2.Close() err = try.GetRequest(backend1.URL, 1*time.Second, try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest(backend2.URL, 1*time.Second, try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tr1 := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -165,27 +158,27 @@ func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) { // With valid TLS options and request req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // With a valid TLS version req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") err = try.RequestWithTransport(req, 3*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // With a bad TLS version req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr3.TLSClientConfig.ServerName req.Header.Set("Host", tr3.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") @@ -193,27 +186,23 @@ func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) { Transport: tr3, } _, err = client.Do(req) - c.Assert(err, checker.NotNil) - c.Assert(err.Error(), checker.Contains, "tls: no supported versions satisfy MinVersion and MaxVersion") + assert.Error(s.T(), err) + assert.Contains(s.T(), err.Error(), "tls: no supported versions satisfy MinVersion and MaxVersion") // with unknown tls option err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("unknown TLS options: unknown@file")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } // TestWithConflictingTLSOptions checks that routers with same SNI but different TLS options get fallbacked to the default TLS options. -func (s *HTTPSSuite) TestWithConflictingTLSOptions(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_tls_options.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + +func (s *HTTPSSuite) TestWithConflictingTLSOptions() { + file := s.adaptFile("fixtures/https/https_tls_options.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.net`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.net`)")) + require.NoError(s.T(), err) backend1 := startTestServer("9010", http.StatusNoContent, "") backend2 := startTestServer("9020", http.StatusResetContent, "") @@ -221,9 +210,9 @@ func (s *HTTPSSuite) TestWithConflictingTLSOptions(c *check.C) { defer backend2.Close() err = try.GetRequest(backend1.URL, 1*time.Second, try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest(backend2.URL, 1*time.Second, try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tr4 := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -243,17 +232,17 @@ func (s *HTTPSSuite) TestWithConflictingTLSOptions(c *check.C) { // With valid TLS options and request req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = trDefault.TLSClientConfig.ServerName req.Header.Set("Host", trDefault.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") err = try.RequestWithTransport(req, 30*time.Second, trDefault, try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // With a bad TLS version req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr4.TLSClientConfig.ServerName req.Header.Set("Host", tr4.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") @@ -261,29 +250,24 @@ func (s *HTTPSSuite) TestWithConflictingTLSOptions(c *check.C) { Transport: tr4, } _, err = client.Do(req) - c.Assert(err, checker.NotNil) - c.Assert(err.Error(), checker.Contains, "tls: no supported versions satisfy MinVersion and MaxVersion") + assert.ErrorContains(s.T(), err, "tls: no supported versions satisfy MinVersion and MaxVersion") // with unknown tls option err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(fmt.Sprintf("found different TLS options for routers on the same host %v, so using the default TLS options instead", tr4.TLSClientConfig.ServerName))) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } // TestWithSNIStrictNotMatchedRequest involves a client sending a SNI hostname of // "snitest.org", which does not match the CN of 'snitest.com.crt'. The test // verifies that traefik closes the connection. -func (s *HTTPSSuite) TestWithSNIStrictNotMatchedRequest(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_sni_strict.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + +func (s *HTTPSSuite) TestWithSNIStrictNotMatchedRequest() { + file := s.adaptFile("fixtures/https/https_sni_strict.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) + require.NoError(s.T(), err) tlsConfig := &tls.Config{ InsecureSkipVerify: true, @@ -292,24 +276,20 @@ func (s *HTTPSSuite) TestWithSNIStrictNotMatchedRequest(c *check.C) { } // Connection with no matching certificate should fail _, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.NotNil, check.Commentf("failed to connect to server")) + assert.Error(s.T(), err, "failed to connect to server") } // TestWithDefaultCertificate involves a client sending a SNI hostname of // "snitest.org", which does not match the CN of 'snitest.com.crt'. The test // verifies that traefik returns the default certificate. -func (s *HTTPSSuite) TestWithDefaultCertificate(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_sni_default_cert.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + +func (s *HTTPSSuite) TestWithDefaultCertificate() { + file := s.adaptFile("fixtures/https/https_sni_default_cert.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) + require.NoError(s.T(), err) tlsConfig := &tls.Config{ InsecureSkipVerify: true, @@ -317,71 +297,63 @@ func (s *HTTPSSuite) TestWithDefaultCertificate(c *check.C) { NextProtos: []string{"h2", "http/1.1"}, } conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) + assert.NoError(s.T(), err, "failed to connect to server") defer conn.Close() err = conn.Handshake() - c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error")) + assert.NoError(s.T(), err, "TLS handshake error") cs := conn.ConnectionState() err = cs.PeerCertificates[0].VerifyHostname("snitest.com") - c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate")) + assert.NoError(s.T(), err, "server did not serve correct default certificate") proto := cs.NegotiatedProtocol - c.Assert(proto, checker.Equals, "h2") + assert.Equal(s.T(), "h2", proto) } // TestWithDefaultCertificateNoSNI involves a client sending a request with no ServerName // which does not match the CN of 'snitest.com.crt'. The test // verifies that traefik returns the default certificate. -func (s *HTTPSSuite) TestWithDefaultCertificateNoSNI(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_sni_default_cert.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + +func (s *HTTPSSuite) TestWithDefaultCertificateNoSNI() { + file := s.adaptFile("fixtures/https/https_sni_default_cert.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) + require.NoError(s.T(), err) tlsConfig := &tls.Config{ InsecureSkipVerify: true, NextProtos: []string{"h2", "http/1.1"}, } conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) + assert.NoError(s.T(), err, "failed to connect to server") defer conn.Close() err = conn.Handshake() - c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error")) + assert.NoError(s.T(), err, "TLS handshake error") cs := conn.ConnectionState() err = cs.PeerCertificates[0].VerifyHostname("snitest.com") - c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate")) + assert.NoError(s.T(), err, "server did not serve correct default certificate") proto := cs.NegotiatedProtocol - c.Assert(proto, checker.Equals, "h2") + assert.Equal(s.T(), "h2", proto) } // TestWithOverlappingCertificate involves a client sending a SNI hostname of // "www.snitest.com", which matches the CN of two static certificates: // 'wildcard.snitest.com.crt', and `www.snitest.com.crt`. The test // verifies that traefik returns the non-wildcard certificate. -func (s *HTTPSSuite) TestWithOverlappingStaticCertificate(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_sni_default_cert.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + +func (s *HTTPSSuite) TestWithOverlappingStaticCertificate() { + file := s.adaptFile("fixtures/https/https_sni_default_cert.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) + require.NoError(s.T(), err) tlsConfig := &tls.Config{ InsecureSkipVerify: true, @@ -389,36 +361,32 @@ func (s *HTTPSSuite) TestWithOverlappingStaticCertificate(c *check.C) { NextProtos: []string{"h2", "http/1.1"}, } conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) + assert.NoError(s.T(), err, "failed to connect to server") defer conn.Close() err = conn.Handshake() - c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error")) + assert.NoError(s.T(), err, "TLS handshake error") cs := conn.ConnectionState() err = cs.PeerCertificates[0].VerifyHostname("www.snitest.com") - c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate")) + assert.NoError(s.T(), err, "server did not serve correct default certificate") proto := cs.NegotiatedProtocol - c.Assert(proto, checker.Equals, "h2") + assert.Equal(s.T(), "h2", proto) } // TestWithOverlappingCertificate involves a client sending a SNI hostname of // "www.snitest.com", which matches the CN of two dynamic certificates: // 'wildcard.snitest.com.crt', and `www.snitest.com.crt`. The test // verifies that traefik returns the non-wildcard certificate. -func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate(c *check.C) { - file := s.adaptFile(c, "fixtures/https/dynamic_https_sni_default_cert.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + +func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate() { + file := s.adaptFile("fixtures/https/dynamic_https_sni_default_cert.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) + require.NoError(s.T(), err) tlsConfig := &tls.Config{ InsecureSkipVerify: true, @@ -426,34 +394,30 @@ func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate(c *check.C) { NextProtos: []string{"h2", "http/1.1"}, } conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) + assert.NoError(s.T(), err, "failed to connect to server") defer conn.Close() err = conn.Handshake() - c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error")) + assert.NoError(s.T(), err, "TLS handshake error") cs := conn.ConnectionState() err = cs.PeerCertificates[0].VerifyHostname("www.snitest.com") - c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate")) + assert.NoError(s.T(), err, "server did not serve correct default certificate") proto := cs.NegotiatedProtocol - c.Assert(proto, checker.Equals, "h2") + assert.Equal(s.T(), "h2", proto) } // TestWithClientCertificateAuthentication // The client can send a certificate signed by a CA trusted by the server but it's optional. -func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) { - file := s.adaptFile(c, "fixtures/https/clientca/https_1ca1config.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + +func (s *HTTPSSuite) TestWithClientCertificateAuthentication() { + file := s.adaptFile("fixtures/https/clientca/https_1ca1config.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)")) + require.NoError(s.T(), err) tlsConfig := &tls.Config{ InsecureSkipVerify: true, @@ -462,25 +426,25 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) { } // Connection without client certificate should fail _, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("should be allowed to connect to server")) + assert.NoError(s.T(), err, "should be allowed to connect to server") // Connect with client certificate signed by ca1 cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key") - c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key")) + assert.NoError(s.T(), err, "unable to load client certificate and key") tlsConfig.Certificates = append(tlsConfig.Certificates, cert) conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) + assert.NoError(s.T(), err, "failed to connect to server") conn.Close() // Connect with client certificate not signed by ca1 cert, err = tls.LoadX509KeyPair("fixtures/https/snitest.org.cert", "fixtures/https/snitest.org.key") - c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key")) + assert.NoError(s.T(), err, "unable to load client certificate and key") tlsConfig.Certificates = append(tlsConfig.Certificates, cert) conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) + assert.NoError(s.T(), err, "failed to connect to server") conn.Close() @@ -491,16 +455,17 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) { Certificates: []tls.Certificate{}, } cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client2.crt", "fixtures/https/clientca/client2.key") - c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key")) + assert.NoError(s.T(), err, "unable to load client certificate and key") tlsConfig.Certificates = append(tlsConfig.Certificates, cert) _, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("should be allowed to connect to server")) + assert.NoError(s.T(), err, "should be allowed to connect to server") } // TestWithClientCertificateAuthentication // Use two CA:s and test that clients with client signed by either of them can connect. -func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check.C) { + +func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs() { server1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server1")) })) server2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server2")) })) defer func() { @@ -508,7 +473,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check server2.Close() }() - file := s.adaptFile(c, "fixtures/https/clientca/https_2ca1config.toml", struct { + file := s.adaptFile("fixtures/https/clientca/https_2ca1config.toml", struct { Server1 string Server2 string }{ @@ -516,19 +481,14 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check Server2: server2.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "snitest.com" tlsConfig := &tls.Config{ @@ -544,15 +504,15 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check // Connection without client certificate should fail _, err = client.Do(req) - c.Assert(err, checker.NotNil) + assert.Error(s.T(), err) cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key") - c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key")) + assert.NoError(s.T(), err, "unable to load client certificate and key") tlsConfig.Certificates = append(tlsConfig.Certificates, cert) // Connect with client signed by ca1 _, err = client.Do(req) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Connect with client signed by ca2 tlsConfig = &tls.Config{ @@ -562,7 +522,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check } cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client2.crt", "fixtures/https/clientca/client2.key") - c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key")) + assert.NoError(s.T(), err, "unable to load client certificate and key") tlsConfig.Certificates = append(tlsConfig.Certificates, cert) client = http.Client{ @@ -572,7 +532,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check // Connect with client signed by ca1 _, err = client.Do(req) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Connect with client signed by ca3 should fail tlsConfig = &tls.Config{ @@ -582,7 +542,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check } cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client3.crt", "fixtures/https/clientca/client3.key") - c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key")) + assert.NoError(s.T(), err, "unable to load client certificate and key") tlsConfig.Certificates = append(tlsConfig.Certificates, cert) client = http.Client{ @@ -592,12 +552,13 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check // Connect with client signed by ca1 _, err = client.Do(req) - c.Assert(err, checker.NotNil) + assert.Error(s.T(), err) } // TestWithClientCertificateAuthentication // Use two CA:s in two different files and test that clients with client signed by either of them can connect. -func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleFiles(c *check.C) { + +func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleFiles() { server1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server1")) })) server2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { _, _ = rw.Write([]byte("server2")) })) defer func() { @@ -605,26 +566,21 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleF server2.Close() }() - file := s.adaptFile(c, "fixtures/https/clientca/https_2ca2config.toml", struct { + file := s.adaptFile("fixtures/https/clientca/https_2ca2config.toml", struct { Server1 string Server2 string }{ Server1: server1.URL, Server2: server2.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "snitest.com" tlsConfig := &tls.Config{ @@ -640,15 +596,15 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleF // Connection without client certificate should fail _, err = client.Do(req) - c.Assert(err, checker.NotNil) + assert.Error(s.T(), err) // Connect with client signed by ca1 cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key") - c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key")) + assert.NoError(s.T(), err, "unable to load client certificate and key") tlsConfig.Certificates = append(tlsConfig.Certificates, cert) _, err = client.Do(req) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Connect with client signed by ca2 tlsConfig = &tls.Config{ @@ -658,7 +614,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleF } cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client2.crt", "fixtures/https/clientca/client2.key") - c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key")) + assert.NoError(s.T(), err, "unable to load client certificate and key") tlsConfig.Certificates = append(tlsConfig.Certificates, cert) client = http.Client{ @@ -667,7 +623,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleF } _, err = client.Do(req) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Connect with client signed by ca3 should fail tlsConfig = &tls.Config{ @@ -677,7 +633,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleF } cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client3.crt", "fixtures/https/clientca/client3.key") - c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key")) + assert.NoError(s.T(), err, "unable to load client certificate and key") tlsConfig.Certificates = append(tlsConfig.Certificates, cert) client = http.Client{ @@ -686,51 +642,41 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleF } _, err = client.Do(req) - c.Assert(err, checker.NotNil) + assert.Error(s.T(), err) } -func (s *HTTPSSuite) TestWithRootCAsContentForHTTPSOnBackend(c *check.C) { +func (s *HTTPSSuite) TestWithRootCAsContentForHTTPSOnBackend() { backend := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) defer backend.Close() - file := s.adaptFile(c, "fixtures/https/rootcas/https.toml", struct{ BackendHost string }{backend.URL}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + file := s.adaptFile("fixtures/https/rootcas/https.toml", struct{ BackendHost string }{backend.URL}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(backend.URL)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(backend.URL)) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8081/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *HTTPSSuite) TestWithRootCAsFileForHTTPSOnBackend(c *check.C) { +func (s *HTTPSSuite) TestWithRootCAsFileForHTTPSOnBackend() { backend := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) defer backend.Close() - file := s.adaptFile(c, "fixtures/https/rootcas/https_with_file.toml", struct{ BackendHost string }{backend.URL}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + file := s.adaptFile("fixtures/https/rootcas/https_with_file.toml", struct{ BackendHost string }{backend.URL}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(backend.URL)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(backend.URL)) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8081/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } func startTestServer(port string, statusCode int, textContent string) (ts *httptest.Server) { @@ -757,20 +703,14 @@ func startTestServer(port string, statusCode int, textContent string) (ts *httpt // SNI hostnames of "snitest.org" and "snitest.com". The test verifies // that traefik routes the requests to the expected backends thanks to given certificate if possible // otherwise thanks to the default one. -func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) { - dynamicConfFileName := s.adaptFile(c, "fixtures/https/dynamic_https.toml", struct{}{}) - defer os.Remove(dynamicConfFileName) - confFileName := s.adaptFile(c, "fixtures/https/dynamic_https_sni.toml", struct { +func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange() { + dynamicConfFileName := s.adaptFile("fixtures/https/dynamic_https.toml", struct{}{}) + confFileName := s.adaptFile("fixtures/https/dynamic_https_sni.toml", struct { DynamicConfFileName string }{ DynamicConfFileName: dynamicConfFileName, }) - defer os.Remove(confFileName) - cmd, display := s.traefikCmd(withConfigFile(confFileName)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(confFileName)) tr1 := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -787,8 +727,8 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) { } // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr1.TLSClientConfig.ServerName+"`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr1.TLSClientConfig.ServerName+"`)")) + require.NoError(s.T(), err) backend1 := startTestServer("9010", http.StatusNoContent, "") backend2 := startTestServer("9020", http.StatusResetContent, "") @@ -796,29 +736,29 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) { defer backend2.Close() err = try.GetRequest(backend1.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") // snitest.org certificate must be used yet && Expected a 204 (from backend1) err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") // snitest.com certificate does not exist, default certificate has to be used && Expected a 205 (from backend2) err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("TRAEFIK DEFAULT CERT"), try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } // TestWithSNIDynamicConfigRouteWithChange involves a client sending HTTPS requests with @@ -826,20 +766,15 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) { // that traefik updates its configuration when the HTTPS configuration is modified and // it routes the requests to the expected backends thanks to given certificate if possible // otherwise thanks to the default one. -func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) { - dynamicConfFileName := s.adaptFile(c, "fixtures/https/dynamic_https.toml", struct{}{}) - defer os.Remove(dynamicConfFileName) - confFileName := s.adaptFile(c, "fixtures/https/dynamic_https_sni.toml", struct { + +func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange() { + dynamicConfFileName := s.adaptFile("fixtures/https/dynamic_https.toml", struct{}{}) + confFileName := s.adaptFile("fixtures/https/dynamic_https_sni.toml", struct { DynamicConfFileName string }{ DynamicConfFileName: dynamicConfFileName, }) - defer os.Remove(confFileName) - cmd, display := s.traefikCmd(withConfigFile(confFileName)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(confFileName)) tr1 := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -856,8 +791,8 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) { } // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) + require.NoError(s.T(), err) backend1 := startTestServer("9010", http.StatusNoContent, "") backend2 := startTestServer("9020", http.StatusResetContent, "") @@ -865,30 +800,30 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) { defer backend2.Close() err = try.GetRequest(backend1.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Change certificates configuration file content - modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName) + s.modifyCertificateConfFileContent(tr1.TLSClientConfig.ServerName, dynamicConfFileName) req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("TRAEFIK DEFAULT CERT"), try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } // TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion involves a client sending HTTPS requests with @@ -896,20 +831,15 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) { // that traefik updates its configuration when the HTTPS configuration is modified, even if it totally deleted, and // it routes the requests to the expected backends thanks to given certificate if possible // otherwise thanks to the default one. -func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c *check.C) { - dynamicConfFileName := s.adaptFile(c, "fixtures/https/dynamic_https.toml", struct{}{}) - defer os.Remove(dynamicConfFileName) - confFileName := s.adaptFile(c, "fixtures/https/dynamic_https_sni.toml", struct { + +func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion() { + dynamicConfFileName := s.adaptFile("fixtures/https/dynamic_https.toml", struct{}{}) + confFileName := s.adaptFile("fixtures/https/dynamic_https_sni.toml", struct { DynamicConfFileName string }{ DynamicConfFileName: dynamicConfFileName, }) - defer os.Remove(confFileName) - cmd, display := s.traefikCmd(withConfigFile(confFileName)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(confFileName)) tr2 := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -919,41 +849,41 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c } // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) + require.NoError(s.T(), err) backend2 := startTestServer("9020", http.StatusResetContent, "") defer backend2.Close() err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Change certificates configuration file content - modifyCertificateConfFileContent(c, "", dynamicConfFileName) + s.modifyCertificateConfFileContent("", dynamicConfFileName) err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("TRAEFIK DEFAULT CERT"), try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } // modifyCertificateConfFileContent replaces the content of a HTTPS configuration file. -func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName string) { +func (s *HTTPSSuite) modifyCertificateConfFileContent(certFileName, confFileName string) { file, err := os.OpenFile("./"+confFileName, os.O_WRONLY, os.ModeExclusive) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) defer func() { file.Close() }() err = file.Truncate(0) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // If certificate file is not provided, just truncate the configuration file if len(certFileName) > 0 { @@ -962,8 +892,8 @@ func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName str Certificates: []*traefiktls.CertAndStores{ { Certificate: traefiktls.Certificate{ - CertFile: traefiktls.FileOrContent("fixtures/https/" + certFileName + ".cert"), - KeyFile: traefiktls.FileOrContent("fixtures/https/" + certFileName + ".key"), + CertFile: types.FileOrContent("fixtures/https/" + certFileName + ".cert"), + KeyFile: types.FileOrContent("fixtures/https/" + certFileName + ".key"), }, }, }, @@ -972,25 +902,20 @@ func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName str var confBuffer bytes.Buffer err := toml.NewEncoder(&confBuffer).Encode(tlsConf) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = file.Write(confBuffer.Bytes()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } } -func (s *HTTPSSuite) TestEntryPointHttpsRedirectAndPathModification(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_redirect.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *HTTPSSuite) TestEntryPointHttpsRedirectAndPathModification() { + file := s.adaptFile("fixtures/https/https_redirect.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.BodyContains("Host(`example.com`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.BodyContains("Host(`example.com`)")) + require.NoError(s.T(), err) client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { @@ -1064,17 +989,17 @@ func (s *HTTPSSuite) TestEntryPointHttpsRedirectAndPathModification(c *check.C) sourceURL := fmt.Sprintf("http://127.0.0.1:8888%s", test.path) for _, host := range test.hosts { req, err := http.NewRequest(http.MethodGet, sourceURL, nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = host resp, err := client.Do(req) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) resp.Body.Close() location := resp.Header.Get("Location") expected := "https://" + net.JoinHostPort(host, "8443") + test.path - c.Assert(location, checker.Equals, expected) + assert.Equal(s.T(), expected, location) } } } @@ -1082,18 +1007,13 @@ func (s *HTTPSSuite) TestEntryPointHttpsRedirectAndPathModification(c *check.C) // TestWithSNIDynamicCaseInsensitive involves a client sending a SNI hostname of // "bar.www.snitest.com", which matches the DNS SAN of '*.WWW.SNITEST.COM'. The test // verifies that traefik presents the correct certificate. -func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) { - file := s.adaptFile(c, "fixtures/https/https_sni_case_insensitive_dynamic.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive() { + file := s.adaptFile("fixtures/https/https_sni_case_insensitive_dynamic.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("HostRegexp(`[a-z1-9-]+\\\\.www\\\\.snitest\\\\.com`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("HostRegexp(`[a-z1-9-]+\\\\.www\\\\.snitest\\\\.com`)")) + require.NoError(s.T(), err) tlsConfig := &tls.Config{ InsecureSkipVerify: true, @@ -1101,22 +1021,22 @@ func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) { NextProtos: []string{"h2", "http/1.1"}, } conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) + assert.NoError(s.T(), err, "failed to connect to server") defer conn.Close() err = conn.Handshake() - c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error")) + assert.NoError(s.T(), err, "TLS handshake error") cs := conn.ConnectionState() err = cs.PeerCertificates[0].VerifyHostname("*.WWW.SNITEST.COM") - c.Assert(err, checker.IsNil, check.Commentf("certificate did not match SNI servername")) + assert.NoError(s.T(), err, "certificate did not match SNI servername") proto := conn.ConnectionState().NegotiatedProtocol - c.Assert(proto, checker.Equals, "h2") + assert.Equal(s.T(), "h2", proto) } // TestWithDomainFronting verify the domain fronting behavior -func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) { +func (s *HTTPSSuite) TestWithDomainFronting() { backend := startTestServer("9010", http.StatusOK, "server1") defer backend.Close() backend2 := startTestServer("9020", http.StatusOK, "server2") @@ -1124,17 +1044,12 @@ func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) { backend3 := startTestServer("9030", http.StatusOK, "server3") defer backend3.Close() - file := s.adaptFile(c, "fixtures/https/https_domain_fronting.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + file := s.adaptFile("fixtures/https/https_domain_fronting.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`site1.www.snitest.com`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`site1.www.snitest.com`)")) + require.NoError(s.T(), err) testCases := []struct { desc string @@ -1222,34 +1137,29 @@ func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) { test := test req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = test.hostHeader err = try.RequestWithTransport(req, 500*time.Millisecond, &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true, ServerName: test.serverName}}, try.StatusCodeIs(test.expectedStatusCode), try.BodyContains(test.expectedContent)) if test.expectedError { - c.Assert(err, checker.NotNil) + assert.Error(s.T(), err) } else { - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } } } // TestWithInvalidTLSOption verifies the behavior when using an invalid tlsOption configuration. -func (s *HTTPSSuite) TestWithInvalidTLSOption(c *check.C) { +func (s *HTTPSSuite) TestWithInvalidTLSOption() { backend := startTestServer("9010", http.StatusOK, "server1") defer backend.Close() - file := s.adaptFile(c, "fixtures/https/https_invalid_tls_options.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + file := s.adaptFile("fixtures/https/https_invalid_tls_options.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) + require.NoError(s.T(), err) testCases := []struct { desc string @@ -1279,7 +1189,7 @@ func (s *HTTPSSuite) TestWithInvalidTLSOption(c *check.C) { } conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) - c.Assert(err, checker.NotNil, check.Commentf("connected to server successfully")) - c.Assert(conn, checker.IsNil) + assert.Error(s.T(), err, "connected to server successfully") + assert.Nil(s.T(), conn) } } diff --git a/integration/integration_test.go b/integration/integration_test.go index 6c13e75b3..7149d8fd8 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -7,208 +7,332 @@ import ( "errors" "flag" "fmt" + "io" "io/fs" stdlog "log" + "net/http" "os" "os/exec" "path/filepath" + "regexp" + "runtime" + "slices" "strings" "testing" "text/template" "time" - "github.com/compose-spec/compose-go/cli" - "github.com/compose-spec/compose-go/types" - "github.com/docker/cli/cli/config/configfile" - "github.com/docker/compose/v2/cmd/formatter" - composeapi "github.com/docker/compose/v2/pkg/api" - "github.com/docker/compose/v2/pkg/compose" - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/client" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" + dockernetwork "github.com/docker/docker/api/types/network" "github.com/fatih/structs" - "github.com/go-check/check" - "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/sirupsen/logrus" - "github.com/traefik/traefik/v3/pkg/logs" - checker "github.com/vdemeester/shakers" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/network" + "github.com/traefik/traefik/v3/integration/try" + "gopkg.in/yaml.v3" ) -var ( - integration = flag.Bool("integration", false, "run integration tests") - showLog = flag.Bool("tlog", false, "always show Traefik logs") -) +var showLog = flag.Bool("tlog", false, "always show Traefik logs") -func Test(t *testing.T) { - if !*integration { - log.Info().Msg("Integration tests disabled.") - return - } - - log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}). - With().Timestamp().Caller().Logger() - zerolog.SetGlobalLevel(zerolog.InfoLevel) - - // Global logrus replacement - logrus.StandardLogger().Out = logs.NoLevel(log.Logger, zerolog.DebugLevel) - - // configure default standard log. - stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags) - stdlog.SetOutput(logs.NoLevel(log.Logger, zerolog.DebugLevel)) - - // TODO(mpl): very niche optimization: do not start tailscale if none of the wanted tests actually need it (e.g. KeepAliveSuite does not). - var ( - vpn *tailscaleNotSuite - useVPN bool - ) - if os.Getenv("IN_DOCKER") != "true" { - if vpn = setupVPN(nil, "tailscale.secret"); vpn != nil { - defer vpn.TearDownSuite(nil) - useVPN = true - } - } - - check.Suite(&AccessLogSuite{}) - if !useVPN { - check.Suite(&AcmeSuite{}) - } - check.Suite(&ConsulCatalogSuite{}) - check.Suite(&ConsulSuite{}) - check.Suite(&DockerComposeSuite{}) - check.Suite(&DockerSuite{}) - check.Suite(&ErrorPagesSuite{}) - check.Suite(&EtcdSuite{}) - check.Suite(&FileSuite{}) - check.Suite(&GRPCSuite{}) - check.Suite(&HeadersSuite{}) - check.Suite(&HealthCheckSuite{}) - check.Suite(&HostResolverSuite{}) - check.Suite(&HTTPSSuite{}) - check.Suite(&HTTPSuite{}) - if !useVPN { - check.Suite(&K8sSuite{}) - } - check.Suite(&KeepAliveSuite{}) - check.Suite(&LogRotationSuite{}) - if !useVPN { - check.Suite(&ProxyProtocolSuite{}) - } - check.Suite(&RateLimitSuite{}) - check.Suite(&RedisSuite{}) - check.Suite(&RestSuite{}) - check.Suite(&RetrySuite{}) - check.Suite(&SimpleSuite{}) - check.Suite(&TCPSuite{}) - check.Suite(&TimeoutSuite{}) - check.Suite(&ThrottlingSuite{}) - check.Suite(&TLSClientHeadersSuite{}) - check.Suite(&TracingSuite{}) - check.Suite(&UDPSuite{}) - check.Suite(&WebsocketSuite{}) - check.Suite(&ZookeeperSuite{}) - - check.TestingT(t) +type composeConfig struct { + Services map[string]composeService `yaml:"services"` } -var traefikBinary = "../dist/traefik" +type composeService struct { + Image string `yaml:"image"` + Labels map[string]string `yaml:"labels"` + Hostname string `yaml:"hostname"` + Volumes []string `yaml:"volumes"` + CapAdd []string `yaml:"cap_add"` + Command []string `yaml:"command"` + Environment map[string]string `yaml:"environment"` + Privileged bool `yaml:"privileged"` + Deploy composeDeploy `yaml:"deploy"` +} + +type composeDeploy struct { + Replicas int `yaml:"replicas"` +} type BaseSuite struct { - composeProject *types.Project - dockerComposeService composeapi.Service - dockerClient *client.Client + suite.Suite + containers map[string]testcontainers.Container + network *testcontainers.DockerNetwork + hostIP string } -func (s *BaseSuite) TearDownSuite(c *check.C) { - if s.composeProject != nil && s.dockerComposeService != nil { - s.composeDown(c) +func (s *BaseSuite) waitForTraefik(containerName string) { + time.Sleep(1 * time.Second) + + // Wait for Traefik to turn ready. + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil) + require.NoError(s.T(), err) + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains(containerName)) + require.NoError(s.T(), err) +} + +func (s *BaseSuite) displayTraefikLogFile(path string) { + if s.T().Failed() { + if _, err := os.Stat(path); !os.IsNotExist(err) { + content, errRead := os.ReadFile(path) + // TODO TestName + // fmt.Printf("%s: Traefik logs: \n", c.TestName()) + fmt.Print("Traefik logs: \n") + if errRead == nil { + fmt.Println(content) + } else { + fmt.Println(errRead) + } + } else { + // fmt.Printf("%s: No Traefik logs.\n", c.TestName()) + fmt.Print("No Traefik logs.\n") + } + errRemove := os.Remove(path) + if errRemove != nil { + fmt.Println(errRemove) + } } } +func (s *BaseSuite) SetupSuite() { + // configure default standard log. + stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags) + // TODO + // stdlog.SetOutput(log.Logger) + + ctx := context.Background() + // Create docker network + // docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24 + var opts []network.NetworkCustomizer + opts = append(opts, network.WithDriver("bridge")) + opts = append(opts, network.WithIPAM(&dockernetwork.IPAM{ + Driver: "default", + Config: []dockernetwork.IPAMConfig{ + { + Subnet: "172.31.42.0/24", + }, + }, + })) + dockerNetwork, err := network.New(ctx, opts...) + require.NoError(s.T(), err) + + s.network = dockerNetwork + s.hostIP = "172.31.42.1" + if isDockerDesktop(ctx, s.T()) { + s.hostIP = getDockerDesktopHostIP(ctx, s.T()) + s.setupVPN("tailscale.secret") + } +} + +func getDockerDesktopHostIP(ctx context.Context, t *testing.T) string { + t.Helper() + + req := testcontainers.ContainerRequest{ + Image: "alpine", + HostConfigModifier: func(config *container.HostConfig) { + config.AutoRemove = true + }, + Cmd: []string{"getent", "hosts", "host.docker.internal"}, + } + + con, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + require.NoError(t, err) + + closer, err := con.Logs(ctx) + require.NoError(t, err) + + all, err := io.ReadAll(closer) + require.NoError(t, err) + + ipRegex := regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`) + matches := ipRegex.FindAllString(string(all), -1) + require.Len(t, matches, 1) + + return matches[0] +} + +func isDockerDesktop(ctx context.Context, t *testing.T) bool { + t.Helper() + + cli, err := testcontainers.NewDockerClientWithOpts(ctx) + if err != nil { + t.Fatalf("failed to create docker client: %s", err) + } + + info, err := cli.Info(ctx) + if err != nil { + t.Fatalf("failed to get docker info: %s", err) + } + + return info.OperatingSystem == "Docker Desktop" +} + +func (s *BaseSuite) TearDownSuite() { + s.composeDown() + + err := try.Do(5*time.Second, func() error { + if s.network != nil { + err := s.network.Remove(context.Background()) + if err != nil { + return err + } + } + + return nil + }) + require.NoError(s.T(), err) +} + // createComposeProject creates the docker compose project stored as a field in the BaseSuite. // This method should be called before starting and/or stopping compose services. -func (s *BaseSuite) createComposeProject(c *check.C, name string) { - projectName := fmt.Sprintf("traefik-integration-test-%s", name) +func (s *BaseSuite) createComposeProject(name string) { composeFile := fmt.Sprintf("resources/compose/%s.yml", name) - var err error - s.dockerClient, err = client.NewClientWithOpts() - c.Assert(err, checker.IsNil) + file, err := os.ReadFile(composeFile) + require.NoError(s.T(), err) - s.dockerComposeService = compose.NewComposeService(s.dockerClient, &configfile.ConfigFile{}) - ops, err := cli.NewProjectOptions([]string{composeFile}, cli.WithName(projectName)) - c.Assert(err, checker.IsNil) + var composeConfigData composeConfig + err = yaml.Unmarshal(file, &composeConfigData) + require.NoError(s.T(), err) - s.composeProject, err = cli.ProjectFromOptions(ops) - c.Assert(err, checker.IsNil) + if s.containers == nil { + s.containers = map[string]testcontainers.Container{} + } + + ctx := context.Background() + + for id, containerConfig := range composeConfigData.Services { + var mounts []mount.Mount + for _, volume := range containerConfig.Volumes { + split := strings.Split(volume, ":") + if len(split) != 2 { + continue + } + + if strings.HasPrefix(split[0], "./") { + path, err := os.Getwd() + if err != nil { + log.Err(err).Msg("can't determine current directory") + continue + } + split[0] = strings.Replace(split[0], "./", path+"/", 1) + } + + abs, err := filepath.Abs(split[0]) + require.NoError(s.T(), err) + + mounts = append(mounts, mount.Mount{Source: abs, Target: split[1], Type: mount.TypeBind}) + } + + if containerConfig.Deploy.Replicas > 0 { + for i := 0; i < containerConfig.Deploy.Replicas; i++ { + id = fmt.Sprintf("%s-%d", id, i+1) + con, err := s.createContainer(ctx, containerConfig, id, mounts) + require.NoError(s.T(), err) + s.containers[id] = con + } + continue + } + + con, err := s.createContainer(ctx, containerConfig, id, mounts) + require.NoError(s.T(), err) + s.containers[id] = con + } +} + +func (s *BaseSuite) createContainer(ctx context.Context, containerConfig composeService, id string, mounts []mount.Mount) (testcontainers.Container, error) { + req := testcontainers.ContainerRequest{ + Image: containerConfig.Image, + Env: containerConfig.Environment, + Cmd: containerConfig.Command, + Labels: containerConfig.Labels, + Name: id, + Hostname: containerConfig.Hostname, + Privileged: containerConfig.Privileged, + Networks: []string{s.network.Name}, + HostConfigModifier: func(config *container.HostConfig) { + if containerConfig.CapAdd != nil { + config.CapAdd = containerConfig.CapAdd + } + if !isDockerDesktop(ctx, s.T()) { + config.ExtraHosts = append(config.ExtraHosts, "host.docker.internal:"+s.hostIP) + } + config.Mounts = mounts + }, + } + con, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: false, + }) + + return con, err } // composeUp starts the given services of the current docker compose project, if they are not already started. // Already running services are not affected (i.e. not stopped). -func (s *BaseSuite) composeUp(c *check.C, services ...string) { - c.Assert(s.composeProject, check.NotNil) - c.Assert(s.dockerComposeService, check.NotNil) - - // We use Create and Restart instead of Up, because the only option that actually works to control which containers - // are started is within the RestartOptions. - err := s.dockerComposeService.Create(context.Background(), s.composeProject, composeapi.CreateOptions{}) - c.Assert(err, checker.IsNil) - - err = s.dockerComposeService.Restart(context.Background(), s.composeProject, composeapi.RestartOptions{Services: services}) - c.Assert(err, checker.IsNil) -} - -// composeExec runs the command in the given args in the given compose service container. -// Already running services are not affected (i.e. not stopped). -func (s *BaseSuite) composeExec(c *check.C, service string, args ...string) { - c.Assert(s.composeProject, check.NotNil) - c.Assert(s.dockerComposeService, check.NotNil) - - _, err := s.dockerComposeService.Exec(context.Background(), s.composeProject.Name, composeapi.RunOptions{ - Service: service, - Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, - Command: args, - Tty: false, - Index: 1, - }) - c.Assert(err, checker.IsNil) +func (s *BaseSuite) composeUp(services ...string) { + for name, con := range s.containers { + if len(services) == 0 || slices.Contains(services, name) { + err := con.Start(context.Background()) + require.NoError(s.T(), err) + } + } } // composeStop stops the given services of the current docker compose project and removes the corresponding containers. -func (s *BaseSuite) composeStop(c *check.C, services ...string) { - c.Assert(s.dockerComposeService, check.NotNil) - c.Assert(s.composeProject, check.NotNil) - - err := s.dockerComposeService.Stop(context.Background(), s.composeProject, composeapi.StopOptions{Services: services}) - c.Assert(err, checker.IsNil) - - err = s.dockerComposeService.Remove(context.Background(), s.composeProject, composeapi.RemoveOptions{ - Services: services, - Force: true, - }) - c.Assert(err, checker.IsNil) +func (s *BaseSuite) composeStop(services ...string) { + for name, con := range s.containers { + if len(services) == 0 || slices.Contains(services, name) { + timeout := 10 * time.Second + err := con.Stop(context.Background(), &timeout) + require.NoError(s.T(), err) + } + } } // composeDown stops all compose project services and removes the corresponding containers. -func (s *BaseSuite) composeDown(c *check.C) { - c.Assert(s.dockerComposeService, check.NotNil) - c.Assert(s.composeProject, check.NotNil) - - err := s.dockerComposeService.Down(context.Background(), s.composeProject.Name, composeapi.DownOptions{}) - c.Assert(err, checker.IsNil) +func (s *BaseSuite) composeDown() { + for _, c := range s.containers { + err := c.Terminate(context.Background()) + require.NoError(s.T(), err) + } + s.containers = map[string]testcontainers.Container{} } func (s *BaseSuite) cmdTraefik(args ...string) (*exec.Cmd, *bytes.Buffer) { - cmd := exec.Command(traefikBinary, args...) + binName := "traefik" + if runtime.GOOS == "windows" { + binName += ".exe" + } + + traefikBinPath := filepath.Join("..", "dist", runtime.GOOS, runtime.GOARCH, binName) + cmd := exec.Command(traefikBinPath, args...) + + s.T().Cleanup(func() { + s.killCmd(cmd) + }) var out bytes.Buffer cmd.Stdout = &out cmd.Stderr = &out + + err := cmd.Start() + require.NoError(s.T(), err) + return cmd, &out } func (s *BaseSuite) killCmd(cmd *exec.Cmd) { + if cmd.Process == nil { + log.Error().Msg("No process to kill") + return + } err := cmd.Process.Kill() if err != nil { log.Error().Err(err).Msg("Kill") @@ -217,15 +341,18 @@ func (s *BaseSuite) killCmd(cmd *exec.Cmd) { time.Sleep(100 * time.Millisecond) } -func (s *BaseSuite) traefikCmd(args ...string) (*exec.Cmd, func(*check.C)) { +func (s *BaseSuite) traefikCmd(args ...string) *exec.Cmd { cmd, out := s.cmdTraefik(args...) - return cmd, func(c *check.C) { - if c.Failed() || *showLog { + + s.T().Cleanup(func() { + if s.T().Failed() || *showLog { s.displayLogK3S() - s.displayLogCompose(c) - s.displayTraefikLog(c, out) + s.displayLogCompose() + s.displayTraefikLog(out) } - } + }) + + return cmd } func (s *BaseSuite) displayLogK3S() { @@ -242,30 +369,33 @@ func (s *BaseSuite) displayLogK3S() { log.Print() } -func (s *BaseSuite) displayLogCompose(c *check.C) { - if s.dockerComposeService == nil || s.composeProject == nil { - log.Info().Str("testName", c.TestName()).Msg("No docker compose logs.") - return +func (s *BaseSuite) displayLogCompose() { + for name, ctn := range s.containers { + readCloser, err := ctn.Logs(context.Background()) + require.NoError(s.T(), err) + for { + b := make([]byte, 1024) + _, err := readCloser.Read(b) + if errors.Is(err, io.EOF) { + break + } + require.NoError(s.T(), err) + + trimLogs := bytes.Trim(bytes.TrimSpace(b), string([]byte{0})) + if len(trimLogs) > 0 { + log.Info().Str("container", name).Msg(string(trimLogs)) + } + } } - - log.Info().Str("testName", c.TestName()).Msg("docker compose logs") - - logConsumer := formatter.NewLogConsumer(context.Background(), logs.NoLevel(log.Logger, zerolog.InfoLevel), false, true) - - err := s.dockerComposeService.Logs(context.Background(), s.composeProject.Name, logConsumer, composeapi.LogOptions{}) - c.Assert(err, checker.IsNil) - - log.Print() - log.Print("################################") - log.Print() } -func (s *BaseSuite) displayTraefikLog(c *check.C, output *bytes.Buffer) { +func (s *BaseSuite) displayTraefikLog(output *bytes.Buffer) { if output == nil || output.Len() == 0 { - log.Info().Str("testName", c.TestName()).Msg("No Traefik logs.") + log.Info().Msg("No Traefik logs.") } else { - log.Info().Str("testName", c.TestName()). - Str("logs", output.String()).Msg("Traefik logs") + for _, line := range strings.Split(output.String(), "\n") { + log.Info().Msg(line) + } } } @@ -279,67 +409,47 @@ func (s *BaseSuite) getDockerHost() string { return dockerHost } -func (s *BaseSuite) adaptFile(c *check.C, path string, tempObjects interface{}) string { +func (s *BaseSuite) adaptFile(path string, tempObjects interface{}) string { // Load file tmpl, err := template.ParseFiles(path) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) folder, prefix := filepath.Split(path) tmpFile, err := os.CreateTemp(folder, strings.TrimSuffix(prefix, filepath.Ext(prefix))+"_*"+filepath.Ext(prefix)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) defer tmpFile.Close() model := structs.Map(tempObjects) model["SelfFilename"] = tmpFile.Name() err = tmpl.ExecuteTemplate(tmpFile, prefix, model) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = tmpFile.Sync() - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) + s.T().Cleanup(func() { + os.Remove(tmpFile.Name()) + }) return tmpFile.Name() } -func (s *BaseSuite) getComposeServiceIP(c *check.C, name string) string { - filter := filters.NewArgs( - filters.Arg("label", fmt.Sprintf("%s=%s", composeapi.ProjectLabel, s.composeProject.Name)), - filters.Arg("label", fmt.Sprintf("%s=%s", composeapi.ServiceLabel, name)), - ) - - containers, err := s.dockerClient.ContainerList(context.Background(), dockertypes.ContainerListOptions{Filters: filter}) - c.Assert(err, checker.IsNil) - c.Assert(containers, checker.HasLen, 1) - - networkNames := s.composeProject.NetworkNames() - c.Assert(networkNames, checker.HasLen, 1) - - network := s.composeProject.Networks[networkNames[0]] - return containers[0].NetworkSettings.Networks[network.Name].IPAddress -} - -func (s *BaseSuite) getContainerIP(c *check.C, name string) string { - container, err := s.dockerClient.ContainerInspect(context.Background(), name) - c.Assert(err, checker.IsNil) - c.Assert(container.NetworkSettings.Networks, check.NotNil) - - for _, network := range container.NetworkSettings.Networks { - return network.IPAddress +func (s *BaseSuite) getComposeServiceIP(name string) string { + container, ok := s.containers[name] + if !ok { + return "" } - - // Should never happen. - c.Error("No network found") - return "" + ip, err := container.ContainerIP(context.Background()) + if err != nil { + return "" + } + return ip } func withConfigFile(file string) string { return "--configFile=" + file } -// tailscaleNotSuite includes a BaseSuite out of convenience, so we can benefit -// from composeUp et co., but it is not meant to function as a TestSuite per se. -type tailscaleNotSuite struct{ BaseSuite } - // setupVPN starts Tailscale on the corresponding container, and makes it a subnet // router, for all the other containers (whoamis, etc) subsequently started for the // integration tests. @@ -355,25 +465,35 @@ type tailscaleNotSuite struct{ BaseSuite } // "172.0.0.0/8": ["your_tailscale_identity"], // }, // }, -// -// TODO(mpl): we could maybe even move this setup to the Makefile, to start it -// and let it run (forever, or until voluntarily stopped). -func setupVPN(c *check.C, keyFile string) *tailscaleNotSuite { +func (s *BaseSuite) setupVPN(keyFile string) { data, err := os.ReadFile(keyFile) if err != nil { if !errors.Is(err, fs.ErrNotExist) { - log.Fatal().Err(err).Send() + log.Error().Err(err).Send() } - return nil + + return } authKey := strings.TrimSpace(string(data)) - // TODO: copy and create versions that don't need a check.C? - vpn := &tailscaleNotSuite{} - vpn.createComposeProject(c, "tailscale") - vpn.composeUp(c) + // // TODO: copy and create versions that don't need a check.C? + s.createComposeProject("tailscale") + s.composeUp() time.Sleep(5 * time.Second) // If we ever change the docker subnet in the Makefile, // we need to change this one below correspondingly. - vpn.composeExec(c, "tailscaled", "tailscale", "up", "--authkey="+authKey, "--advertise-routes=172.31.42.0/24") - return vpn + s.composeExec("tailscaled", "tailscale", "up", "--authkey="+authKey, "--advertise-routes=172.31.42.0/24") +} + +// composeExec runs the command in the given args in the given compose service container. +// Already running services are not affected (i.e. not stopped). +func (s *BaseSuite) composeExec(service string, args ...string) string { + require.Contains(s.T(), s.containers, service) + + _, reader, err := s.containers[service].Exec(context.Background(), args) + require.NoError(s.T(), err) + + content, err := io.ReadAll(reader) + require.NoError(s.T(), err) + + return string(content) } diff --git a/integration/k8s_test.go b/integration/k8s_test.go index 5d54bc3e1..396913538 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -7,18 +7,21 @@ import ( "flag" "fmt" "io" + "net" "net/http" "os" "path/filepath" "regexp" + "strings" + "testing" "time" - "github.com/go-check/check" "github.com/pmezard/go-difflib/difflib" "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/api" - checker "github.com/vdemeester/shakers" ) var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata") @@ -26,25 +29,39 @@ var updateExpected = flag.Bool("update_expected", false, "Update expected files // K8sSuite tests suite. type K8sSuite struct{ BaseSuite } -func (s *K8sSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "k8s") - s.composeUp(c) +func TestK8sSuite(t *testing.T) { + suite.Run(t, new(K8sSuite)) +} + +func (s *K8sSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("k8s") + s.composeUp() abs, err := filepath.Abs("./fixtures/k8s/config.skip/kubeconfig.yaml") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Do(60*time.Second, func() error { _, err := os.Stat(abs) return err }) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) + + data, err := os.ReadFile(abs) + require.NoError(s.T(), err) + + content := strings.ReplaceAll(string(data), "https://server:6443", fmt.Sprintf("https://%s", net.JoinHostPort(s.getComposeServiceIP("server"), "6443"))) + + err = os.WriteFile(abs, []byte(content), 0o644) + require.NoError(s.T(), err) err = os.Setenv("KUBECONFIG", abs) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *K8sSuite) TearDownSuite(c *check.C) { - s.composeDown(c) +func (s *K8sSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() generatedFiles := []string{ "./fixtures/k8s/config.skip/kubeconfig.yaml", @@ -62,120 +79,84 @@ func (s *K8sSuite) TearDownSuite(c *check.C) { } } -func (s *K8sSuite) TestIngressConfiguration(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_default.toml")) - defer display(c) +func (s *K8sSuite) TestIngressConfiguration() { + s.traefikCmd(withConfigFile("fixtures/k8s_default.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - testConfiguration(c, "testdata/rawdata-ingress.json", "8080") + s.testConfiguration("testdata/rawdata-ingress.json", "8080") } -func (s *K8sSuite) TestIngressLabelSelector(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_ingress_label_selector.toml")) - defer display(c) +func (s *K8sSuite) TestIngressLabelSelector() { + s.traefikCmd(withConfigFile("fixtures/k8s_ingress_label_selector.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - testConfiguration(c, "testdata/rawdata-ingress-label-selector.json", "8080") + s.testConfiguration("testdata/rawdata-ingress-label-selector.json", "8080") } -func (s *K8sSuite) TestCRDConfiguration(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_crd.toml")) - defer display(c) +func (s *K8sSuite) TestCRDConfiguration() { + s.traefikCmd(withConfigFile("fixtures/k8s_crd.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - testConfiguration(c, "testdata/rawdata-crd.json", "8000") + s.testConfiguration("testdata/rawdata-crd.json", "8000") } -func (s *K8sSuite) TestCRDLabelSelector(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_crd_label_selector.toml")) - defer display(c) +func (s *K8sSuite) TestCRDLabelSelector() { + s.traefikCmd(withConfigFile("fixtures/k8s_crd_label_selector.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - testConfiguration(c, "testdata/rawdata-crd-label-selector.json", "8000") + s.testConfiguration("testdata/rawdata-crd-label-selector.json", "8000") } -func (s *K8sSuite) TestGatewayConfiguration(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_gateway.toml")) - defer display(c) +func (s *K8sSuite) TestGatewayConfiguration() { + s.traefikCmd(withConfigFile("fixtures/k8s_gateway.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - testConfiguration(c, "testdata/rawdata-gateway.json", "8080") + s.testConfiguration("testdata/rawdata-gateway.json", "8080") } -func (s *K8sSuite) TestIngressclass(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass.toml")) - defer display(c) +func (s *K8sSuite) TestIngressclass() { + s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - testConfiguration(c, "testdata/rawdata-ingressclass.json", "8080") + s.testConfiguration("testdata/rawdata-ingressclass.json", "8080") } -func (s *K8sSuite) TestDisableIngressclassLookup(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass_disabled.toml")) - defer display(c) +func (s *K8sSuite) TestDisableIngressclassLookup() { + s.traefikCmd(withConfigFile("fixtures/k8s_ingressclass_disabled.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - testConfiguration(c, "testdata/rawdata-ingressclass-disabled.json", "8080") + s.testConfiguration("testdata/rawdata-ingressclass-disabled.json", "8080") } -func testConfiguration(c *check.C, path, apiPort string) { +func (s *K8sSuite) testConfiguration(path, apiPort string) { err := try.GetRequest("http://127.0.0.1:"+apiPort+"/api/entrypoints", 20*time.Second, try.BodyContains(`"name":"web"`)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) expectedJSON := filepath.FromSlash(path) if *updateExpected { fi, err := os.Create(expectedJSON) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = fi.Close() - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } var buf bytes.Buffer err = try.GetRequest("http://127.0.0.1:"+apiPort+"/api/rawdata", 1*time.Minute, try.StatusCodeIs(http.StatusOK), matchesConfig(expectedJSON, &buf)) if !*updateExpected { - if err != nil { - c.Error(err) - } + require.NoError(s.T(), err) return } if err != nil { - c.Logf("In file update mode, got expected error: %v", err) + log.Info().Msgf("In file update mode, got expected error: %v", err) } var rtRepr api.RunTimeRepresentation err = json.Unmarshal(buf.Bytes(), &rtRepr) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) newJSON, err := json.MarshalIndent(rtRepr, "", "\t") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = os.WriteFile(expectedJSON, newJSON, 0o644) - c.Assert(err, checker.IsNil) - c.Errorf("We do not want a passing test in file update mode") + require.NoError(s.T(), err) + + s.T().Fatal("We do not want a passing test in file update mode") } func matchesConfig(wantConfig string, buf *bytes.Buffer) try.ResponseCondition { diff --git a/integration/keepalive_test.go b/integration/keepalive_test.go index a56cbdd48..54fc14555 100644 --- a/integration/keepalive_test.go +++ b/integration/keepalive_test.go @@ -5,18 +5,23 @@ import ( "net" "net/http" "net/http/httptest" - "os" + "testing" "time" - "github.com/go-check/check" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type KeepAliveSuite struct { BaseSuite } +func TestKeepAliveSuite(t *testing.T) { + suite.Run(t, new(KeepAliveSuite)) +} + type KeepAliveConfig struct { KeepAliveServer string IdleConnTimeout string @@ -27,7 +32,7 @@ type connStateChangeEvent struct { state http.ConnState } -func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime(c *check.C) { +func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime() { idleTimeout := time.Duration(75) * time.Millisecond connStateChanges := make(chan connStateChangeEvent) @@ -59,18 +64,18 @@ func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime(c * case <-noMoreRequests: moreRequestsExpected = false case <-maxWaitTimeExceeded: - c.Logf("timeout waiting for all connections to close, waited for %v, configured idle timeout was %v", maxWaitDuration, idleTimeout) - c.Fail() + log.Info().Msgf("timeout waiting for all connections to close, waited for %v, configured idle timeout was %v", maxWaitDuration, idleTimeout) + s.T().Fail() close(completed) return } } - c.Check(connCount, checker.Equals, 1) + require.Equal(s.T(), 1, connCount) for _, idlePeriod := range idlePeriodLengthMap { // Our method of measuring the actual idle period is not precise, so allow some sub-ms deviation - c.Check(math.Round(idlePeriod.Seconds()), checker.LessOrEqualThan, idleTimeout.Seconds()) + require.LessOrEqual(s.T(), math.Round(idlePeriod.Seconds()), idleTimeout.Seconds()) } close(completed) @@ -87,22 +92,16 @@ func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime(c * defer server.Close() config := KeepAliveConfig{KeepAliveServer: server.URL, IdleConnTimeout: idleTimeout.String()} - file := s.adaptFile(c, "fixtures/timeout/keepalive.toml", config) + file := s.adaptFile("fixtures/timeout/keepalive.toml", config) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Check(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // Wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Duration(1)*time.Second, try.StatusCodeIs(200), try.BodyContains("PathPrefix(`/keepalive`)")) - c.Check(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Duration(1)*time.Second, try.StatusCodeIs(200), try.BodyContains("PathPrefix(`/keepalive`)")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/keepalive", time.Duration(1)*time.Second, try.StatusCodeIs(200)) - c.Check(err, checker.IsNil) + require.NoError(s.T(), err) close(noMoreRequests) <-completed diff --git a/integration/log_rotation_test.go b/integration/log_rotation_test.go index c753dc7ff..28d72f8cd 100644 --- a/integration/log_rotation_test.go +++ b/integration/log_rotation_test.go @@ -9,12 +9,14 @@ import ( "os" "strings" "syscall" + "testing" "time" - "github.com/go-check/check" "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) const traefikTestAccessLogFileRotated = traefikTestAccessLogFile + ".rotated" @@ -22,13 +24,23 @@ const traefikTestAccessLogFileRotated = traefikTestAccessLogFile + ".rotated" // Log rotation integration test suite. type LogRotationSuite struct{ BaseSuite } -func (s *LogRotationSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "access_log") - s.composeUp(c) +func TestLogRorationSuite(t *testing.T) { + suite.Run(t, new(LogRotationSuite)) } -func (s *LogRotationSuite) TearDownSuite(c *check.C) { - s.composeDown(c) +func (s *LogRotationSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + os.Remove(traefikTestAccessLogFile) + os.Remove(traefikTestLogFile) + os.Remove(traefikTestAccessLogFileRotated) + + s.createComposeProject("access_log") + s.composeUp() +} + +func (s *LogRotationSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() generatedFiles := []string{ traefikTestLogFile, @@ -43,84 +55,80 @@ func (s *LogRotationSuite) TearDownSuite(c *check.C) { } } -func (s *LogRotationSuite) TestAccessLogRotation(c *check.C) { +func (s *LogRotationSuite) TestAccessLogRotation() { // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) - defer displayTraefikLogFile(c, traefikTestLogFile) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log_config.toml")) + defer s.displayTraefikLogFile(traefikTestLogFile) // Verify Traefik started ok - verifyEmptyErrorLog(c, "traefik.log") + s.verifyEmptyErrorLog("traefik.log") - waitForTraefik(c, "server1") + s.waitForTraefik("server1") // Make some requests req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "frontend1.docker.local" err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Rename access log err = os.Rename(traefikTestAccessLogFile, traefikTestAccessLogFileRotated) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // in the midst of the requests, issue SIGUSR1 signal to server process err = cmd.Process.Signal(syscall.SIGUSR1) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // continue issuing requests err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Verify access.log.rotated output as expected - logAccessLogFile(c, traefikTestAccessLogFileRotated) - lineCount := verifyLogLines(c, traefikTestAccessLogFileRotated, 0, true) - c.Assert(lineCount, checker.GreaterOrEqualThan, 1) + s.logAccessLogFile(traefikTestAccessLogFileRotated) + lineCount := s.verifyLogLines(traefikTestAccessLogFileRotated, 0, true) + assert.GreaterOrEqual(s.T(), lineCount, 1) // make sure that the access log file is at least created before we do assertions on it err = try.Do(1*time.Second, func() error { _, err := os.Stat(traefikTestAccessLogFile) return err }) - c.Assert(err, checker.IsNil, check.Commentf("access log file was not created in time")) + assert.NoError(s.T(), err, "access log file was not created in time") // Verify access.log output as expected - logAccessLogFile(c, traefikTestAccessLogFile) - lineCount = verifyLogLines(c, traefikTestAccessLogFile, lineCount, true) - c.Assert(lineCount, checker.Equals, 3) + s.logAccessLogFile(traefikTestAccessLogFile) + lineCount = s.verifyLogLines(traefikTestAccessLogFile, lineCount, true) + assert.Equal(s.T(), 3, lineCount) - verifyEmptyErrorLog(c, traefikTestLogFile) + s.verifyEmptyErrorLog(traefikTestLogFile) } -func logAccessLogFile(c *check.C, fileName string) { +func (s *LogRotationSuite) logAccessLogFile(fileName string) { output, err := os.ReadFile(fileName) - c.Assert(err, checker.IsNil) - c.Logf("Contents of file %s\n%s", fileName, string(output)) + require.NoError(s.T(), err) + log.Info().Msgf("Contents of file %s\n%s", fileName, string(output)) } -func verifyEmptyErrorLog(c *check.C, name string) { +func (s *LogRotationSuite) verifyEmptyErrorLog(name string) { err := try.Do(5*time.Second, func() error { traefikLog, e2 := os.ReadFile(name) if e2 != nil { return e2 } - c.Assert(string(traefikLog), checker.HasLen, 0) + assert.Empty(s.T(), string(traefikLog)) + return nil }) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func verifyLogLines(c *check.C, fileName string, countInit int, accessLog bool) int { +func (s *LogRotationSuite) verifyLogLines(fileName string, countInit int, accessLog bool) int { rotated, err := os.Open(fileName) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) rotatedLog := bufio.NewScanner(rotated) count := countInit for rotatedLog.Scan() { @@ -128,7 +136,7 @@ func verifyLogLines(c *check.C, fileName string, countInit int, accessLog bool) if accessLog { if len(line) > 0 { if !strings.Contains(line, "/api/rawdata") { - CheckAccessLogFormat(c, line, count) + s.CheckAccessLogFormat(line, count) count++ } } diff --git a/integration/proxy_protocol_test.go b/integration/proxy_protocol_test.go index 40640bae7..f30a641c7 100644 --- a/integration/proxy_protocol_test.go +++ b/integration/proxy_protocol_test.go @@ -1,103 +1,138 @@ package integration import ( - "net/http" - "os" + "bufio" + "net" + "testing" "time" - "github.com/go-check/check" + "github.com/pires/go-proxyproto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type ProxyProtocolSuite struct { BaseSuite - gatewayIP string - haproxyIP string - whoamiIP string + whoamiIP string } -func (s *ProxyProtocolSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "proxy-protocol") - s.composeUp(c) - - s.gatewayIP = s.getContainerIP(c, "traefik") - s.haproxyIP = s.getComposeServiceIP(c, "haproxy") - s.whoamiIP = s.getComposeServiceIP(c, "whoami") +func TestProxyProtocolSuite(t *testing.T) { + suite.Run(t, new(ProxyProtocolSuite)) } -func (s *ProxyProtocolSuite) TestProxyProtocolTrusted(c *check.C) { - file := s.adaptFile(c, "fixtures/proxy-protocol/with.toml", struct { +func (s *ProxyProtocolSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("proxy-protocol") + s.composeUp() + + s.whoamiIP = s.getComposeServiceIP("whoami") +} + +func (s *ProxyProtocolSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *ProxyProtocolSuite) TestProxyProtocolTrusted() { + file := s.adaptFile("fixtures/proxy-protocol/proxy-protocol.toml", struct { HaproxyIP string WhoamiIP string - }{HaproxyIP: s.haproxyIP, WhoamiIP: s.whoamiIP}) - defer os.Remove(file) + }{WhoamiIP: s.whoamiIP}) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) - err = try.GetRequest("http://"+s.haproxyIP+"/whoami", 1*time.Second, - try.StatusCodeIs(http.StatusOK), - try.BodyContains("X-Forwarded-For: "+s.gatewayIP)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/whoami", 10*time.Second) + require.NoError(s.T(), err) + + content, err := proxyProtoRequest("127.0.0.1:8000", 1) + require.NoError(s.T(), err) + assert.Contains(s.T(), content, "X-Forwarded-For: 1.2.3.4") + + content, err = proxyProtoRequest("127.0.0.1:8000", 2) + require.NoError(s.T(), err) + assert.Contains(s.T(), content, "X-Forwarded-For: 1.2.3.4") } -func (s *ProxyProtocolSuite) TestProxyProtocolV2Trusted(c *check.C) { - file := s.adaptFile(c, "fixtures/proxy-protocol/with.toml", struct { +func (s *ProxyProtocolSuite) TestProxyProtocolNotTrusted() { + file := s.adaptFile("fixtures/proxy-protocol/proxy-protocol.toml", struct { HaproxyIP string WhoamiIP string - }{HaproxyIP: s.haproxyIP, WhoamiIP: s.whoamiIP}) - defer os.Remove(file) + }{WhoamiIP: s.whoamiIP}) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) - err = try.GetRequest("http://"+s.haproxyIP+":81/whoami", 1*time.Second, - try.StatusCodeIs(http.StatusOK), - try.BodyContains("X-Forwarded-For: "+s.gatewayIP)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:9000/whoami", 10*time.Second) + require.NoError(s.T(), err) + + content, err := proxyProtoRequest("127.0.0.1:9000", 1) + require.NoError(s.T(), err) + assert.Contains(s.T(), content, "X-Forwarded-For: 127.0.0.1") + + content, err = proxyProtoRequest("127.0.0.1:9000", 2) + require.NoError(s.T(), err) + assert.Contains(s.T(), content, "X-Forwarded-For: 127.0.0.1") } -func (s *ProxyProtocolSuite) TestProxyProtocolNotTrusted(c *check.C) { - file := s.adaptFile(c, "fixtures/proxy-protocol/without.toml", struct { - HaproxyIP string - WhoamiIP string - }{HaproxyIP: s.haproxyIP, WhoamiIP: s.whoamiIP}) - defer os.Remove(file) +func proxyProtoRequest(address string, version byte) (string, error) { + // Open a TCP connection to the server + conn, err := net.Dial("tcp", address) + if err != nil { + return "", err + } + defer conn.Close() - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + // Create a Proxy Protocol header with v1 + proxyHeader := &proxyproto.Header{ + Version: version, + Command: proxyproto.PROXY, + TransportProtocol: proxyproto.TCPv4, + DestinationAddr: &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 8000, + }, + SourceAddr: &net.TCPAddr{ + IP: net.ParseIP("1.2.3.4"), + Port: 62541, + }, + } - err = try.GetRequest("http://"+s.haproxyIP+"/whoami", 1*time.Second, - try.StatusCodeIs(http.StatusOK), - try.BodyContains("X-Forwarded-For: "+s.haproxyIP)) - c.Assert(err, checker.IsNil) -} - -func (s *ProxyProtocolSuite) TestProxyProtocolV2NotTrusted(c *check.C) { - file := s.adaptFile(c, "fixtures/proxy-protocol/without.toml", struct { - HaproxyIP string - WhoamiIP string - }{HaproxyIP: s.haproxyIP, WhoamiIP: s.whoamiIP}) - defer os.Remove(file) - - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://"+s.haproxyIP+":81/whoami", 1*time.Second, - try.StatusCodeIs(http.StatusOK), - try.BodyContains("X-Forwarded-For: "+s.haproxyIP)) - c.Assert(err, checker.IsNil) + // After the connection was created write the proxy headers first + _, err = proxyHeader.WriteTo(conn) + if err != nil { + return "", err + } + + // Create an HTTP request + request := "GET /whoami HTTP/1.1\r\n" + + "Host: 127.0.0.1\r\n" + + "Connection: close\r\n" + + "\r\n" + + // Write the HTTP request to the TCP connection + writer := bufio.NewWriter(conn) + _, err = writer.WriteString(request) + if err != nil { + return "", err + } + + // Flush the buffer to ensure the request is sent + err = writer.Flush() + if err != nil { + return "", err + } + + // Read the response from the server + var content string + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + content += scanner.Text() + "\n" + } + + if scanner.Err() != nil { + return "", err + } + + return content, nil } diff --git a/integration/ratelimit_test.go b/integration/ratelimit_test.go index 5edbf64b8..b0254564d 100644 --- a/integration/ratelimit_test.go +++ b/integration/ratelimit_test.go @@ -2,12 +2,12 @@ package integration import ( "net/http" - "os" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type RateLimitSuite struct { @@ -15,33 +15,38 @@ type RateLimitSuite struct { ServerIP string } -func (s *RateLimitSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "ratelimit") - s.composeUp(c) - - s.ServerIP = s.getComposeServiceIP(c, "whoami1") +func TestRateLimitSuite(t *testing.T) { + suite.Run(t, new(RateLimitSuite)) } -func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) { - file := s.adaptFile(c, "fixtures/ratelimit/simple.toml", struct { +func (s *RateLimitSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("ratelimit") + s.composeUp() + + s.ServerIP = s.getComposeServiceIP("whoami1") +} + +func (s *RateLimitSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *RateLimitSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/ratelimit/simple.toml", struct { Server1 string }{s.ServerIP}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("ratelimit")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("ratelimit")) + require.NoError(s.T(), err) start := time.Now() count := 0 for { err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) count++ if count > 100 { break @@ -50,6 +55,6 @@ func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) { stop := time.Now() elapsed := stop.Sub(start) if elapsed < time.Second*99/100 { - c.Fatalf("requests throughput was too fast wrt to rate limiting: 100 requests in %v", elapsed) + s.T().Fatalf("requests throughput was too fast wrt to rate limiting: 100 requests in %v", elapsed) } } diff --git a/integration/redis_sentinel_test.go b/integration/redis_sentinel_test.go new file mode 100644 index 000000000..ec60f8c74 --- /dev/null +++ b/integration/redis_sentinel_test.go @@ -0,0 +1,201 @@ +package integration + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + "text/template" + "time" + + "github.com/fatih/structs" + "github.com/kvtools/redis" + "github.com/kvtools/valkeyrie" + "github.com/kvtools/valkeyrie/store" + "github.com/pmezard/go-difflib/difflib" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/traefik/traefik/v3/integration/try" + "github.com/traefik/traefik/v3/pkg/api" +) + +// Redis test suites. +type RedisSentinelSuite struct { + BaseSuite + kvClient store.Store + redisEndpoints []string +} + +func TestRedisSentinelSuite(t *testing.T) { + suite.Run(t, new(RedisSentinelSuite)) +} + +func (s *RedisSentinelSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.setupSentinelConfiguration([]string{"26379", "26379", "26379"}) + + s.createComposeProject("redis_sentinel") + s.composeUp() + + s.redisEndpoints = []string{ + net.JoinHostPort(s.getComposeServiceIP("sentinel1"), "26379"), + net.JoinHostPort(s.getComposeServiceIP("sentinel2"), "26379"), + net.JoinHostPort(s.getComposeServiceIP("sentinel3"), "26379"), + } + kv, err := valkeyrie.NewStore( + context.Background(), + redis.StoreName, + s.redisEndpoints, + &redis.Config{ + Sentinel: &redis.Sentinel{ + MasterName: "mymaster", + }, + }, + ) + require.NoError(s.T(), err, "Cannot create store redis") + s.kvClient = kv + + // wait for redis + err = try.Do(60*time.Second, try.KVExists(kv, "test")) + require.NoError(s.T(), err) +} + +func (s *RedisSentinelSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() + + for _, filename := range []string{"sentinel1.conf", "sentinel2.conf", "sentinel3.conf"} { + _ = os.Remove(filepath.Join(".", "resources", "compose", "config", filename)) + } +} + +func (s *RedisSentinelSuite) setupSentinelConfiguration(ports []string) { + for i, port := range ports { + templateValue := struct{ SentinelPort string }{SentinelPort: port} + + // Load file + templateFile := "resources/compose/config/sentinel_template.conf" + tmpl, err := template.ParseFiles(templateFile) + require.NoError(s.T(), err) + + folder, prefix := filepath.Split(templateFile) + + fileName := fmt.Sprintf("%s/sentinel%d.conf", folder, i+1) + tmpFile, err := os.Create(fileName) + require.NoError(s.T(), err) + defer tmpFile.Close() + + model := structs.Map(templateValue) + model["SelfFilename"] = tmpFile.Name() + + err = tmpl.ExecuteTemplate(tmpFile, prefix, model) + require.NoError(s.T(), err) + + err = tmpFile.Sync() + require.NoError(s.T(), err) + } +} + +func (s *RedisSentinelSuite) TestSentinelConfiguration() { + file := s.adaptFile("fixtures/redis/sentinel.toml", struct{ RedisAddress string }{ + RedisAddress: strings.Join(s.redisEndpoints, `","`), + }) + + data := map[string]string{ + "traefik/http/routers/Router0/entryPoints/0": "web", + "traefik/http/routers/Router0/middlewares/0": "compressor", + "traefik/http/routers/Router0/middlewares/1": "striper", + "traefik/http/routers/Router0/service": "simplesvc", + "traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)", + "traefik/http/routers/Router0/priority": "42", + "traefik/http/routers/Router0/tls": "true", + + "traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)", + "traefik/http/routers/Router1/priority": "42", + "traefik/http/routers/Router1/tls/domains/0/main": "aaa.localhost", + "traefik/http/routers/Router1/tls/domains/0/sans/0": "aaa.aaa.localhost", + "traefik/http/routers/Router1/tls/domains/0/sans/1": "bbb.aaa.localhost", + "traefik/http/routers/Router1/tls/domains/1/main": "bbb.localhost", + "traefik/http/routers/Router1/tls/domains/1/sans/0": "aaa.bbb.localhost", + "traefik/http/routers/Router1/tls/domains/1/sans/1": "bbb.bbb.localhost", + "traefik/http/routers/Router1/entryPoints/0": "web", + "traefik/http/routers/Router1/service": "simplesvc", + + "traefik/http/services/simplesvc/loadBalancer/servers/0/url": "http://10.0.1.1:8888", + "traefik/http/services/simplesvc/loadBalancer/servers/1/url": "http://10.0.1.1:8889", + + "traefik/http/services/srvcA/loadBalancer/servers/0/url": "http://10.0.1.2:8888", + "traefik/http/services/srvcA/loadBalancer/servers/1/url": "http://10.0.1.2:8889", + + "traefik/http/services/srvcB/loadBalancer/servers/0/url": "http://10.0.1.3:8888", + "traefik/http/services/srvcB/loadBalancer/servers/1/url": "http://10.0.1.3:8889", + + "traefik/http/services/mirror/mirroring/service": "simplesvc", + "traefik/http/services/mirror/mirroring/mirrors/0/name": "srvcA", + "traefik/http/services/mirror/mirroring/mirrors/0/percent": "42", + "traefik/http/services/mirror/mirroring/mirrors/1/name": "srvcB", + "traefik/http/services/mirror/mirroring/mirrors/1/percent": "42", + + "traefik/http/services/Service03/weighted/services/0/name": "srvcA", + "traefik/http/services/Service03/weighted/services/0/weight": "42", + "traefik/http/services/Service03/weighted/services/1/name": "srvcB", + "traefik/http/services/Service03/weighted/services/1/weight": "42", + + "traefik/http/middlewares/compressor/compress": "true", + "traefik/http/middlewares/striper/stripPrefix/prefixes/0": "foo", + "traefik/http/middlewares/striper/stripPrefix/prefixes/1": "bar", + } + + for k, v := range data { + err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + require.NoError(s.T(), err) + } + + s.traefikCmd(withConfigFile(file)) + + // wait for traefik + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, + try.BodyContains(`"striper@redis":`, `"compressor@redis":`, `"srvcA@redis":`, `"srvcB@redis":`), + ) + require.NoError(s.T(), err) + + resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") + require.NoError(s.T(), err) + + var obtained api.RunTimeRepresentation + err = json.NewDecoder(resp.Body).Decode(&obtained) + require.NoError(s.T(), err) + got, err := json.MarshalIndent(obtained, "", " ") + require.NoError(s.T(), err) + + expectedJSON := filepath.FromSlash("testdata/rawdata-redis.json") + + if *updateExpected { + err = os.WriteFile(expectedJSON, got, 0o666) + require.NoError(s.T(), err) + } + + expected, err := os.ReadFile(expectedJSON) + require.NoError(s.T(), err) + + if !bytes.Equal(expected, got) { + diff := difflib.UnifiedDiff{ + FromFile: "Expected", + A: difflib.SplitLines(string(expected)), + ToFile: "Got", + B: difflib.SplitLines(string(got)), + Context: 3, + } + + text, err := difflib.GetUnifiedDiffString(diff) + require.NoError(s.T(), err) + log.Info().Msg(text) + } +} diff --git a/integration/redis_test.go b/integration/redis_test.go index a5f45bff7..6685b7645 100644 --- a/integration/redis_test.go +++ b/integration/redis_test.go @@ -8,52 +8,63 @@ import ( "net/http" "os" "path/filepath" + "strings" + "testing" "time" - "github.com/go-check/check" "github.com/kvtools/redis" "github.com/kvtools/valkeyrie" "github.com/kvtools/valkeyrie/store" "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/api" - checker "github.com/vdemeester/shakers" ) // Redis test suites. type RedisSuite struct { BaseSuite - kvClient store.Store - redisAddr string + kvClient store.Store + redisEndpoints []string } -func (s *RedisSuite) setupStore(c *check.C) { - s.createComposeProject(c, "redis") - s.composeUp(c) +func TestRedisSuite(t *testing.T) { + suite.Run(t, new(RedisSuite)) +} - s.redisAddr = net.JoinHostPort(s.getComposeServiceIP(c, "redis"), "6379") +func (s *RedisSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("redis") + s.composeUp() + + s.redisEndpoints = []string{} + s.redisEndpoints = append(s.redisEndpoints, net.JoinHostPort(s.getComposeServiceIP("redis"), "6379")) kv, err := valkeyrie.NewStore( context.Background(), redis.StoreName, - []string{s.redisAddr}, + s.redisEndpoints, &redis.Config{}, ) - if err != nil { - c.Fatal("Cannot create store redis") - } + require.NoError(s.T(), err, "Cannot create store redis") + s.kvClient = kv // wait for redis err = try.Do(60*time.Second, try.KVExists(kv, "test")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *RedisSuite) TestSimpleConfiguration(c *check.C) { - s.setupStore(c) +func (s *RedisSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} - file := s.adaptFile(c, "fixtures/redis/simple.toml", struct{ RedisAddress string }{s.redisAddr}) - defer os.Remove(file) +func (s *RedisSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/redis/simple.toml", struct{ RedisAddress string }{ + RedisAddress: strings.Join(s.redisEndpoints, ","), + }) data := map[string]string{ "traefik/http/routers/Router0/entryPoints/0": "web", @@ -102,39 +113,35 @@ func (s *RedisSuite) TestSimpleConfiguration(c *check.C) { for k, v := range data { err := s.kvClient.Put(context.Background(), k, []byte(v), nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains(`"striper@redis":`, `"compressor@redis":`, `"srvcA@redis":`, `"srvcB@redis":`), ) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) var obtained api.RunTimeRepresentation err = json.NewDecoder(resp.Body).Decode(&obtained) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) got, err := json.MarshalIndent(obtained, "", " ") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) expectedJSON := filepath.FromSlash("testdata/rawdata-redis.json") if *updateExpected { err = os.WriteFile(expectedJSON, got, 0o666) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } expected, err := os.ReadFile(expectedJSON) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if !bytes.Equal(expected, got) { diff := difflib.UnifiedDiff{ @@ -146,7 +153,6 @@ func (s *RedisSuite) TestSimpleConfiguration(c *check.C) { } text, err := difflib.GetUnifiedDiffString(diff) - c.Assert(err, checker.IsNil) - c.Error(text) + require.NoError(s.T(), err, text) } } diff --git a/integration/resources/compose/access_log.yml b/integration/resources/compose/access_log.yml index 4e0cdbc83..c9cf06db7 100644 --- a/integration/resources/compose/access_log.yml +++ b/integration/resources/compose/access_log.yml @@ -40,7 +40,7 @@ services: traefik.http.routers.rt-authFrontend.entryPoints: httpFrontendAuth traefik.http.routers.rt-authFrontend.rule: Host(`frontend.auth.docker.local`) traefik.http.routers.rt-authFrontend.middlewares: basicauth - traefik.http.middlewares.basicauth.basicauth.users: test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/ + traefik.http.middlewares.basicauth.basicauth.users: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" traefik.http.services.service3.loadbalancer.server.port: 80 digestAuthMiddleware: @@ -85,7 +85,12 @@ services: traefik.http.middlewares.wl.ipallowlist.sourcerange: 8.8.8.8/32 traefik.http.services.service3.loadbalancer.server.port: 80 -networks: - default: - name: traefik-test-network - external: true + preflightCORS: + image: traefik/whoami + labels: + traefik.enable: true + traefik.http.routers.rt-preflightCORS.entryPoints: preflight + traefik.http.routers.rt-preflightCORS.rule: Host(`preflight.docker.local`) + traefik.http.routers.rt-preflightCORS.middlewares: preflightCORS + traefik.http.middlewares.preflightCORS.headers.accessControlAllowMethods: OPTIONS, GET + traefik.http.services.preflightCORS.loadbalancer.server.port: 80 diff --git a/integration/resources/compose/allowlist.yml b/integration/resources/compose/allowlist.yml index e5f4e0b31..0fd241322 100644 --- a/integration/resources/compose/allowlist.yml +++ b/integration/resources/compose/allowlist.yml @@ -6,7 +6,7 @@ services: traefik.enable: true traefik.http.routers.rt1.rule: Host(`no.override.allowlist.docker.local`) traefik.http.routers.rt1.middlewares: wl1 - traefik.http.middlewares.wl1.ipallowList.sourceRange: 8.8.8.8 + traefik.http.middlewares.wl1.ipallowlist.sourceRange: 8.8.8.8 overrideIPStrategyRemoteAddrAllowlist: image: traefik/whoami @@ -34,8 +34,3 @@ services: traefik.http.routers.rt4.middlewares: wl4 traefik.http.middlewares.wl4.ipallowlist.sourceRange: 8.8.8.8 traefik.http.middlewares.wl4.ipallowlist.ipStrategy.excludedIPs: 10.0.0.1,10.0.0.2 - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/base.yml b/integration/resources/compose/base.yml index 795f2a214..2d6380051 100644 --- a/integration/resources/compose/base.yml +++ b/integration/resources/compose/base.yml @@ -11,8 +11,3 @@ services: image: traefik/whoami labels: traefik.enable: false - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/config/sentinel_template.conf b/integration/resources/compose/config/sentinel_template.conf new file mode 100644 index 000000000..c9f5acf6d --- /dev/null +++ b/integration/resources/compose/config/sentinel_template.conf @@ -0,0 +1,5 @@ +port {{ .SentinelPort }} +dir "/tmp" +sentinel resolve-hostnames yes +sentinel monitor mymaster master 6380 2 +sentinel deny-scripts-reconfig yes diff --git a/integration/resources/compose/consul.yml b/integration/resources/compose/consul.yml index 068dc9a36..041ba0b45 100644 --- a/integration/resources/compose/consul.yml +++ b/integration/resources/compose/consul.yml @@ -2,8 +2,5 @@ version: "3.8" services: consul: image: consul:1.6 - -networks: - default: - name: traefik-test-network - external: true + whoami: + image: traefik/whoami diff --git a/integration/resources/compose/consul_catalog.yml b/integration/resources/compose/consul_catalog.yml index 3c9dd9f3e..6a0fd279c 100644 --- a/integration/resources/compose/consul_catalog.yml +++ b/integration/resources/compose/consul_catalog.yml @@ -2,11 +2,24 @@ version: "3.8" services: consul: image: consul:1.6.2 - command: agent -server -bootstrap -ui -client 0.0.0.0 -hcl 'connect { enabled = true }' + command: + - agent + - -server + - -bootstrap + - -ui + - -client + - 0.0.0.0 + - -hcl + - 'connect { enabled = true }' consul-agent: image: consul:1.6.2 - command: agent -retry-join consul -client 0.0.0.0 + command: + - agent + - -retry-join + - consul + - -client + - 0.0.0.0 whoami1: image: traefik/whoami @@ -30,8 +43,3 @@ services: PORT: 443 BIND: 0.0.0.0 CONSUL_HTTP_ADDR: http://consul:8500 - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/docker.yml b/integration/resources/compose/docker.yml index 734e76b25..e594ec87c 100644 --- a/integration/resources/compose/docker.yml +++ b/integration/resources/compose/docker.yml @@ -36,8 +36,3 @@ services: labels: traefik.http.Routers.Super.Rule: Host(`my.super.host`) traefik.http.Services.powpow.LoadBalancer.server.Port: 2375 - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/error_pages.yml b/integration/resources/compose/error_pages.yml index 6d6ab1389..03cc13f80 100644 --- a/integration/resources/compose/error_pages.yml +++ b/integration/resources/compose/error_pages.yml @@ -1,12 +1,7 @@ version: "3.8" services: nginx1: - image: nginx:1.13.8-alpine + image: nginx:1.25.3-alpine3.18 nginx2: - image: nginx:1.13.8-alpine - -networks: - default: - name: traefik-test-network - external: true + image: nginx:1.25.3-alpine3.18 diff --git a/integration/resources/compose/etcd.yml b/integration/resources/compose/etcd.yml index 72c0b3698..fb1f3f7ec 100644 --- a/integration/resources/compose/etcd.yml +++ b/integration/resources/compose/etcd.yml @@ -2,9 +2,9 @@ version: "3.8" services: etcd: image: quay.io/coreos/etcd:v3.3.18 - command: etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2380 - -networks: - default: - name: traefik-test-network - external: true + command: + - etcd + - --listen-client-urls + - http://0.0.0.0:2379 + - --advertise-client-urls + - http://0.0.0.0:2380 diff --git a/integration/resources/compose/file.yml b/integration/resources/compose/file.yml index c5f2d696f..52e973e53 100644 --- a/integration/resources/compose/file.yml +++ b/integration/resources/compose/file.yml @@ -14,8 +14,3 @@ services: whoami5: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/healthcheck.yml b/integration/resources/compose/healthcheck.yml index ba130fcf3..9419f4bce 100644 --- a/integration/resources/compose/healthcheck.yml +++ b/integration/resources/compose/healthcheck.yml @@ -11,8 +11,3 @@ services: whoami4: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/hostresolver.yml b/integration/resources/compose/hostresolver.yml index 44d19df57..680962006 100644 --- a/integration/resources/compose/hostresolver.yml +++ b/integration/resources/compose/hostresolver.yml @@ -6,8 +6,3 @@ services: traefik.enable: true traefik.http.services.service1.loadbalancer.server.port: 80 traefik.http.routers.router1.rule: Host(`github.com`) - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/k8s.yml b/integration/resources/compose/k8s.yml index d718743c1..a34f72bd7 100644 --- a/integration/resources/compose/k8s.yml +++ b/integration/resources/compose/k8s.yml @@ -1,10 +1,24 @@ version: "3.8" services: server: - image: rancher/k3s:v1.23.17-k3s1 - command: server --disable-agent --no-deploy coredns --no-deploy servicelb --no-deploy traefik --no-deploy local-storage --no-deploy metrics-server --log /output/k3s.log --bind-address=server --tls-san=server + image: rancher/k3s:v1.20.15-k3s1 + privileged: true + command: + - server + - --disable-agent + - --disable=coredns + - --disable=servicelb + - --disable=traefik + - --disable=local-storage + - --disable=metrics-server + - --log=/output/k3s.log + - --bind-address=server + - --tls-san=server + - --tls-san=172.31.42.3 + - --tls-san=172.31.42.4 environment: K3S_CLUSTER_SECRET: somethingtotallyrandom + K3S_TOKEN: somethingtotallyrandom K3S_KUBECONFIG_OUTPUT: /output/kubeconfig.yaml K3S_KUBECONFIG_MODE: 666 volumes: @@ -12,13 +26,9 @@ services: - ./fixtures/k8s:/var/lib/rancher/k3s/server/manifests node: - image: rancher/k3s:v1.23.17-k3s1 + image: rancher/k3s:v1.20.15-k3s1 privileged: true environment: + K3S_TOKEN: somethingtotallyrandom K3S_URL: https://server:6443 K3S_CLUSTER_SECRET: somethingtotallyrandom - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/minimal.yml b/integration/resources/compose/minimal.yml index c6de60515..acb12804e 100644 --- a/integration/resources/compose/minimal.yml +++ b/integration/resources/compose/minimal.yml @@ -3,12 +3,9 @@ services: whoami1: image: traefik/whoami labels: - traefik.http.Routers.RouterMini.Rule: PathPrefix(`/whoami`) + traefik.http.routers.router-mini.Rule: PathPrefix(`/whoami`) + traefik.http.routers.router-mini.service: service-mini + traefik.http.services.service-mini.loadbalancer.server.port: 80 traefik.enable: true deploy: replicas: 2 - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/pebble.yml b/integration/resources/compose/pebble.yml index 0f8c18b1f..f39dc1378 100644 --- a/integration/resources/compose/pebble.yml +++ b/integration/resources/compose/pebble.yml @@ -2,14 +2,12 @@ version: "3.8" services: pebble: image: letsencrypt/pebble:v2.3.1 - command: pebble --dnsserver traefik:5053 + command: + - pebble + - --dnsserver + - host.docker.internal:5053 environment: # https://github.com/letsencrypt/pebble#testing-at-full-speed PEBBLE_VA_NOSLEEP: 1 # https://github.com/letsencrypt/pebble#invalid-anti-replay-nonce-errors PEBBLE_WFE_NONCEREJECT: 0 - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/proxy-protocol.yml b/integration/resources/compose/proxy-protocol.yml index 5fa8b101c..8fa69a9ba 100644 --- a/integration/resources/compose/proxy-protocol.yml +++ b/integration/resources/compose/proxy-protocol.yml @@ -1,14 +1,4 @@ version: "3.8" services: - haproxy: - image: haproxy:2.2 - volumes: - - ./resources/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg - whoami: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/ratelimit.yml b/integration/resources/compose/ratelimit.yml index 0f4d4673b..251568165 100644 --- a/integration/resources/compose/ratelimit.yml +++ b/integration/resources/compose/ratelimit.yml @@ -2,8 +2,3 @@ version: "3.8" services: whoami1: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/redis.yml b/integration/resources/compose/redis.yml index cf859c268..09bbeacad 100644 --- a/integration/resources/compose/redis.yml +++ b/integration/resources/compose/redis.yml @@ -2,8 +2,3 @@ version: "3.8" services: redis: image: redis:5.0 - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/redis_sentinel.yml b/integration/resources/compose/redis_sentinel.yml new file mode 100644 index 000000000..1737c2a1a --- /dev/null +++ b/integration/resources/compose/redis_sentinel.yml @@ -0,0 +1,53 @@ +version: "3.8" +services: + master: + image: redis + container_name: redis-master + command: + - redis-server + - --port + - 6380 + node1: + image: redis + container_name: redis-node-1 + command: + - redis-server + - --port + - 6381 + - --slaveof + - redis-master + - 6380 + node2: + image: redis + container_name: redis-node-2 + command: + - redis-server + - --port + - 6382 + - --slaveof + - redis-master + - 6380 + sentinel1: + image: redis + container_name: redis-sentinel-1 + command: + - redis-sentinel + - /usr/local/etc/redis/conf/sentinel1.conf + volumes: + - ./resources/compose/config:/usr/local/etc/redis/conf + sentinel2: + image: redis + container_name: redis-sentinel-2 + command: + - redis-sentinel + - /usr/local/etc/redis/conf/sentinel2.conf + volumes: + - ./resources/compose/config:/usr/local/etc/redis/conf + sentinel3: + image: redis + container_name: redis-sentinel-3 + command: + - redis-sentinel + - /usr/local/etc/redis/conf/sentinel3.conf + volumes: + - ./resources/compose/config:/usr/local/etc/redis/conf diff --git a/integration/resources/compose/reqacceptgrace.yml b/integration/resources/compose/reqacceptgrace.yml index ce50efd8c..8fa69a9ba 100644 --- a/integration/resources/compose/reqacceptgrace.yml +++ b/integration/resources/compose/reqacceptgrace.yml @@ -2,8 +2,3 @@ version: "3.8" services: whoami: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/rest.yml b/integration/resources/compose/rest.yml index 0f4d4673b..251568165 100644 --- a/integration/resources/compose/rest.yml +++ b/integration/resources/compose/rest.yml @@ -2,8 +2,3 @@ version: "3.8" services: whoami1: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/retry.yml b/integration/resources/compose/retry.yml index ce50efd8c..8fa69a9ba 100644 --- a/integration/resources/compose/retry.yml +++ b/integration/resources/compose/retry.yml @@ -2,8 +2,3 @@ version: "3.8" services: whoami: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/stats.yml b/integration/resources/compose/stats.yml index 15a2813ca..599fed311 100644 --- a/integration/resources/compose/stats.yml +++ b/integration/resources/compose/stats.yml @@ -5,8 +5,3 @@ services: whoami2: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/tailscale.yml b/integration/resources/compose/tailscale.yml index 82b033ebf..dbad56561 100644 --- a/integration/resources/compose/tailscale.yml +++ b/integration/resources/compose/tailscale.yml @@ -9,9 +9,5 @@ services: cap_add: # Required for tailscale to work - net_admin - sys_module - command: tailscaled - -networks: - default: - name: traefik-test-network - external: true + command: + - tailscaled diff --git a/integration/resources/compose/tcp.yml b/integration/resources/compose/tcp.yml index 518f70ddd..cd7fc0627 100644 --- a/integration/resources/compose/tcp.yml +++ b/integration/resources/compose/tcp.yml @@ -2,38 +2,58 @@ version: "3.8" services: whoami-a: image: traefik/whoamitcp - command: -name whoami-a -certFile /certs/whoami-a.crt -keyFile /certs/whoami-a.key + command: + - -name + - whoami-a + - -certFile + - /certs/whoami-a.crt + - -keyFile + - /certs/whoami-a.key volumes: - ./fixtures/tcp:/certs whoami-b: image: traefik/whoamitcp - command: -name whoami-b -certFile /certs/whoami-b.crt -keyFile /certs/whoami-b.key + command: + - -name + - whoami-b + - -certFile + - /certs/whoami-b.crt + - -keyFile + - /certs/whoami-b.key volumes: - ./fixtures/tcp:/certs whoami-ab: image: traefik/whoamitcp - command: -name whoami-ab -certFile /certs/whoami-b.crt -keyFile /certs/whoami-b.key + command: + - -name + - whoami-ab + - -certFile + - /certs/whoami-b.crt + - -keyFile + - /certs/whoami-b.key volumes: - ./fixtures/tcp:/certs whoami-no-cert: image: traefik/whoamitcp - command: -name whoami-no-cert + command: + - -name + - whoami-no-cert whoami-no-tls: image: traefik/whoamitcp - command: -name whoami-no-tls + command: + - -name + - whoami-no-tls whoami: image: traefik/whoami whoami-banner: image: traefik/whoamitcp - command: -name whoami-banner --banner - -networks: - default: - name: traefik-test-network - external: true + command: + - -name + - whoami-banner + - --banner diff --git a/integration/resources/compose/timeout.yml b/integration/resources/compose/timeout.yml index 727f2e567..6e415b9b0 100644 --- a/integration/resources/compose/timeout.yml +++ b/integration/resources/compose/timeout.yml @@ -5,8 +5,3 @@ services: environment: PROTO: http PORT: 9000 - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/tlsclientheaders.yml b/integration/resources/compose/tlsclientheaders.yml index 5bb836607..ef16f5f36 100644 --- a/integration/resources/compose/tlsclientheaders.yml +++ b/integration/resources/compose/tlsclientheaders.yml @@ -7,8 +7,3 @@ services: traefik.http.routers.route1.middlewares: passtls traefik.http.routers.route1.tls: true traefik.http.middlewares.passtls.passtlsclientcert.pem: true - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/tracing.yml b/integration/resources/compose/tracing.yml index aeea9b378..6f6c15526 100644 --- a/integration/resources/compose/tracing.yml +++ b/integration/resources/compose/tracing.yml @@ -1,20 +1,14 @@ version: "3.8" services: - zipkin: - image: openzipkin/zipkin:2.16.2 - environment: - STORAGE_TYPE: mem - JAVA_OPTS: -Dlogging.level.zipkin=DEBUG - - jaeger: - image: jaegertracing/all-in-one:1.14 - environment: - COLLECTOR_ZIPKIN_HTTP_PORT: 9411 - + tempo: + hostname: tempo + image: grafana/tempo:latest + command: [ "-config.file=/etc/tempo.yaml" ] + volumes: + - ./fixtures/tracing/tempo.yaml:/etc/tempo.yaml + otel-collector: + image: otel/opentelemetry-collector-contrib:0.89.0 + volumes: + - ./fixtures/tracing/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml whoami: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/udp.yml b/integration/resources/compose/udp.yml index d3018fae9..ce2633199 100644 --- a/integration/resources/compose/udp.yml +++ b/integration/resources/compose/udp.yml @@ -2,20 +2,21 @@ version: "3.8" services: whoami-a: image: traefik/whoamiudp:latest - command: -name whoami-a + command: + - -name + - whoami-a whoami-b: image: traefik/whoamiudp:latest - command: -name whoami-b + command: + - -name + - whoami-b whoami-c: image: traefik/whoamiudp:latest - command: -name whoami-c + command: + - -name + - whoami-c whoami-d: image: traefik/whoami - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/compose/whitelist.yml b/integration/resources/compose/whitelist.yml new file mode 100644 index 000000000..790ce52b7 --- /dev/null +++ b/integration/resources/compose/whitelist.yml @@ -0,0 +1,36 @@ +version: "3.8" +services: + noOverrideWhitelist: + image: traefik/whoami + labels: + traefik.enable: true + traefik.http.routers.rt1.rule: Host(`no.override.whitelist.docker.local`) + traefik.http.routers.rt1.middlewares: wl1 + traefik.http.middlewares.wl1.ipwhitelist.sourceRange: 8.8.8.8 + + overrideIPStrategyRemoteAddrWhitelist: + image: traefik/whoami + labels: + traefik.enable: true + traefik.http.routers.rt2.rule: Host(`override.remoteaddr.whitelist.docker.local`) + traefik.http.routers.rt2.middlewares: wl2 + traefik.http.middlewares.wl2.ipwhitelist.sourceRange: 8.8.8.8 + traefik.http.middlewares.wl2.ipwhitelist.ipStrategy: true + + overrideIPStrategyDepthWhitelist: + image: traefik/whoami + labels: + traefik.enable: true + traefik.http.routers.rt3.rule: Host(`override.depth.whitelist.docker.local`) + traefik.http.routers.rt3.middlewares: wl3 + traefik.http.middlewares.wl3.ipwhitelist.sourceRange: 8.8.8.8 + traefik.http.middlewares.wl3.ipwhitelist.ipStrategy.depth: 3 + + overrideIPStrategyExcludedIPsWhitelist: + image: traefik/whoami + labels: + traefik.enable: true + traefik.http.routers.rt4.rule: Host(`override.excludedips.whitelist.docker.local`) + traefik.http.routers.rt4.middlewares: wl4 + traefik.http.middlewares.wl4.ipwhitelist.sourceRange: 8.8.8.8 + traefik.http.middlewares.wl4.ipwhitelist.ipStrategy.excludedIPs: 10.0.0.1,10.0.0.2 diff --git a/integration/resources/compose/zookeeper.yml b/integration/resources/compose/zookeeper.yml index c75424e8d..9861c1437 100644 --- a/integration/resources/compose/zookeeper.yml +++ b/integration/resources/compose/zookeeper.yml @@ -2,8 +2,3 @@ version: "3.8" services: zookeeper: image: zookeeper:3.5 - -networks: - default: - name: traefik-test-network - external: true diff --git a/integration/resources/haproxy/haproxy.cfg b/integration/resources/haproxy/haproxy.cfg deleted file mode 100644 index 22b3fbd62..000000000 --- a/integration/resources/haproxy/haproxy.cfg +++ /dev/null @@ -1,30 +0,0 @@ -global - maxconn 4096 - -defaults - log global - mode http - retries 3 - option redispatch - maxconn 2000 - timeout connect 5000 - timeout client 50000 - timeout server 50000 - -frontend TestServerTest - bind 0.0.0.0:80 - mode tcp - default_backend TestServerNodes - -frontend TestServerTestV2 - bind 0.0.0.0:81 - mode tcp - default_backend TestServerNodesV2 - -backend TestServerNodes - mode tcp - server TestServer01 traefik:8000 send-proxy - - backend TestServerNodesV2 - mode tcp - server TestServer01 traefik:8000 send-proxy-v2 diff --git a/integration/rest_test.go b/integration/rest_test.go index 1178fbb94..2600a08c9 100644 --- a/integration/rest_test.go +++ b/integration/rest_test.go @@ -5,14 +5,15 @@ import ( "encoding/json" "net" "net/http" - "os" "strings" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/config/dynamic" - checker "github.com/vdemeester/shakers" ) type RestSuite struct { @@ -20,28 +21,33 @@ type RestSuite struct { whoamiAddr string } -func (s *RestSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "rest") - s.composeUp(c) - - s.whoamiAddr = net.JoinHostPort(s.getComposeServiceIP(c, "whoami1"), "80") +func TestRestSuite(t *testing.T) { + suite.Run(t, new(RestSuite)) } -func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) { - cmd, display := s.traefikCmd(withConfigFile("fixtures/rest/simple.toml")) +func (s *RestSuite) SetupSuite() { + s.BaseSuite.SetupSuite() - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.createComposeProject("rest") + s.composeUp() + + s.whoamiAddr = net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80") +} + +func (s *RestSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *RestSuite) TestSimpleConfigurationInsecure() { + s.traefikCmd(withConfigFile("fixtures/rest/simple.toml")) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal")) + require.NoError(s.T(), err) // Expected a 404 as we did not configure anything. err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) testCase := []struct { desc string @@ -105,47 +111,41 @@ func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) { for _, test := range testCase { data, err := json.Marshal(test.config) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(data)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) response, err := http.DefaultClient.Do(request) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 3*time.Second, try.BodyContains(test.ruleMatch)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } } -func (s *RestSuite) TestSimpleConfiguration(c *check.C) { - file := s.adaptFile(c, "fixtures/rest/simple_secure.toml", struct{}{}) - defer os.Remove(file) +func (s *RestSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/rest/simple_secure.toml", struct{}{}) - cmd, display := s.traefikCmd(withConfigFile(file)) - - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // Expected a 404 as we did not configure anything. - err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2000*time.Millisecond, try.BodyContains("PathPrefix(`/secure`)")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", strings.NewReader("{}")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) response, err := http.DefaultClient.Do(request) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusNotFound) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusNotFound, response.StatusCode) testCase := []struct { desc string @@ -209,19 +209,19 @@ func (s *RestSuite) TestSimpleConfiguration(c *check.C) { for _, test := range testCase { data, err := json.Marshal(test.config) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8000/secure/api/providers/rest", bytes.NewReader(data)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) response, err := http.DefaultClient.Do(request) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains(test.ruleMatch)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/", time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } } diff --git a/integration/retry_test.go b/integration/retry_test.go index 29fc0e7d0..12d0be8ac 100644 --- a/integration/retry_test.go +++ b/integration/retry_test.go @@ -1,14 +1,16 @@ package integration import ( + "io" "net/http" - "os" + "testing" "time" - "github.com/go-check/check" "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type RetrySuite struct { @@ -16,73 +18,86 @@ type RetrySuite struct { whoamiIP string } -func (s *RetrySuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "retry") - s.composeUp(c) - - s.whoamiIP = s.getComposeServiceIP(c, "whoami") +func TestRetrySuite(t *testing.T) { + suite.Run(t, new(RetrySuite)) } -func (s *RetrySuite) TestRetry(c *check.C) { - file := s.adaptFile(c, "fixtures/retry/simple.toml", struct{ WhoamiIP string }{s.whoamiIP}) - defer os.Remove(file) +func (s *RetrySuite) SetupSuite() { + s.BaseSuite.SetupSuite() - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.createComposeProject("retry") + s.composeUp() - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) - c.Assert(err, checker.IsNil) + s.whoamiIP = s.getComposeServiceIP("whoami") +} + +func (s *RetrySuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *RetrySuite) TestRetry() { + file := s.adaptFile("fixtures/retry/simple.toml", struct{ WhoamiIP string }{s.whoamiIP}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) + require.NoError(s.T(), err) response, err := http.Get("http://127.0.0.1:8000/") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // The test only verifies that the retry middleware makes sure that the working service is eventually reached. - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) } -func (s *RetrySuite) TestRetryBackoff(c *check.C) { - file := s.adaptFile(c, "fixtures/retry/backoff.toml", struct{ WhoamiIP string }{s.whoamiIP}) - defer os.Remove(file) +func (s *RetrySuite) TestRetryBackoff() { + file := s.adaptFile("fixtures/retry/backoff.toml", struct{ WhoamiIP string }{s.whoamiIP}) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) + require.NoError(s.T(), err) response, err := http.Get("http://127.0.0.1:8000/") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // The test only verifies that the retry middleware allows finally to reach the working service. - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) } -func (s *RetrySuite) TestRetryWebsocket(c *check.C) { - file := s.adaptFile(c, "fixtures/retry/simple.toml", struct{ WhoamiIP string }{s.whoamiIP}) - defer os.Remove(file) +func (s *RetrySuite) TestRetryWebsocket() { + file := s.adaptFile("fixtures/retry/simple.toml", struct{ WhoamiIP string }{s.whoamiIP}) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) + require.NoError(s.T(), err) // The test only verifies that the retry middleware makes sure that the working service is eventually reached. _, response, err := websocket.DefaultDialer.Dial("ws://127.0.0.1:8000/echo", nil) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusSwitchingProtocols) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusSwitchingProtocols, response.StatusCode) // The test verifies a second time that the working service is eventually reached. _, response, err = websocket.DefaultDialer.Dial("ws://127.0.0.1:8000/echo", nil) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusSwitchingProtocols) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusSwitchingProtocols, response.StatusCode) +} + +func (s *RetrySuite) TestRetryWithStripPrefix() { + file := s.adaptFile("fixtures/retry/strip_prefix.toml", struct{ WhoamiIP string }{s.whoamiIP}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) + require.NoError(s.T(), err) + + response, err := http.Get("http://127.0.0.1:8000/test") + require.NoError(s.T(), err) + + body, err := io.ReadAll(response.Body) + require.NoError(s.T(), err) + + assert.Contains(s.T(), string(body), "GET / HTTP/1.1") + assert.Contains(s.T(), string(body), "X-Forwarded-Prefix: /test") } diff --git a/integration/simple_test.go b/integration/simple_test.go index dac607339..d8f0f45b7 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -15,25 +15,36 @@ import ( "strings" "sync/atomic" "syscall" + "testing" "time" - "github.com/go-check/check" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/config/dynamic" - checker "github.com/vdemeester/shakers" ) // SimpleSuite tests suite. type SimpleSuite struct{ BaseSuite } -func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) { - cmd, output := s.cmdTraefik(withConfigFile("fixtures/invalid_configuration.toml")) +func TestSimpleSuite(t *testing.T) { + suite.Run(t, new(SimpleSuite)) +} - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *SimpleSuite) SetupSuite() { + s.BaseSuite.SetupSuite() +} - err = try.Do(500*time.Millisecond, func() error { +func (s *SimpleSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *SimpleSuite) TestInvalidConfigShouldFail() { + _, output := s.cmdTraefik(withConfigFile("fixtures/invalid_configuration.toml")) + + err := try.Do(500*time.Millisecond, func() error { expected := "expected '.' or '=', but got '{' instead" actual := output.String() @@ -43,40 +54,28 @@ func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) { return nil }) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/simple_default.toml")) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *SimpleSuite) TestSimpleDefaultConfig() { + s.cmdTraefik(withConfigFile("fixtures/simple_default.toml")) // Expected a 404 as we did not configure anything - err = try.GetRequest("http://127.0.0.1:8000/", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestWithWebConfig(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/simple_web.toml")) +func (s *SimpleSuite) TestWithWebConfig() { + s.cmdTraefik(withConfigFile("fixtures/simple_web.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestPrintHelp(c *check.C) { - cmd, output := s.cmdTraefik("--help") +func (s *SimpleSuite) TestPrintHelp() { + _, output := s.cmdTraefik("--help") - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.Do(500*time.Millisecond, func() error { + err := try.Do(500*time.Millisecond, func() error { expected := "Usage:" notExpected := "panic:" actual := output.String() @@ -90,61 +89,55 @@ func (s *SimpleSuite) TestPrintHelp(c *check.C) { return nil }) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestRequestAcceptGraceTimeout(c *check.C) { - s.createComposeProject(c, "reqacceptgrace") +func (s *SimpleSuite) TestRequestAcceptGraceTimeout() { + s.createComposeProject("reqacceptgrace") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - whoamiURL := "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "whoami"), "80") + whoamiURL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami"), "80") - file := s.adaptFile(c, "fixtures/reqacceptgrace.toml", struct { + file := s.adaptFile("fixtures/reqacceptgrace.toml", struct { Server string }{whoamiURL}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + cmd, _ := s.cmdTraefik(withConfigFile(file)) // Wait for Traefik to turn ready. - err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) // Make sure exposed service is ready. err = try.GetRequest("http://127.0.0.1:8000/service", 3*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Check that /ping endpoint is responding with 200. err = try.GetRequest("http://127.0.0.1:8001/ping", 3*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Send SIGTERM to Traefik. proc, err := os.FindProcess(cmd.Process.Pid) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = proc.Signal(syscall.SIGTERM) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Give Traefik time to process the SIGTERM and send a request half-way // into the request accepting grace period, by which requests should // still get served. time.Sleep(5 * time.Second) resp, err := http.Get("http://127.0.0.1:8000/service") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) defer resp.Body.Close() - c.Assert(resp.StatusCode, checker.Equals, http.StatusOK) + assert.Equal(s.T(), http.StatusOK, resp.StatusCode) // ping endpoint should now return a Service Unavailable. resp, err = http.Get("http://127.0.0.1:8001/ping") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) defer resp.Body.Close() - c.Assert(resp.StatusCode, checker.Equals, http.StatusServiceUnavailable) + assert.Equal(s.T(), http.StatusServiceUnavailable, resp.StatusCode) // Expect Traefik to shut down gracefully once the request accepting grace // period has elapsed. @@ -155,229 +148,192 @@ func (s *SimpleSuite) TestRequestAcceptGraceTimeout(c *check.C) { select { case err := <-waitErr: - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) case <-time.After(10 * time.Second): // By now we are ~5 seconds out of the request accepting grace period // (start + 5 seconds sleep prior to the mid-grace period request + // 10 seconds timeout = 15 seconds > 10 seconds grace period). // Something must have gone wrong if we still haven't terminated at // this point. - c.Fatal("Traefik did not terminate in time") + s.T().Fatal("Traefik did not terminate in time") } } -func (s *SimpleSuite) TestCustomPingTerminationStatusCode(c *check.C) { - file := s.adaptFile(c, "fixtures/custom_ping_termination_status_code.toml", struct{}{}) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) +func (s *SimpleSuite) TestCustomPingTerminationStatusCode() { + file := s.adaptFile("fixtures/custom_ping_termination_status_code.toml", struct{}{}) + cmd, _ := s.cmdTraefik(withConfigFile(file)) // Wait for Traefik to turn ready. - err = try.GetRequest("http://127.0.0.1:8001/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8001/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) // Check that /ping endpoint is responding with 200. err = try.GetRequest("http://127.0.0.1:8001/ping", 3*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Send SIGTERM to Traefik. proc, err := os.FindProcess(cmd.Process.Pid) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = proc.Signal(syscall.SIGTERM) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // ping endpoint should now return a Service Unavailable. err = try.GetRequest("http://127.0.0.1:8001/ping", 2*time.Second, try.StatusCodeIs(http.StatusNoContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestStatsWithMultipleEntryPoint(c *check.C) { - c.Skip("Stats is missing") - s.createComposeProject(c, "stats") +func (s *SimpleSuite) TestStatsWithMultipleEntryPoint() { + s.T().Skip("Stats is missing") + s.createComposeProject("stats") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "whoami1"), "80") - whoami2URL := "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "whoami2"), "80") + whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80") + whoami2URL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami2"), "80") - file := s.adaptFile(c, "fixtures/simple_stats.toml", struct { + file := s.adaptFile("fixtures/simple_stats.toml", struct { Server1 string Server2 string }{whoami1URL, whoami2URL}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/health", 1*time.Second, try.BodyContains(`"total_status_code_count":{"200":2}`)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) { - c.Skip("Waiting for new api handler implementation") +func (s *SimpleSuite) TestNoAuthOnPing() { + s.T().Skip("Waiting for new api handler implementation") - s.createComposeProject(c, "base") + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - file := s.adaptFile(c, "./fixtures/simple_auth.toml", struct{}{}) - defer os.Remove(file) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + file := s.adaptFile("./fixtures/simple_auth.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8001/api/rawdata", 2*time.Second, try.StatusCodeIs(http.StatusUnauthorized)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8001/api/rawdata", 2*time.Second, try.StatusCodeIs(http.StatusUnauthorized)) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8001/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestDefaultEntryPointHTTP(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestDefaultEntryPointHTTP() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api.insecure") - defer output(c) + s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api.insecure") - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestWithNonExistingEntryPoint(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestWithNonExistingEntryPoint() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api.insecure") - defer output(c) + s.traefikCmd("--entryPoints.http.Address=:8000", "--log.level=DEBUG", "--providers.docker", "--api.insecure") - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestMetricsPrometheusDefaultEntryPoint(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestMetricsPrometheusDefaultEntryPoint() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--api.insecure", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--providers.docker", "--metrics.prometheus.addrouterslabels=true", "--log.level=DEBUG") - defer output(c) + s.traefikCmd("--entryPoints.http.Address=:8000", "--api.insecure", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--providers.docker", "--metrics.prometheus.addrouterslabels=true", "--log.level=DEBUG") - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/whoami`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/whoami`)")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyContains("_router_")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyContains("_entrypoint_")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyContains("_service_")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestMetricsPrometheusTwoRoutersOneService(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestMetricsPrometheusTwoRoutersOneService() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - cmd, output := s.traefikCmd("--entryPoints.http.Address=:8000", "--api.insecure", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--providers.docker", "--metrics.prometheus.addentrypointslabels=false", "--metrics.prometheus.addrouterslabels=true", "--log.level=DEBUG") - defer output(c) + s.traefikCmd("--entryPoints.http.Address=:8000", "--api.insecure", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--providers.docker", "--metrics.prometheus.addentrypointslabels=false", "--metrics.prometheus.addrouterslabels=true", "--log.level=DEBUG") - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami2", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // adding a loop to test if metrics are not deleted for i := 0; i < 10; i++ { request, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/metrics", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) response, err := http.DefaultClient.Do(request) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) body, err := io.ReadAll(response.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // Reqs count of 1 for both routers - c.Assert(string(body), checker.Contains, "traefik_router_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",router=\"router1@docker\",service=\"whoami1-traefik-integration-test-base@docker\"} 1") - c.Assert(string(body), checker.Contains, "traefik_router_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",router=\"router2@docker\",service=\"whoami1-traefik-integration-test-base@docker\"} 1") + assert.Contains(s.T(), string(body), "traefik_router_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",router=\"router1@docker\",service=\"whoami1@docker\"} 1") + assert.Contains(s.T(), string(body), "traefik_router_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",router=\"router2@docker\",service=\"whoami1@docker\"} 1") // Reqs count of 2 for service behind both routers - c.Assert(string(body), checker.Contains, "traefik_service_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"whoami1-traefik-integration-test-base@docker\"} 2") + assert.Contains(s.T(), string(body), "traefik_service_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"whoami1@docker\"} 2") } } // TestMetricsWithBufferingMiddleware checks that the buffering middleware // (which introduces its own response writer in the chain), does not interfere with // the capture middleware on which the metrics mechanism relies. -func (s *SimpleSuite) TestMetricsWithBufferingMiddleware(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestMetricsWithBufferingMiddleware() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -387,113 +343,96 @@ func (s *SimpleSuite) TestMetricsWithBufferingMiddleware(c *check.C) { server.Start() defer server.Close() - file := s.adaptFile(c, "fixtures/simple_metrics_with_buffer_middleware.toml", struct{ IP string }{IP: strings.TrimPrefix(server.URL, "http://")}) - defer os.Remove(file) + file := s.adaptFile("fixtures/simple_metrics_with_buffer_middleware.toml", struct{ IP string }{IP: strings.TrimPrefix(server.URL, "http://")}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/without`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/without`)")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8001/without", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8002/with-req", strings.NewReader("MORE THAN TEN BYTES IN REQUEST")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // The request should fail because the body is too large. err = try.Request(req, 1*time.Second, try.StatusCodeIs(http.StatusRequestEntityTooLarge)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // The request should fail because the response exceeds the configured limit. err = try.GetRequest("http://127.0.0.1:8003/with-resp", 1*time.Second, try.StatusCodeIs(http.StatusInternalServerError)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) request, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/metrics", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) response, err := http.DefaultClient.Do(request) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) body, err := io.ReadAll(response.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // For allowed requests and responses, the entrypoint and service metrics have the same status code. - c.Assert(string(body), checker.Contains, "traefik_entrypoint_requests_total{code=\"200\",entrypoint=\"webA\",method=\"GET\",protocol=\"http\"} 1") - c.Assert(string(body), checker.Contains, "traefik_entrypoint_requests_bytes_total{code=\"200\",entrypoint=\"webA\",method=\"GET\",protocol=\"http\"} 0") - c.Assert(string(body), checker.Contains, "traefik_entrypoint_responses_bytes_total{code=\"200\",entrypoint=\"webA\",method=\"GET\",protocol=\"http\"} 31") + assert.Contains(s.T(), string(body), "traefik_entrypoint_requests_total{code=\"200\",entrypoint=\"webA\",method=\"GET\",protocol=\"http\"} 1") + assert.Contains(s.T(), string(body), "traefik_entrypoint_requests_bytes_total{code=\"200\",entrypoint=\"webA\",method=\"GET\",protocol=\"http\"} 0") + assert.Contains(s.T(), string(body), "traefik_entrypoint_responses_bytes_total{code=\"200\",entrypoint=\"webA\",method=\"GET\",protocol=\"http\"} 31") - c.Assert(string(body), checker.Contains, "traefik_service_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-without@file\"} 1") - c.Assert(string(body), checker.Contains, "traefik_service_requests_bytes_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-without@file\"} 0") - c.Assert(string(body), checker.Contains, "traefik_service_responses_bytes_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-without@file\"} 31") + assert.Contains(s.T(), string(body), "traefik_service_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-without@file\"} 1") + assert.Contains(s.T(), string(body), "traefik_service_requests_bytes_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-without@file\"} 0") + assert.Contains(s.T(), string(body), "traefik_service_responses_bytes_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-without@file\"} 31") // For forbidden requests, the entrypoints have metrics, the services don't. - c.Assert(string(body), checker.Contains, "traefik_entrypoint_requests_total{code=\"413\",entrypoint=\"webB\",method=\"GET\",protocol=\"http\"} 1") - c.Assert(string(body), checker.Contains, "traefik_entrypoint_requests_bytes_total{code=\"413\",entrypoint=\"webB\",method=\"GET\",protocol=\"http\"} 0") - c.Assert(string(body), checker.Contains, "traefik_entrypoint_responses_bytes_total{code=\"413\",entrypoint=\"webB\",method=\"GET\",protocol=\"http\"} 24") + assert.Contains(s.T(), string(body), "traefik_entrypoint_requests_total{code=\"413\",entrypoint=\"webB\",method=\"GET\",protocol=\"http\"} 1") + assert.Contains(s.T(), string(body), "traefik_entrypoint_requests_bytes_total{code=\"413\",entrypoint=\"webB\",method=\"GET\",protocol=\"http\"} 0") + assert.Contains(s.T(), string(body), "traefik_entrypoint_responses_bytes_total{code=\"413\",entrypoint=\"webB\",method=\"GET\",protocol=\"http\"} 24") // For disallowed responses, the entrypoint and service metrics don't have the same status code. - c.Assert(string(body), checker.Contains, "traefik_entrypoint_requests_bytes_total{code=\"500\",entrypoint=\"webC\",method=\"GET\",protocol=\"http\"} 0") - c.Assert(string(body), checker.Contains, "traefik_entrypoint_requests_total{code=\"500\",entrypoint=\"webC\",method=\"GET\",protocol=\"http\"} 1") - c.Assert(string(body), checker.Contains, "traefik_entrypoint_responses_bytes_total{code=\"500\",entrypoint=\"webC\",method=\"GET\",protocol=\"http\"} 21") + assert.Contains(s.T(), string(body), "traefik_entrypoint_requests_bytes_total{code=\"500\",entrypoint=\"webC\",method=\"GET\",protocol=\"http\"} 0") + assert.Contains(s.T(), string(body), "traefik_entrypoint_requests_total{code=\"500\",entrypoint=\"webC\",method=\"GET\",protocol=\"http\"} 1") + assert.Contains(s.T(), string(body), "traefik_entrypoint_responses_bytes_total{code=\"500\",entrypoint=\"webC\",method=\"GET\",protocol=\"http\"} 21") - c.Assert(string(body), checker.Contains, "traefik_service_requests_bytes_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-resp@file\"} 0") - c.Assert(string(body), checker.Contains, "traefik_service_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-resp@file\"} 1") - c.Assert(string(body), checker.Contains, "traefik_service_responses_bytes_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-resp@file\"} 31") + assert.Contains(s.T(), string(body), "traefik_service_requests_bytes_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-resp@file\"} 0") + assert.Contains(s.T(), string(body), "traefik_service_requests_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-resp@file\"} 1") + assert.Contains(s.T(), string(body), "traefik_service_responses_bytes_total{code=\"200\",method=\"GET\",protocol=\"http\",service=\"service-resp@file\"} 31") } -func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestMultipleProviderSameBackendName() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - whoami1IP := s.getComposeServiceIP(c, "whoami1") - whoami2IP := s.getComposeServiceIP(c, "whoami2") - file := s.adaptFile(c, "fixtures/multiple_provider.toml", struct{ IP string }{IP: whoami2IP}) - defer os.Remove(file) + whoami1IP := s.getComposeServiceIP("whoami1") + whoami2IP := s.getComposeServiceIP("whoami2") + file := s.adaptFile("fixtures/multiple_provider.toml", struct{ IP string }{IP: whoami2IP}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.BodyContains(whoami1IP)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/file", 1*time.Second, try.BodyContains(whoami2IP)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestIPStrategyAllowlist(c *check.C) { - s.createComposeProject(c, "allowlist") +func (s *SimpleSuite) TestIPStrategyAllowlist() { + s.createComposeProject("allowlist") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - cmd, output := s.traefikCmd(withConfigFile("fixtures/simple_allowlist.toml")) - defer output(c) + s.traefikCmd(withConfigFile("fixtures/simple_allowlist.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override.remoteaddr.allowlist.docker.local")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) testCases := []struct { desc string @@ -540,31 +479,87 @@ func (s *SimpleSuite) TestIPStrategyAllowlist(c *check.C) { req.RequestURI = "" err = try.Request(req, 1*time.Second, try.StatusCodeIs(test.expectedStatusCode)) - if err != nil { - c.Fatalf("Error while %s: %v", test.desc, err) - } + require.NoErrorf(s.T(), err, "Error during %s: %v", test.desc, err) } } -func (s *SimpleSuite) TestXForwardedHeaders(c *check.C) { - s.createComposeProject(c, "allowlist") +func (s *SimpleSuite) TestIPStrategyWhitelist() { + s.createComposeProject("whitelist") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - cmd, output := s.traefikCmd(withConfigFile("fixtures/simple_allowlist.toml")) - defer output(c) + s.traefikCmd(withConfigFile("fixtures/simple_whitelist.toml")) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override")) + require.NoError(s.T(), err) - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override.remoteaddr.whitelist.docker.local")) + require.NoError(s.T(), err) + + testCases := []struct { + desc string + xForwardedFor string + host string + expectedStatusCode int + }{ + { + desc: "override remote addr reject", + xForwardedFor: "8.8.8.8,8.8.8.8", + host: "override.remoteaddr.whitelist.docker.local", + expectedStatusCode: 403, + }, + { + desc: "override depth accept", + xForwardedFor: "8.8.8.8,10.0.0.1,127.0.0.1", + host: "override.depth.whitelist.docker.local", + expectedStatusCode: 200, + }, + { + desc: "override depth reject", + xForwardedFor: "10.0.0.1,8.8.8.8,127.0.0.1", + host: "override.depth.whitelist.docker.local", + expectedStatusCode: 403, + }, + { + desc: "override excludedIPs reject", + xForwardedFor: "10.0.0.3,10.0.0.1,10.0.0.2", + host: "override.excludedips.whitelist.docker.local", + expectedStatusCode: 403, + }, + { + desc: "override excludedIPs accept", + xForwardedFor: "8.8.8.8,10.0.0.1,10.0.0.2", + host: "override.excludedips.whitelist.docker.local", + expectedStatusCode: 200, + }, + } + + for _, test := range testCases { + req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) + req.Header.Set("X-Forwarded-For", test.xForwardedFor) + req.Host = test.host + req.RequestURI = "" + + err = try.Request(req, 1*time.Second, try.StatusCodeIs(test.expectedStatusCode)) + require.NoErrorf(s.T(), err, "Error during %s: %v", test.desc, err) + } +} + +func (s *SimpleSuite) TestXForwardedHeaders() { + s.createComposeProject("allowlist") + + s.composeUp() + defer s.composeDown() + + s.traefikCmd(withConfigFile("fixtures/simple_allowlist.toml")) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override.remoteaddr.allowlist.docker.local")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = "override.depth.allowlist.docker.local" req.Header.Set("X-Forwarded-For", "8.8.8.8,10.0.0.1,127.0.0.1") @@ -573,29 +568,23 @@ func (s *SimpleSuite) TestXForwardedHeaders(c *check.C) { try.StatusCodeIs(http.StatusOK), try.BodyContains("X-Forwarded-Proto", "X-Forwarded-For", "X-Forwarded-Host", "X-Forwarded-Host", "X-Forwarded-Port", "X-Forwarded-Server", "X-Real-Ip")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestMultiProvider(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestMultiProvider() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - whoamiURL := "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "whoami1"), "80") + whoamiURL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80") - file := s.adaptFile(c, "fixtures/multiprovider.toml", struct{ Server string }{Server: whoamiURL}) - defer os.Remove(file) + file := s.adaptFile("fixtures/multiprovider.toml", struct{ Server string }{Server: whoamiURL}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("service")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("service")) + require.NoError(s.T(), err) config := dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ @@ -611,39 +600,33 @@ func (s *SimpleSuite) TestMultiProvider(c *check.C) { } jsonContent, err := json.Marshal(config) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(jsonContent)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) response, err := http.DefaultClient.Do(request) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("PathPrefix(`/`)")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("CustomValue")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestSimpleConfigurationHostRequestTrailingPeriod(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestSimpleConfigurationHostRequestTrailingPeriod() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - whoamiURL := "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "whoami1"), "80") + whoamiURL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80") - file := s.adaptFile(c, "fixtures/file/simple-hosts.toml", struct{ Server string }{Server: whoamiURL}) - defer os.Remove(file) + file := s.adaptFile("fixtures/file/simple-hosts.toml", struct{ Server string }{Server: whoamiURL}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) testCases := []struct { desc string @@ -669,177 +652,133 @@ func (s *SimpleSuite) TestSimpleConfigurationHostRequestTrailingPeriod(c *check. for _, test := range testCases { req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Host = test.requestHost err = try.Request(req, 1*time.Second, try.StatusCodeIs(http.StatusOK)) - if err != nil { - c.Fatalf("Error while testing %s: %v", test.desc, err) - } + require.NoErrorf(s.T(), err, "Error while testing %s: %v", test.desc, err) } } -func (s *SimpleSuite) TestRouterConfigErrors(c *check.C) { - file := s.adaptFile(c, "fixtures/router_errors.toml", struct{}{}) - defer os.Remove(file) +func (s *SimpleSuite) TestRouterConfigErrors() { + file := s.adaptFile("fixtures/router_errors.toml", struct{}{}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // All errors - err = try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1000*time.Millisecond, try.BodyContains(`["middleware \"unknown@file\" does not exist","found different TLS options for routers on the same host snitest.net, so using the default TLS options instead"]`)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1000*time.Millisecond, try.BodyContains(`["middleware \"unknown@file\" does not exist","found different TLS options for routers on the same host snitest.net, so using the default TLS options instead"]`)) + require.NoError(s.T(), err) // router3 has an error because it uses an unknown entrypoint err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // router4 is enabled, but in warning state because its tls options conf was messed up err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router4@file", 1000*time.Millisecond, try.BodyContains(`"status":"warning"`)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // router5 is disabled because its middleware conf is broken err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router5@file", 1000*time.Millisecond, try.BodyContains()) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestServiceConfigErrors(c *check.C) { - file := s.adaptFile(c, "fixtures/service_errors.toml", struct{}{}) - defer os.Remove(file) +func (s *SimpleSuite) TestServiceConfigErrors() { + file := s.adaptFile("fixtures/service_errors.toml", struct{}{}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains(`["the service \"service1@file\" does not have any type defined"]`)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains(`["the service \"service1@file\" does not have any type defined"]`)) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/http/services/service1@file", 1000*time.Millisecond, try.BodyContains(`"status":"disabled"`)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/http/services/service2@file", 1000*time.Millisecond, try.BodyContains(`"status":"enabled"`)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestTCPRouterConfigErrors(c *check.C) { - file := s.adaptFile(c, "fixtures/router_errors.toml", struct{}{}) - defer os.Remove(file) +func (s *SimpleSuite) TestTCPRouterConfigErrors() { + file := s.adaptFile("fixtures/router_errors.toml", struct{}{}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // router3 has an error because it uses an unknown entrypoint - err = try.GetRequest("http://127.0.0.1:8080/api/tcp/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/tcp/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router")) + require.NoError(s.T(), err) // router4 has an unsupported Rule err = try.GetRequest("http://127.0.0.1:8080/api/tcp/routers/router4@file", 1000*time.Millisecond, try.BodyContains("invalid rule: \\\"Host(`mydomain.com`)\\\"")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestTCPServiceConfigErrors(c *check.C) { - file := s.adaptFile(c, "fixtures/tcp/service_errors.toml", struct{}{}) - defer os.Remove(file) +func (s *SimpleSuite) TestTCPServiceConfigErrors() { + file := s.adaptFile("fixtures/tcp/service_errors.toml", struct{}{}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/tcp/services", 1000*time.Millisecond, try.BodyContains(`["the service \"service1@file\" does not have any type defined"]`)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/tcp/services", 1000*time.Millisecond, try.BodyContains(`["the service \"service1@file\" does not have any type defined"]`)) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/tcp/services/service1@file", 1000*time.Millisecond, try.BodyContains(`"status":"disabled"`)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/tcp/services/service2@file", 1000*time.Millisecond, try.BodyContains(`"status":"enabled"`)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestUDPRouterConfigErrors(c *check.C) { - file := s.adaptFile(c, "fixtures/router_errors.toml", struct{}{}) - defer os.Remove(file) +func (s *SimpleSuite) TestUDPRouterConfigErrors() { + file := s.adaptFile("fixtures/router_errors.toml", struct{}{}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/udp/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/udp/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router")) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestUDPServiceConfigErrors(c *check.C) { - file := s.adaptFile(c, "fixtures/udp/service_errors.toml", struct{}{}) - defer os.Remove(file) +func (s *SimpleSuite) TestUDPServiceConfigErrors() { + file := s.adaptFile("fixtures/udp/service_errors.toml", struct{}{}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/udp/services", 1000*time.Millisecond, try.BodyContains(`["the UDP service \"service1@file\" does not have any type defined"]`)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/udp/services", 1000*time.Millisecond, try.BodyContains(`["the UDP service \"service1@file\" does not have any type defined"]`)) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/udp/services/service1@file", 1000*time.Millisecond, try.BodyContains(`"status":"disabled"`)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/udp/services/service2@file", 1000*time.Millisecond, try.BodyContains(`"status":"enabled"`)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestWRR(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestWRR() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - whoami1IP := s.getComposeServiceIP(c, "whoami1") - whoami2IP := s.getComposeServiceIP(c, "whoami2") + whoami1IP := s.getComposeServiceIP("whoami1") + whoami2IP := s.getComposeServiceIP("whoami2") - file := s.adaptFile(c, "fixtures/wrr.toml", struct { + file := s.adaptFile("fixtures/wrr.toml", struct { Server1 string Server2 string }{Server1: "http://" + whoami1IP, Server2: "http://" + whoami2IP}) - defer os.Remove(file) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1", "service2")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1", "service2")) + require.NoError(s.T(), err) repartition := map[string]int{} for i := 0; i < 4; i++ { req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) response, err := http.DefaultClient.Do(req) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) body, err := io.ReadAll(response.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if strings.Contains(string(body), whoami1IP) { repartition[whoami1IP]++ @@ -849,50 +788,44 @@ func (s *SimpleSuite) TestWRR(c *check.C) { } } - c.Assert(repartition[whoami1IP], checker.Equals, 3) - c.Assert(repartition[whoami2IP], checker.Equals, 1) + assert.Equal(s.T(), 3, repartition[whoami1IP]) + assert.Equal(s.T(), 1, repartition[whoami2IP]) } -func (s *SimpleSuite) TestWRRSticky(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestWRRSticky() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - whoami1IP := s.getComposeServiceIP(c, "whoami1") - whoami2IP := s.getComposeServiceIP(c, "whoami2") + whoami1IP := s.getComposeServiceIP("whoami1") + whoami2IP := s.getComposeServiceIP("whoami2") - file := s.adaptFile(c, "fixtures/wrr_sticky.toml", struct { + file := s.adaptFile("fixtures/wrr_sticky.toml", struct { Server1 string Server2 string }{Server1: "http://" + whoami1IP, Server2: "http://" + whoami2IP}) - defer os.Remove(file) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1", "service2")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1", "service2")) + require.NoError(s.T(), err) repartition := map[string]int{} req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) for i := 0; i < 4; i++ { response, err := http.DefaultClient.Do(req) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) for _, cookie := range response.Cookies() { req.AddCookie(cookie) } body, err := io.ReadAll(response.Body) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if strings.Contains(string(body), whoami1IP) { repartition[whoami1IP]++ @@ -902,11 +835,11 @@ func (s *SimpleSuite) TestWRRSticky(c *check.C) { } } - c.Assert(repartition[whoami1IP], checker.Equals, 4) - c.Assert(repartition[whoami2IP], checker.Equals, 0) + assert.Equal(s.T(), 4, repartition[whoami1IP]) + assert.Equal(s.T(), 0, repartition[whoami2IP]) } -func (s *SimpleSuite) TestMirror(c *check.C) { +func (s *SimpleSuite) TestMirror() { var count, countMirror1, countMirror2 int32 main := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -925,64 +858,54 @@ func (s *SimpleSuite) TestMirror(c *check.C) { mirror1Server := mirror1.URL mirror2Server := mirror2.URL - file := s.adaptFile(c, "fixtures/mirror.toml", struct { + file := s.adaptFile("fixtures/mirror.toml", struct { MainServer string Mirror1Server string Mirror2Server string }{MainServer: mainServer, Mirror1Server: mirror1Server, Mirror2Server: mirror2Server}) - defer os.Remove(file) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("mirror1", "mirror2", "service1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("mirror1", "mirror2", "service1")) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) for i := 0; i < 10; i++ { response, err := http.DefaultClient.Do(req) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) } countTotal := atomic.LoadInt32(&count) val1 := atomic.LoadInt32(&countMirror1) val2 := atomic.LoadInt32(&countMirror2) - c.Assert(countTotal, checker.Equals, int32(10)) - c.Assert(val1, checker.Equals, int32(1)) - c.Assert(val2, checker.Equals, int32(5)) + assert.Equal(s.T(), int32(10), countTotal) + assert.Equal(s.T(), int32(1), val1) + assert.Equal(s.T(), int32(5), val2) } -func (s *SimpleSuite) TestMirrorWithBody(c *check.C) { +func (s *SimpleSuite) TestMirrorWithBody() { var count, countMirror1, countMirror2 int32 body20 := make([]byte, 20) _, err := rand.Read(body20) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) body5 := make([]byte, 5) _, err = rand.Read(body5) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) verifyBody := func(req *http.Request) { b, _ := io.ReadAll(req.Body) switch req.Header.Get("Size") { case "20": - if !bytes.Equal(b, body20) { - c.Fatalf("Not Equals \n%v \n%v", body20, b) - } + require.Equal(s.T(), body20, b) case "5": - if !bytes.Equal(b, body5) { - c.Fatalf("Not Equals \n%v \n%v", body5, b) - } + require.Equal(s.T(), body5, b) default: - c.Fatal("Size header not present") + s.T().Fatal("Size header not present") } } @@ -1005,84 +928,78 @@ func (s *SimpleSuite) TestMirrorWithBody(c *check.C) { mirror1Server := mirror1.URL mirror2Server := mirror2.URL - file := s.adaptFile(c, "fixtures/mirror.toml", struct { + file := s.adaptFile("fixtures/mirror.toml", struct { MainServer string Mirror1Server string Mirror2Server string }{MainServer: mainServer, Mirror1Server: mirror1Server, Mirror2Server: mirror2Server}) - defer os.Remove(file) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) - - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("mirror1", "mirror2", "service1")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", bytes.NewBuffer(body20)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Header.Set("Size", "20") for i := 0; i < 10; i++ { response, err := http.DefaultClient.Do(req) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) } countTotal := atomic.LoadInt32(&count) val1 := atomic.LoadInt32(&countMirror1) val2 := atomic.LoadInt32(&countMirror2) - c.Assert(countTotal, checker.Equals, int32(10)) - c.Assert(val1, checker.Equals, int32(1)) - c.Assert(val2, checker.Equals, int32(5)) + assert.Equal(s.T(), int32(10), countTotal) + assert.Equal(s.T(), int32(1), val1) + assert.Equal(s.T(), int32(5), val2) atomic.StoreInt32(&count, 0) atomic.StoreInt32(&countMirror1, 0) atomic.StoreInt32(&countMirror2, 0) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoamiWithMaxBody", bytes.NewBuffer(body5)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Header.Set("Size", "5") for i := 0; i < 10; i++ { response, err := http.DefaultClient.Do(req) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) } countTotal = atomic.LoadInt32(&count) val1 = atomic.LoadInt32(&countMirror1) val2 = atomic.LoadInt32(&countMirror2) - c.Assert(countTotal, checker.Equals, int32(10)) - c.Assert(val1, checker.Equals, int32(1)) - c.Assert(val2, checker.Equals, int32(5)) + assert.Equal(s.T(), int32(10), countTotal) + assert.Equal(s.T(), int32(1), val1) + assert.Equal(s.T(), int32(5), val2) atomic.StoreInt32(&count, 0) atomic.StoreInt32(&countMirror1, 0) atomic.StoreInt32(&countMirror2, 0) req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoamiWithMaxBody", bytes.NewBuffer(body20)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Header.Set("Size", "20") for i := 0; i < 10; i++ { response, err := http.DefaultClient.Do(req) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) } countTotal = atomic.LoadInt32(&count) val1 = atomic.LoadInt32(&countMirror1) val2 = atomic.LoadInt32(&countMirror2) - c.Assert(countTotal, checker.Equals, int32(10)) - c.Assert(val1, checker.Equals, int32(0)) - c.Assert(val2, checker.Equals, int32(0)) + assert.Equal(s.T(), int32(10), countTotal) + assert.Equal(s.T(), int32(0), val1) + assert.Equal(s.T(), int32(0), val2) } -func (s *SimpleSuite) TestMirrorCanceled(c *check.C) { +func (s *SimpleSuite) TestMirrorCanceled() { var count, countMirror1, countMirror2 int32 main := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -1102,26 +1019,20 @@ func (s *SimpleSuite) TestMirrorCanceled(c *check.C) { mirror1Server := mirror1.URL mirror2Server := mirror2.URL - file := s.adaptFile(c, "fixtures/mirror.toml", struct { + file := s.adaptFile("fixtures/mirror.toml", struct { MainServer string Mirror1Server string Mirror2Server string }{MainServer: mainServer, Mirror1Server: mirror1Server, Mirror2Server: mirror2Server}) - defer os.Remove(file) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("mirror1", "mirror2", "service1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("mirror1", "mirror2", "service1")) + require.NoError(s.T(), err) for i := 0; i < 5; i++ { req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) client := &http.Client{ Timeout: time.Second, @@ -1133,38 +1044,32 @@ func (s *SimpleSuite) TestMirrorCanceled(c *check.C) { val1 := atomic.LoadInt32(&countMirror1) val2 := atomic.LoadInt32(&countMirror2) - c.Assert(countTotal, checker.Equals, int32(5)) - c.Assert(val1, checker.Equals, int32(0)) - c.Assert(val2, checker.Equals, int32(0)) + assert.Equal(s.T(), int32(5), countTotal) + assert.Equal(s.T(), int32(0), val1) + assert.Equal(s.T(), int32(0), val2) } -func (s *SimpleSuite) TestSecureAPI(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestSecureAPI() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - file := s.adaptFile(c, "./fixtures/simple_secure_api.toml", struct{}{}) - defer os.Remove(file) + file := s.adaptFile("./fixtures/simple_secure_api.toml", struct{}{}) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8000/secure/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8000/secure/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) { +func (s *SimpleSuite) TestContentTypeDisableAutoDetect() { srv1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header()["Content-Type"] = nil path := strings.TrimPrefix(req.URL.Path, "/autodetect") @@ -1177,7 +1082,7 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) { rw.WriteHeader(http.StatusOK) _, err := rw.Write([]byte(".testcss { }")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) case "/pdf": if strings.Contains(req.URL.Path, "/ct") { rw.Header().Set("Content-Type", "application/pdf") @@ -1186,38 +1091,32 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) { rw.WriteHeader(http.StatusOK) data, err := os.ReadFile("fixtures/test.pdf") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = rw.Write(data) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } })) defer srv1.Close() - file := s.adaptFile(c, "fixtures/simple_contenttype.toml", struct { + file := s.adaptFile("fixtures/simple_contenttype.toml", struct { Server string }{ Server: srv1.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/css/ct", time.Second, try.HasHeaderValue("Content-Type", "text/css", false)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/pdf/ct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/css/noct", time.Second, func(res *http.Response) error { if ct, ok := res.Header["Content-Type"]; ok { @@ -1225,7 +1124,7 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) { } return nil }) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/pdf/noct", time.Second, func(res *http.Response) error { if ct, ok := res.Header["Content-Type"]; ok { @@ -1233,43 +1132,37 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) { } return nil }) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/autodetect/css/ct", time.Second, try.HasHeaderValue("Content-Type", "text/css", false)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/autodetect/pdf/ct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/autodetect/css/noct", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/autodetect/pdf/noct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *SimpleSuite) TestMuxer(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestMuxer() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "whoami1"), "80") + whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80") - file := s.adaptFile(c, "fixtures/simple_muxer.toml", struct { + file := s.adaptFile("fixtures/simple_muxer.toml", struct { Server1 string }{whoami1URL}) - defer os.Remove(file) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("!Host")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("!Host")) + require.NoError(s.T(), err) testCases := []struct { desc string @@ -1349,79 +1242,68 @@ func (s *SimpleSuite) TestMuxer(c *check.C) { for _, test := range testCases { conn, err := net.Dial("tcp", test.target) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = conn.Write([]byte(test.request)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) resp, err := http.ReadResponse(bufio.NewReader(conn), nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - c.Assert(resp.StatusCode, checker.Equals, test.expected, check.Commentf(test.desc)) + assert.Equal(s.T(), test.expected, resp.StatusCode, test.desc) if test.body != "" { body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) - c.Assert(string(body), checker.Contains, test.body) + require.NoError(s.T(), err) + assert.Contains(s.T(), string(body), test.body) } } } -func (s *SimpleSuite) TestDebugLog(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestDebugLog() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - file := s.adaptFile(c, "fixtures/simple_debug_log.toml", struct{}{}) - defer os.Remove(file) + file := s.adaptFile("fixtures/simple_debug_log.toml", struct{}{}) - cmd, output := s.cmdTraefik(withConfigFile(file)) + _, output := s.cmdTraefik(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/whoami`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/whoami`)")) + require.NoError(s.T(), err) req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/whoami", http.NoBody) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req.Header.Set("Autorization", "Bearer ThisIsABearerToken") response, err := http.DefaultClient.Do(req) - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) if regexp.MustCompile("ThisIsABearerToken").MatchReader(output) { - c.Logf("Traefik Logs: %s", output.String()) - c.Log("Found Authorization Header in Traefik DEBUG logs") - c.Fail() + log.Info().Msgf("Traefik Logs: %s", output.String()) + log.Info().Msg("Found Authorization Header in Traefik DEBUG logs") + s.T().Fail() } } -func (s *SimpleSuite) TestEncodeSemicolons(c *check.C) { - s.createComposeProject(c, "base") +func (s *SimpleSuite) TestEncodeSemicolons() { + s.createComposeProject("base") - s.composeUp(c) - defer s.composeDown(c) + s.composeUp() + defer s.composeDown() - whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "whoami1"), "80") + whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80") - file := s.adaptFile(c, "fixtures/simple_encode_semicolons.toml", struct { + file := s.adaptFile("fixtures/simple_encode_semicolons.toml", struct { Server1 string }{whoami1URL}) - defer os.Remove(file) - cmd, output := s.traefikCmd(withConfigFile(file)) - defer output(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`other.localhost`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`other.localhost`)")) + require.NoError(s.T(), err) testCases := []struct { desc string @@ -1448,22 +1330,45 @@ func (s *SimpleSuite) TestEncodeSemicolons(c *check.C) { for _, test := range testCases { conn, err := net.Dial("tcp", test.target) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = conn.Write([]byte(test.request)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) resp, err := http.ReadResponse(bufio.NewReader(conn), nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if resp.StatusCode != test.expected { - c.Errorf("%s failed with %d instead of %d", test.desc, resp.StatusCode, test.expected) + log.Info().Msgf("%s failed with %d instead of %d", test.desc, resp.StatusCode, test.expected) } if test.body != "" { body, err := io.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) - c.Assert(string(body), checker.Contains, test.body) + require.NoError(s.T(), err) + assert.Contains(s.T(), string(body), test.body) } } } + +func (s *SimpleSuite) TestDenyFragment() { + s.createComposeProject("base") + + s.composeUp() + defer s.composeDown() + + s.traefikCmd(withConfigFile("fixtures/simple_default.toml")) + + // Expected a 404 as we did not configure anything + err := try.GetRequest("http://127.0.0.1:8000/", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) + require.NoError(s.T(), err) + + conn, err := net.Dial("tcp", "127.0.0.1:8000") + require.NoError(s.T(), err) + + _, err = conn.Write([]byte("GET /#/?bar=toto;boo=titi HTTP/1.1\nHost: other.localhost\n\n")) + require.NoError(s.T(), err) + + resp, err := http.ReadResponse(bufio.NewReader(conn), nil) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode) +} diff --git a/integration/tcp_test.go b/integration/tcp_test.go index 43a7158d0..732bc1f03 100644 --- a/integration/tcp_test.go +++ b/integration/tcp_test.go @@ -5,63 +5,69 @@ import ( "crypto/x509" "errors" "fmt" + "io" "net" "net/http" "net/http/httptest" - "os" "strings" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type TCPSuite struct{ BaseSuite } -func (s *TCPSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "tcp") - s.composeUp(c) +func TestTCPSuite(t *testing.T) { + suite.Run(t, new(TCPSuite)) } -func (s *TCPSuite) TestMixed(c *check.C) { - file := s.adaptFile(c, "fixtures/tcp/mixed.toml", struct { +func (s *TCPSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("tcp") + s.composeUp() +} + +func (s *TCPSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *TCPSuite) TestMixed() { + file := s.adaptFile("fixtures/tcp/mixed.toml", struct { Whoami string WhoamiA string WhoamiB string WhoamiNoCert string }{ - Whoami: "http://" + s.getComposeServiceIP(c, "whoami") + ":80", - WhoamiA: s.getComposeServiceIP(c, "whoami-a") + ":8080", - WhoamiB: s.getComposeServiceIP(c, "whoami-b") + ":8080", - WhoamiNoCert: s.getComposeServiceIP(c, "whoami-no-cert") + ":8080", + Whoami: "http://" + s.getComposeServiceIP("whoami") + ":80", + WhoamiA: s.getComposeServiceIP("whoami-a") + ":8080", + WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080", + WhoamiNoCert: s.getComposeServiceIP("whoami-no-cert") + ":8080", }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("Path(`/test`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("Path(`/test`)")) + require.NoError(s.T(), err) // Traefik passes through, termination handled by whoami-a out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test") - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-a") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-a") // Traefik passes through, termination handled by whoami-b out, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-b") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-b") // Termination handled by traefik out, err = guessWho("127.0.0.1:8093", "whoami-c.test", true) - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-no-cert") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-no-cert") tr1 := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -69,174 +75,143 @@ func (s *TCPSuite) TestMixed(c *check.C) { }, } req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8093/whoami/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.RequestWithTransport(req, 10*time.Second, tr1, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:8093/not-found/", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.RequestWithTransport(req, 10*time.Second, tr1, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8093/test", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8093/not-found", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *TCPSuite) TestTLSOptions(c *check.C) { - file := s.adaptFile(c, "fixtures/tcp/multi-tls-options.toml", struct { +func (s *TCPSuite) TestTLSOptions() { + file := s.adaptFile("fixtures/tcp/multi-tls-options.toml", struct { WhoamiNoCert string }{ - WhoamiNoCert: s.getComposeServiceIP(c, "whoami-no-cert") + ":8080", + WhoamiNoCert: s.getComposeServiceIP("whoami-no-cert") + ":8080", }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-c.test`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-c.test`)")) + require.NoError(s.T(), err) // Check that we can use a client tls version <= 1.2 with hostSNI 'whoami-c.test' out, err := guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-c.test", true, tls.VersionTLS12) - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-no-cert") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-no-cert") // Check that we can use a client tls version <= 1.3 with hostSNI 'whoami-d.test' out, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS13) - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-no-cert") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-no-cert") // Check that we cannot use a client tls version <= 1.2 with hostSNI 'whoami-d.test' _, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12) - c.Assert(err, checker.NotNil) - c.Assert(err.Error(), checker.Contains, "protocol version not supported") + assert.ErrorContains(s.T(), err, "protocol version not supported") // Check that we can't reach a route with an invalid mTLS configuration. conn, err := tls.Dial("tcp", "127.0.0.1:8093", &tls.Config{ ServerName: "whoami-i.test", InsecureSkipVerify: true, }) - c.Assert(conn, checker.IsNil) - c.Assert(err, checker.NotNil) + assert.Nil(s.T(), conn) + assert.Error(s.T(), err) } -func (s *TCPSuite) TestNonTLSFallback(c *check.C) { - file := s.adaptFile(c, "fixtures/tcp/non-tls-fallback.toml", struct { +func (s *TCPSuite) TestNonTLSFallback() { + file := s.adaptFile("fixtures/tcp/non-tls-fallback.toml", struct { WhoamiA string WhoamiB string WhoamiNoCert string WhoamiNoTLS string }{ - WhoamiA: s.getComposeServiceIP(c, "whoami-a") + ":8080", - WhoamiB: s.getComposeServiceIP(c, "whoami-b") + ":8080", - WhoamiNoCert: s.getComposeServiceIP(c, "whoami-no-cert") + ":8080", - WhoamiNoTLS: s.getComposeServiceIP(c, "whoami-no-tls") + ":8080", + WhoamiA: s.getComposeServiceIP("whoami-a") + ":8080", + WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080", + WhoamiNoCert: s.getComposeServiceIP("whoami-no-cert") + ":8080", + WhoamiNoTLS: s.getComposeServiceIP("whoami-no-tls") + ":8080", }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)")) + require.NoError(s.T(), err) // Traefik passes through, termination handled by whoami-a out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test") - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-a") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-a") // Traefik passes through, termination handled by whoami-b out, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-b") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-b") // Termination handled by traefik out, err = guessWho("127.0.0.1:8093", "whoami-c.test", true) - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-no-cert") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-no-cert") out, err = guessWho("127.0.0.1:8093", "", false) - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-no-tls") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-no-tls") } -func (s *TCPSuite) TestNonTlsTcp(c *check.C) { - file := s.adaptFile(c, "fixtures/tcp/non-tls.toml", struct { +func (s *TCPSuite) TestNonTlsTcp() { + file := s.adaptFile("fixtures/tcp/non-tls.toml", struct { WhoamiNoTLS string }{ - WhoamiNoTLS: s.getComposeServiceIP(c, "whoami-no-tls") + ":8080", + WhoamiNoTLS: s.getComposeServiceIP("whoami-no-tls") + ":8080", }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)")) + require.NoError(s.T(), err) // Traefik will forward every requests on the given port to whoami-no-tls out, err := guessWho("127.0.0.1:8093", "", false) - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-no-tls") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-no-tls") } -func (s *TCPSuite) TestCatchAllNoTLS(c *check.C) { - file := s.adaptFile(c, "fixtures/tcp/catch-all-no-tls.toml", struct { +func (s *TCPSuite) TestCatchAllNoTLS() { + file := s.adaptFile("fixtures/tcp/catch-all-no-tls.toml", struct { WhoamiBannerAddress string }{ - WhoamiBannerAddress: s.getComposeServiceIP(c, "whoami-banner") + ":8080", + WhoamiBannerAddress: s.getComposeServiceIP("whoami-banner") + ":8080", }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)")) + require.NoError(s.T(), err) // Traefik will forward every requests on the given port to whoami-no-tls out, err := welcome("127.0.0.1:8093") - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "Welcome") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "Welcome") } -func (s *TCPSuite) TestCatchAllNoTLSWithHTTPS(c *check.C) { - file := s.adaptFile(c, "fixtures/tcp/catch-all-no-tls-with-https.toml", struct { +func (s *TCPSuite) TestCatchAllNoTLSWithHTTPS() { + file := s.adaptFile("fixtures/tcp/catch-all-no-tls-with-https.toml", struct { WhoamiNoTLSAddress string WhoamiURL string }{ - WhoamiNoTLSAddress: s.getComposeServiceIP(c, "whoami-no-tls") + ":8080", - WhoamiURL: "http://" + s.getComposeServiceIP(c, "whoami") + ":80", + WhoamiNoTLSAddress: s.getComposeServiceIP("whoami-no-tls") + ":8080", + WhoamiURL: "http://" + s.getComposeServiceIP("whoami") + ":80", }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`*`)")) + require.NoError(s.T(), err) req := httptest.NewRequest(http.MethodGet, "https://127.0.0.1:8093/test", nil) req.RequestURI = "" @@ -246,64 +221,76 @@ func (s *TCPSuite) TestCatchAllNoTLSWithHTTPS(c *check.C) { InsecureSkipVerify: true, }, }, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *TCPSuite) TestMiddlewareAllowList(c *check.C) { - file := s.adaptFile(c, "fixtures/tcp/ipallowlist.toml", struct { +func (s *TCPSuite) TestMiddlewareAllowList() { + file := s.adaptFile("fixtures/tcp/ip-allowlist.toml", struct { WhoamiA string WhoamiB string }{ - WhoamiA: s.getComposeServiceIP(c, "whoami-a") + ":8080", - WhoamiB: s.getComposeServiceIP(c, "whoami-b") + ":8080", + WhoamiA: s.getComposeServiceIP("whoami-a") + ":8080", + WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080", }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-a.test`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-a.test`)")) + require.NoError(s.T(), err) // Traefik not passes through, ipAllowList closes connection _, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test") - c.Assert(err, checker.ErrorMatches, "EOF") + assert.ErrorIs(s.T(), err, io.EOF) // Traefik passes through, termination handled by whoami-b out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") - c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, "whoami-b") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-b") } -func (s *TCPSuite) TestWRR(c *check.C) { - file := s.adaptFile(c, "fixtures/tcp/wrr.toml", struct { +func (s *TCPSuite) TestMiddlewareWhiteList() { + file := s.adaptFile("fixtures/tcp/ip-whitelist.toml", struct { + WhoamiA string + WhoamiB string + }{ + WhoamiA: s.getComposeServiceIP("whoami-a") + ":8080", + WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080", + }) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-a.test`)")) + require.NoError(s.T(), err) + + // Traefik not passes through, ipWhiteList closes connection + _, err = guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-a.test") + assert.ErrorIs(s.T(), err, io.EOF) + + // Traefik passes through, termination handled by whoami-b + out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") + require.NoError(s.T(), err) + assert.Contains(s.T(), out, "whoami-b") +} + +func (s *TCPSuite) TestWRR() { + file := s.adaptFile("fixtures/tcp/wrr.toml", struct { WhoamiB string WhoamiAB string }{ - WhoamiB: s.getComposeServiceIP(c, "whoami-b") + ":8080", - WhoamiAB: s.getComposeServiceIP(c, "whoami-ab") + ":8080", + WhoamiB: s.getComposeServiceIP("whoami-b") + ":8080", + WhoamiAB: s.getComposeServiceIP("whoami-ab") + ":8080", }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-b.test`)")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("HostSNI(`whoami-b.test`)")) + require.NoError(s.T(), err) call := map[string]int{} for i := 0; i < 4; i++ { // Traefik passes through, termination handled by whoami-b or whoami-bb out, err := guessWhoTLSPassthrough("127.0.0.1:8093", "whoami-b.test") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) switch { case strings.Contains(out, "whoami-b"): call["whoami-b"]++ @@ -315,7 +302,7 @@ func (s *TCPSuite) TestWRR(c *check.C) { time.Sleep(time.Second) } - c.Assert(call, checker.DeepEquals, map[string]int{"whoami-b": 3, "whoami-ab": 1}) + assert.EqualValues(s.T(), call, map[string]int{"whoami-b": 3, "whoami-ab": 1}) } func welcome(addr string) (string, error) { diff --git a/integration/testdata/rawdata-crd-label-selector.json b/integration/testdata/rawdata-crd-label-selector.json index b5c9f1669..445a7daf3 100644 --- a/integration/testdata/rawdata-crd-label-selector.json +++ b/integration/testdata/rawdata-crd-label-selector.json @@ -45,7 +45,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.6:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -59,7 +59,7 @@ ], "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.6:80": "UP" + "http://10.42.0.5:80": "UP" } }, "noop@internal": { diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 81f397196..5724a7126 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -139,7 +139,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -153,7 +153,7 @@ ], "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.5:80": "UP" } }, "default-test2-route-23c7f4c450289ee29016@kubernetescrd": { @@ -163,7 +163,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -177,7 +177,7 @@ ], "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.5:80": "UP" } }, "default-testst-route-60ad45fcb5fc1f5f3629@kubernetescrd": { @@ -187,7 +187,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -202,7 +202,7 @@ ], "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.5:80": "UP" } }, "default-whoami-80@kubernetescrd": { @@ -212,7 +212,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -223,7 +223,7 @@ "status": "enabled", "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.5:80": "UP" } }, "default-wrr1@kubernetescrd": { @@ -295,7 +295,7 @@ "address": "10.42.0.2:8080" }, { - "address": "10.42.0.4:8080" + "address": "10.42.0.6:8080" } ] }, @@ -347,10 +347,10 @@ "loadBalancer": { "servers": [ { - "address": "10.42.0.5:8090" + "address": "10.42.0.4:8090" }, { - "address": "10.42.0.6:8090" + "address": "10.42.0.7:8090" } ] }, diff --git a/integration/testdata/rawdata-gateway.json b/integration/testdata/rawdata-gateway.json index d629cfc41..f8e364e66 100644 --- a/integration/testdata/rawdata-gateway.json +++ b/integration/testdata/rawdata-gateway.json @@ -127,7 +127,7 @@ "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -138,7 +138,7 @@ "status": "enabled", "serverStatus": { "http://10.42.0.3:80": "UP", - "http://10.42.0.4:80": "UP" + "http://10.42.0.5:80": "UP" } }, "noop@internal": { diff --git a/integration/testdata/rawdata-ingress-label-selector.json b/integration/testdata/rawdata-ingress-label-selector.json index d72826ae7..f8e4c88a3 100644 --- a/integration/testdata/rawdata-ingress-label-selector.json +++ b/integration/testdata/rawdata-ingress-label-selector.json @@ -83,10 +83,10 @@ "loadBalancer": { "servers": [ { - "url": "http://10.42.0.2:80" + "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.7:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -99,8 +99,8 @@ "default-test-ingress-whoami-test-whoami@kubernetes" ], "serverStatus": { - "http://10.42.0.2:80": "UP", - "http://10.42.0.7:80": "UP" + "http://10.42.0.3:80": "UP", + "http://10.42.0.5:80": "UP" } }, "noop@internal": { diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json index ecfa38ee6..305cb3015 100644 --- a/integration/testdata/rawdata-ingress.json +++ b/integration/testdata/rawdata-ingress.json @@ -119,10 +119,10 @@ "loadBalancer": { "servers": [ { - "url": "XXXX" + "url": "http://10.42.0.3:80" }, { - "url": "XXXX" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -136,18 +136,18 @@ "default-whoami-keep-route-whoami-test-keep-keep@kubernetes" ], "serverStatus": { - "http://XXXX": "UP", - "http://XXXX": "UP" + "http://10.42.0.3:80": "UP", + "http://10.42.0.5:80": "UP" } }, "default-whoami-http@kubernetes": { "loadBalancer": { "servers": [ { - "url": "http://10.42.0.10:80" + "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.8:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true, @@ -161,8 +161,8 @@ "default-test-ingress-whoami-test-whoami@kubernetes" ], "serverStatus": { - "http://10.42.0.10:80": "UP", - "http://10.42.0.8:80": "UP" + "http://10.42.0.3:80": "UP", + "http://10.42.0.5:80": "UP" } }, "noop@internal": { diff --git a/integration/testdata/rawdata-ingressclass.json b/integration/testdata/rawdata-ingressclass.json index 4215504c0..a020096cc 100644 --- a/integration/testdata/rawdata-ingressclass.json +++ b/integration/testdata/rawdata-ingressclass.json @@ -83,7 +83,7 @@ "loadBalancer": { "servers": [ { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.3:80" }, { "url": "http://10.42.0.5:80" @@ -99,7 +99,7 @@ "default-whoami-keep-route-whoami-test-keep-keep@kubernetes" ], "serverStatus": { - "http://10.42.0.4:80": "UP", + "http://10.42.0.3:80": "UP", "http://10.42.0.5:80": "UP" } }, diff --git a/integration/timeout_test.go b/integration/timeout_test.go index 457e2bcfb..8d958db9d 100644 --- a/integration/timeout_test.go +++ b/integration/timeout_test.go @@ -4,47 +4,53 @@ import ( "fmt" "net" "net/http" - "os" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type TimeoutSuite struct{ BaseSuite } -func (s *TimeoutSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "timeout") - s.composeUp(c) +func TestTimeoutSuite(t *testing.T) { + suite.Run(t, new(TimeoutSuite)) } -func (s *TimeoutSuite) TestForwardingTimeouts(c *check.C) { - timeoutEndpointIP := s.getComposeServiceIP(c, "timeoutEndpoint") - file := s.adaptFile(c, "fixtures/timeout/forwarding_timeouts.toml", struct{ TimeoutEndpoint string }{timeoutEndpointIP}) - defer os.Remove(file) +func (s *TimeoutSuite) SetupSuite() { + s.BaseSuite.SetupSuite() - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.createComposeProject("timeout") + s.composeUp() +} - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Path(`/dialTimeout`)")) - c.Assert(err, checker.IsNil) +func (s *TimeoutSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *TimeoutSuite) TestForwardingTimeouts() { + timeoutEndpointIP := s.getComposeServiceIP("timeoutEndpoint") + file := s.adaptFile("fixtures/timeout/forwarding_timeouts.toml", struct{ TimeoutEndpoint string }{timeoutEndpointIP}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Path(`/dialTimeout`)")) + require.NoError(s.T(), err) // This simulates a DialTimeout when connecting to the backend server. response, err := http.Get("http://127.0.0.1:8000/dialTimeout") - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusGatewayTimeout) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusGatewayTimeout, response.StatusCode) // Check that timeout service is available statusURL := fmt.Sprintf("http://%s/statusTest?status=200", net.JoinHostPort(timeoutEndpointIP, "9000")) - c.Assert(try.GetRequest(statusURL, 60*time.Second, try.StatusCodeIs(http.StatusOK)), checker.IsNil) + assert.NoError(s.T(), try.GetRequest(statusURL, 60*time.Second, try.StatusCodeIs(http.StatusOK))) // This simulates a ResponseHeaderTimeout. response, err = http.Get("http://127.0.0.1:8000/responseHeaderTimeout?sleep=1000") - c.Assert(err, checker.IsNil) - c.Assert(response.StatusCode, checker.Equals, http.StatusGatewayTimeout) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusGatewayTimeout, response.StatusCode) } diff --git a/integration/tls_client_headers_test.go b/integration/tls_client_headers_test.go index 08f0c7ea5..71d27ab71 100644 --- a/integration/tls_client_headers_test.go +++ b/integration/tls_client_headers_test.go @@ -4,11 +4,13 @@ import ( "crypto/tls" "net/http" "os" + "testing" "time" - "github.com/go-check/check" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) const ( @@ -19,20 +21,30 @@ const ( type TLSClientHeadersSuite struct{ BaseSuite } -func (s *TLSClientHeadersSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "tlsclientheaders") - s.composeUp(c) +func TestTLSClientHeadersSuite(t *testing.T) { + suite.Run(t, new(TLSClientHeadersSuite)) } -func (s *TLSClientHeadersSuite) TestTLSClientHeaders(c *check.C) { - rootCertContent, err := os.ReadFile(rootCertPath) - c.Assert(err, check.IsNil) - serverCertContent, err := os.ReadFile(certPemPath) - c.Assert(err, check.IsNil) - ServerKeyContent, err := os.ReadFile(certKeyPath) - c.Assert(err, check.IsNil) +func (s *TLSClientHeadersSuite) SetupSuite() { + s.BaseSuite.SetupSuite() - file := s.adaptFile(c, "fixtures/tlsclientheaders/simple.toml", struct { + s.createComposeProject("tlsclientheaders") + s.composeUp() +} + +func (s *TLSClientHeadersSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} + +func (s *TLSClientHeadersSuite) TestTLSClientHeaders() { + rootCertContent, err := os.ReadFile(rootCertPath) + assert.NoError(s.T(), err) + serverCertContent, err := os.ReadFile(certPemPath) + assert.NoError(s.T(), err) + ServerKeyContent, err := os.ReadFile(certKeyPath) + assert.NoError(s.T(), err) + + file := s.adaptFile("fixtures/tlsclientheaders/simple.toml", struct { RootCertContent string ServerCertContent string ServerKeyContent string @@ -41,22 +53,17 @@ func (s *TLSClientHeadersSuite) TestTLSClientHeaders(c *check.C) { ServerCertContent: string(serverCertContent), ServerKeyContent: string(ServerKeyContent), }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err = cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("PathPrefix(`/foo`)")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) request, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8443/foo", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) certificate, err := tls.LoadX509KeyPair(certPemPath, certKeyPath) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) tr := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -66,5 +73,5 @@ func (s *TLSClientHeadersSuite) TestTLSClientHeaders(c *check.C) { } err = try.RequestWithTransport(request, 2*time.Second, tr, try.BodyContains("Forwarded-Tls-Client-Cert: MIIDNTCCAh0CFD0QQcHXUJuKwMBYDA+bBExVSP26MA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxFTATBgNVBAoMDFRyYWVmaWsgTGFiczEQMA4GA1UECwwHdHJhZWZpazENMAsGA1UEAwwEcm9vdDAeFw0yMTAxMDgxNzQ0MjRaFw0zMTAxMDYxNzQ0MjRaMFgxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxFTATBgNVBAoMDFRyYWVmaWsgTGFiczEQMA4GA1UECwwHdHJhZWZpazEPMA0GA1UEAwwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYK2z8gLPOfFLgXNWP2460aeJ9vrH47x/lhKLlv4amSDHDx8Cmz/6blOUM8XOfMRW1xx++AgChWN9dx/kf7G2xlA5grZxRvUQ6xj7AvFG9TQUA3muNh2hvm9c3IjaZBNKH27bRKuDIBvZBvXdX4NL/aaFy7w7v7IKxk8j4WkfB23sgyH43g4b7NqKHJugZiedFu5GALmtLbShVOFbjWcre7Wvatdw8dIBmiFJqZQT3UjIuGAgqczIShtLxo4V+XyVkIPmzfPrRV+4zoMFIFOIaj3syyxb4krPBtxhe7nz2cWvvq0wePB2y4YbAAoVY8NYpd5JsMFwZtG6Uk59ygv4QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDaPg69wNeFNFisfBJTrscqVCTW+B80gMhpLdxXD+KO0/Wgc5xpB/wLSirNtRQyxAa3+EEcIwJv/wdh8EyjlDLSpFm/8ghntrKhkOfIOPDFE41M5HNfx/Fuh5btKEenOL/XdapqtNUt2ZE4RrsfbL79sPYepa9kDUVi2mCbeH5ollZ0MDU68HpB2YwHbCEuQNk5W3pjYK2NaDkVnxTkfEDM1k+3QydO1lqB5JJmcrs59BEveTqaJ3eeh/0I4OOab6OkTTZ0JNjJp1573oxO+fce/bfGud8xHY5gSN9huU7U6RsgvO7Dhmal/sDNl8XC8oU90hVDVXZdA7ewh4jjaoIv")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } diff --git a/integration/tracing_test.go b/integration/tracing_test.go index f2a1f29ad..c94f3c58b 100644 --- a/integration/tracing_test.go +++ b/integration/tracing_test.go @@ -1,20 +1,32 @@ package integration import ( + "encoding/json" + "io" "net/http" + "net/url" "os" + "strings" + "testing" "time" - "github.com/go-check/check" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/tidwall/gjson" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type TracingSuite struct { BaseSuite - whoamiIP string - whoamiPort int - tracerIP string + whoamiIP string + whoamiPort int + tempoIP string + otelCollectorIP string +} + +func TestTracingSuite(t *testing.T) { + suite.Run(t, new(TracingSuite)) } type TracingTemplate struct { @@ -22,304 +34,456 @@ type TracingTemplate struct { WhoamiPort int IP string TraceContextHeaderName string + IsHTTP bool } -func (s *TracingSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "tracing") - s.composeUp(c) +func (s *TracingSuite) SetupSuite() { + s.BaseSuite.SetupSuite() - s.whoamiIP = s.getComposeServiceIP(c, "whoami") + s.createComposeProject("tracing") + s.composeUp() + + s.whoamiIP = s.getComposeServiceIP("whoami") s.whoamiPort = 80 + + // Wait for whoami to turn ready. + err := try.GetRequest("http://"+s.whoamiIP+":80", 30*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + s.otelCollectorIP = s.getComposeServiceIP("otel-collector") + + // Wait for otel collector to turn ready. + err = try.GetRequest("http://"+s.otelCollectorIP+":13133/", 30*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) } -func (s *TracingSuite) startZipkin(c *check.C) { - s.composeUp(c, "zipkin") - s.tracerIP = s.getComposeServiceIP(c, "zipkin") - - // Wait for Zipkin to turn ready. - err := try.GetRequest("http://"+s.tracerIP+":9411/api/v2/services", 20*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) +func (s *TracingSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() } -func (s *TracingSuite) TestZipkinRateLimit(c *check.C) { - s.startZipkin(c) - // defer s.composeStop(c, "zipkin") +func (s *TracingSuite) SetupTest() { + s.composeUp("tempo") - file := s.adaptFile(c, "fixtures/tracing/simple-zipkin.toml", TracingTemplate{ + s.tempoIP = s.getComposeServiceIP("tempo") + + // Wait for tempo to turn ready. + err := try.GetRequest("http://"+s.tempoIP+":3200/ready", 30*time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) +} + +func (s *TracingSuite) TearDownTest() { + s.composeStop("tempo") +} + +func (s *TracingSuite) TestOpentelemetryBasic_HTTP() { + file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ WhoamiIP: s.whoamiIP, WhoamiPort: s.whoamiPort, - IP: s.tracerIP, + IP: s.otelCollectorIP, + IsHTTP: true, + }) + + s.traefikCmd(withConfigFile(file)) + + // wait for traefik + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/basic", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + contains := []map[string]string{ + { + "batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik", + + "batches.0.scopeSpans.0.spans.0.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_SERVER", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"entry_point\").value.stringValue": "web", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.path\").value.stringValue": "/basic", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + + "batches.0.scopeSpans.0.spans.1.name": "Router", + "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router0@file", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file", + + "batches.0.scopeSpans.0.spans.2.name": "Service", + "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file", + + "batches.0.scopeSpans.0.spans.3.name": "ReverseProxy", + "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_CLIENT", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.scheme\").value.stringValue": "http", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + }, + } + + s.checkTraceContent(contains) +} + +func (s *TracingSuite) TestOpentelemetryBasic_gRPC() { + file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ + WhoamiIP: s.whoamiIP, + WhoamiPort: s.whoamiPort, + IP: s.otelCollectorIP, + IsHTTP: false, }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/basic", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + contains := []map[string]string{ + { + "batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik", + + "batches.0.scopeSpans.0.spans.0.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_SERVER", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"entry_point\").value.stringValue": "web", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.path\").value.stringValue": "/basic", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + + "batches.0.scopeSpans.0.spans.1.name": "Router", + "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router0@file", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file", + + "batches.0.scopeSpans.0.spans.2.name": "Service", + "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file", + + "batches.0.scopeSpans.0.spans.3.name": "ReverseProxy", + "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_CLIENT", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.scheme\").value.stringValue": "http", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + }, + } + + s.checkTraceContent(contains) +} + +func (s *TracingSuite) TestOpentelemetryRateLimit() { + file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ + WhoamiIP: s.whoamiIP, + WhoamiPort: s.whoamiPort, + IP: s.otelCollectorIP, + }) + defer os.Remove(file) + + s.traefikCmd(withConfigFile(file)) + + // wait for traefik + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // sleep for 4 seconds to be certain the configured time period has elapsed // then test another request and verify a 200 status code time.Sleep(4 * time.Second) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) // continue requests at 3 second intervals to test the other rate limit time period time.Sleep(3 * time.Second) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) time.Sleep(3 * time.Second) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - time.Sleep(3 * time.Second) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - err = try.GetRequest("http://"+s.tracerIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service1/router1@file", "ratelimit-1@file")) - c.Assert(err, checker.IsNil) + contains := []map[string]string{ + { + "batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik", + + "batches.0.scopeSpans.0.spans.0.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_SERVER", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"entry_point\").value.stringValue": "web", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + + "batches.0.scopeSpans.0.spans.1.name": "Router", + "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file", + + "batches.0.scopeSpans.0.spans.2.name": "Retry", + "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file", + + "batches.0.scopeSpans.0.spans.3.name": "RateLimiter", + "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file", + + "batches.0.scopeSpans.0.spans.4.name": "Service", + "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file", + + "batches.0.scopeSpans.0.spans.5.name": "ReverseProxy", + "batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_CLIENT", + "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"url.scheme\").value.stringValue": "http", + "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + }, + { + "batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik", + + "batches.0.scopeSpans.0.spans.0.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_SERVER", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"entry_point\").value.stringValue": "web", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "429", + + "batches.0.scopeSpans.0.spans.1.name": "Router", + "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file", + + "batches.0.scopeSpans.0.spans.2.name": "Retry", + "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file", + + "batches.0.scopeSpans.0.spans.3.name": "RateLimiter", + "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file", + + "batches.0.scopeSpans.0.spans.4.name": "Retry", + "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file", + "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.resend_count\").value.intValue": "1", + + "batches.0.scopeSpans.0.spans.5.name": "RateLimiter", + "batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file", + + "batches.0.scopeSpans.0.spans.6.name": "Retry", + "batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file", + "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.resend_count\").value.intValue": "2", + + "batches.0.scopeSpans.0.spans.7.name": "RateLimiter", + "batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.7.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file", + }, + } + + s.checkTraceContent(contains) } -func (s *TracingSuite) TestZipkinRetry(c *check.C) { - s.startZipkin(c) - defer s.composeStop(c, "zipkin") - - file := s.adaptFile(c, "fixtures/tracing/simple-zipkin.toml", TracingTemplate{ +func (s *TracingSuite) TestOpentelemetryRetry() { + file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ WhoamiIP: s.whoamiIP, WhoamiPort: 81, - IP: s.tracerIP, + IP: s.otelCollectorIP, }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/retry", 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - err = try.GetRequest("http://"+s.tracerIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("forward service2/router2@file", "retry@file")) - c.Assert(err, checker.IsNil) + contains := []map[string]string{ + { + "batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik", + + "batches.0.scopeSpans.0.spans.0.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.path\").value.stringValue": "/retry", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "502", + "batches.0.scopeSpans.0.spans.0.status.code": "STATUS_CODE_ERROR", + + "batches.0.scopeSpans.0.spans.1.name": "Router", + "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service2@file", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router2@file", + + "batches.0.scopeSpans.0.spans.2.name": "Retry", + "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file", + + "batches.0.scopeSpans.0.spans.3.name": "Service", + "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.service.name\").value.stringValue": "service2@file", + + "batches.0.scopeSpans.0.spans.4.name": "ReverseProxy", + "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_CLIENT", + "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"url.scheme\").value.stringValue": "http", + "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.response.status_code\").value.intValue": "502", + "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + + "batches.0.scopeSpans.0.spans.5.name": "Retry", + "batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file", + "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.resend_count\").value.intValue": "1", + + "batches.0.scopeSpans.0.spans.6.name": "Service", + "batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.service.name\").value.stringValue": "service2@file", + + "batches.0.scopeSpans.0.spans.7.name": "ReverseProxy", + "batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_CLIENT", + "batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.scheme\").value.stringValue": "http", + "batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.response.status_code\").value.intValue": "502", + "batches.0.scopeSpans.0.spans.7.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + + "batches.0.scopeSpans.0.spans.8.name": "Retry", + "batches.0.scopeSpans.0.spans.8.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.8.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file", + "batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.resend_count\").value.intValue": "2", + + "batches.0.scopeSpans.0.spans.9.name": "Service", + "batches.0.scopeSpans.0.spans.9.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.9.attributes.#(key=\"traefik.service.name\").value.stringValue": "service2@file", + + "batches.0.scopeSpans.0.spans.10.name": "ReverseProxy", + "batches.0.scopeSpans.0.spans.10.kind": "SPAN_KIND_CLIENT", + "batches.0.scopeSpans.0.spans.10.attributes.#(key=\"url.scheme\").value.stringValue": "http", + "batches.0.scopeSpans.0.spans.10.attributes.#(key=\"http.response.status_code\").value.intValue": "502", + "batches.0.scopeSpans.0.spans.10.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + }, + } + + s.checkTraceContent(contains) } -func (s *TracingSuite) TestZipkinAuth(c *check.C) { - s.startZipkin(c) - defer s.composeStop(c, "zipkin") - - file := s.adaptFile(c, "fixtures/tracing/simple-zipkin.toml", TracingTemplate{ +func (s *TracingSuite) TestOpentelemetryAuth() { + file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ WhoamiIP: s.whoamiIP, WhoamiPort: s.whoamiPort, - IP: s.tracerIP, + IP: s.otelCollectorIP, }) defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) - err = try.GetRequest("http://"+s.tracerIP+":9411/api/v2/spans?serviceName=tracing", 20*time.Second, try.BodyContains("entrypoint web", "basic-auth@file")) - c.Assert(err, checker.IsNil) + contains := []map[string]string{ + { + "batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik", + + "batches.0.scopeSpans.0.spans.0.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.path\").value.stringValue": "/auth", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "401", + + "batches.0.scopeSpans.0.spans.1.name": "Router", + "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file", + + "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file", + + "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file", + }, + } + + s.checkTraceContent(contains) } -func (s *TracingSuite) startJaeger(c *check.C) { - s.composeUp(c, "jaeger", "whoami") - s.tracerIP = s.getComposeServiceIP(c, "jaeger") +func (s *TracingSuite) checkTraceContent(expectedJSON []map[string]string) { + s.T().Helper() - // Wait for Jaeger to turn ready. - err := try.GetRequest("http://"+s.tracerIP+":16686/api/services", 20*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/search") + require.NoError(s.T(), err) + + req := &http.Request{ + Method: http.MethodGet, + URL: baseURL, + } + // Wait for traces to be available. + time.Sleep(10 * time.Second) + resp, err := try.Response(req, 5*time.Second) + require.NoError(s.T(), err) + + out := &TraceResponse{} + content, err := io.ReadAll(resp.Body) + require.NoError(s.T(), err) + err = json.Unmarshal(content, &out) + require.NoError(s.T(), err) + + s.NotEmptyf(len(out.Traces), "expected at least one trace") + + var contents []string + for _, t := range out.Traces { + baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/traces/" + t.TraceID) + require.NoError(s.T(), err) + + req := &http.Request{ + Method: http.MethodGet, + URL: baseURL, + } + + resp, err := try.Response(req, 5*time.Second) + require.NoError(s.T(), err) + + content, err := io.ReadAll(resp.Body) + require.NoError(s.T(), err) + + contents = append(contents, string(content)) + } + + for _, expected := range expectedJSON { + containsAll(expected, contents) + } } -func (s *TracingSuite) TestJaegerRateLimit(c *check.C) { - s.startJaeger(c) - defer s.composeStop(c, "jaeger") +func containsAll(expectedJSON map[string]string, contents []string) { + for k, v := range expectedJSON { + found := false + for _, content := range contents { + if gjson.Get(content, k).String() == v { + found = true + break + } + } - file := s.adaptFile(c, "fixtures/tracing/simple-jaeger.toml", TracingTemplate{ - WhoamiIP: s.whoamiIP, - WhoamiPort: s.whoamiPort, - IP: s.tracerIP, - TraceContextHeaderName: "uber-trace-id", - }) - defer os.Remove(file) - - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) - err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) - err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests)) - c.Assert(err, checker.IsNil) - - // sleep for 4 seconds to be certain the configured time period has elapsed - // then test another request and verify a 200 status code - time.Sleep(4 * time.Second) - err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) - - // continue requests at 3 second intervals to test the other rate limit time period - time.Sleep(3 * time.Second) - err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) - - time.Sleep(3 * time.Second) - err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://"+s.tracerIP+":16686/api/traces?service=tracing", 20*time.Second, try.BodyContains("forward service1/router1@file", "ratelimit-1@file")) - c.Assert(err, checker.IsNil) + if !found { + log.Info().Msgf("[" + strings.Join(contents, ",") + "]") + log.Error().Msgf("missing element: \nKey: %q\nValue: %q ", k, v) + } + } } -func (s *TracingSuite) TestJaegerRetry(c *check.C) { - s.startJaeger(c) - defer s.composeStop(c, "jaeger") - - file := s.adaptFile(c, "fixtures/tracing/simple-jaeger.toml", TracingTemplate{ - WhoamiIP: s.whoamiIP, - WhoamiPort: 81, - IP: s.tracerIP, - TraceContextHeaderName: "uber-trace-id", - }) - defer os.Remove(file) - - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/retry", 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://"+s.tracerIP+":16686/api/traces?service=tracing", 20*time.Second, try.BodyContains("forward service2/router2@file", "retry@file")) - c.Assert(err, checker.IsNil) +// TraceResponse contains a list of traces. +type TraceResponse struct { + Traces []Trace `json:"traces"` } -func (s *TracingSuite) TestJaegerAuth(c *check.C) { - s.startJaeger(c) - defer s.composeStop(c, "jaeger") - - file := s.adaptFile(c, "fixtures/tracing/simple-jaeger.toml", TracingTemplate{ - WhoamiIP: s.whoamiIP, - WhoamiPort: s.whoamiPort, - IP: s.tracerIP, - TraceContextHeaderName: "uber-trace-id", - }) - defer os.Remove(file) - - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://"+s.tracerIP+":16686/api/traces?service=tracing", 20*time.Second, try.BodyContains("EntryPoint web", "basic-auth@file")) - c.Assert(err, checker.IsNil) -} - -func (s *TracingSuite) TestJaegerCustomHeader(c *check.C) { - s.startJaeger(c) - defer s.composeStop(c, "jaeger") - - file := s.adaptFile(c, "fixtures/tracing/simple-jaeger.toml", TracingTemplate{ - WhoamiIP: s.whoamiIP, - WhoamiPort: s.whoamiPort, - IP: s.tracerIP, - TraceContextHeaderName: "powpow", - }) - defer os.Remove(file) - - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://"+s.tracerIP+":16686/api/traces?service=tracing", 20*time.Second, try.BodyContains("EntryPoint web", "basic-auth@file")) - c.Assert(err, checker.IsNil) -} - -func (s *TracingSuite) TestJaegerAuthCollector(c *check.C) { - s.startJaeger(c) - defer s.composeStop(c, "jaeger") - - file := s.adaptFile(c, "fixtures/tracing/simple-jaeger-collector.toml", TracingTemplate{ - WhoamiIP: s.whoamiIP, - WhoamiPort: s.whoamiPort, - IP: s.tracerIP, - }) - defer os.Remove(file) - - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://"+s.tracerIP+":16686/api/traces?service=tracing", 20*time.Second, try.BodyContains("EntryPoint web", "basic-auth@file")) - c.Assert(err, checker.IsNil) +// Trace represents a simplified grafana tempo trace. +type Trace struct { + TraceID string `json:"traceID"` } diff --git a/integration/udp_test.go b/integration/udp_test.go index 3493af17e..30753a7f5 100644 --- a/integration/udp_test.go +++ b/integration/udp_test.go @@ -3,20 +3,32 @@ package integration import ( "net" "net/http" - "os" "strings" + "testing" "time" - "github.com/go-check/check" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" ) type UDPSuite struct{ BaseSuite } -func (s *UDPSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "udp") - s.composeUp(c) +func TestUDPSuite(t *testing.T) { + suite.Run(t, new(UDPSuite)) +} + +func (s *UDPSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("udp") + s.composeUp() +} + +func (s *UDPSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() } func guessWhoUDP(addr string) (string, error) { @@ -46,39 +58,33 @@ func guessWhoUDP(addr string) (string, error) { return string(out[:n]), nil } -func (s *UDPSuite) TestWRR(c *check.C) { - file := s.adaptFile(c, "fixtures/udp/wrr.toml", struct { +func (s *UDPSuite) TestWRR() { + file := s.adaptFile("fixtures/udp/wrr.toml", struct { WhoamiAIP string WhoamiBIP string WhoamiCIP string WhoamiDIP string }{ - WhoamiAIP: s.getComposeServiceIP(c, "whoami-a"), - WhoamiBIP: s.getComposeServiceIP(c, "whoami-b"), - WhoamiCIP: s.getComposeServiceIP(c, "whoami-c"), - WhoamiDIP: s.getComposeServiceIP(c, "whoami-d"), + WhoamiAIP: s.getComposeServiceIP("whoami-a"), + WhoamiBIP: s.getComposeServiceIP("whoami-b"), + WhoamiCIP: s.getComposeServiceIP("whoami-c"), + WhoamiDIP: s.getComposeServiceIP("whoami-d"), }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) + s.traefikCmd(withConfigFile(file)) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) - - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("whoami-a")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("whoami-a")) + require.NoError(s.T(), err) err = try.GetRequest("http://127.0.0.1:8093/who", 5*time.Second, try.StatusCodeIs(http.StatusOK)) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) stop := make(chan struct{}) go func() { call := map[string]int{} for i := 0; i < 8; i++ { out, err := guessWhoUDP("127.0.0.1:8093") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) switch { case strings.Contains(out, "whoami-a"): call["whoami-a"]++ @@ -90,13 +96,13 @@ func (s *UDPSuite) TestWRR(c *check.C) { call["unknown"]++ } } - c.Assert(call, checker.DeepEquals, map[string]int{"whoami-a": 3, "whoami-b": 2, "whoami-c": 3}) + assert.EqualValues(s.T(), call, map[string]int{"whoami-a": 3, "whoami-b": 2, "whoami-c": 3}) close(stop) }() select { case <-stop: case <-time.Tick(5 * time.Second): - c.Error("Timeout") + log.Info().Msg("Timeout") } } diff --git a/integration/websocket_test.go b/integration/websocket_test.go index c343c1b78..508bb9573 100644 --- a/integration/websocket_test.go +++ b/integration/websocket_test.go @@ -8,19 +8,25 @@ import ( "net/http" "net/http/httptest" "os" + "testing" "time" - "github.com/go-check/check" gorillawebsocket "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" - checker "github.com/vdemeester/shakers" "golang.org/x/net/websocket" ) // WebsocketSuite tests suite. type WebsocketSuite struct{ BaseSuite } -func (s *WebsocketSuite) TestBase(c *check.C) { +func TestWebsocketSuite(t *testing.T) { + suite.Run(t, new(WebsocketSuite)) +} + +func (s *WebsocketSuite) TestBase() { upgrader := gorillawebsocket.Upgrader{} // use default options srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -41,36 +47,30 @@ func (s *WebsocketSuite) TestBase(c *check.C) { } })) - file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile("fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, msg, err := conn.ReadMessage() - c.Assert(err, checker.IsNil) - c.Assert(string(msg), checker.Equals, "OK") + require.NoError(s.T(), err) + assert.Equal(s.T(), "OK", string(msg)) } -func (s *WebsocketSuite) TestWrongOrigin(c *check.C) { +func (s *WebsocketSuite) TestWrongOrigin() { upgrader := gorillawebsocket.Upgrader{} // use default options srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -91,35 +91,28 @@ func (s *WebsocketSuite) TestWrongOrigin(c *check.C) { } })) - file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile("fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:800") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, err = websocket.NewClient(config, conn) - c.Assert(err, checker.NotNil) - c.Assert(err, checker.ErrorMatches, "bad status") + assert.ErrorContains(s.T(), err, "bad status") } -func (s *WebsocketSuite) TestOrigin(c *check.C) { +func (s *WebsocketSuite) TestOrigin() { // use default options upgrader := gorillawebsocket.Upgrader{} @@ -141,44 +134,38 @@ func (s *WebsocketSuite) TestOrigin(c *check.C) { } })) - file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile("fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) client, err := websocket.NewClient(config, conn) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) n, err := client.Write([]byte("OK")) - c.Assert(err, checker.IsNil) - c.Assert(n, checker.Equals, 2) + require.NoError(s.T(), err) + assert.Equal(s.T(), 2, n) msg := make([]byte, 2) n, err = client.Read(msg) - c.Assert(err, checker.IsNil) - c.Assert(n, checker.Equals, 2) - c.Assert(string(msg), checker.Equals, "OK") + require.NoError(s.T(), err) + assert.Equal(s.T(), 2, n) + assert.Equal(s.T(), "OK", string(msg)) } -func (s *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) { +func (s *WebsocketSuite) TestWrongOriginIgnoredByServer() { upgrader := gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }} @@ -201,44 +188,38 @@ func (s *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) { } })) - file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile("fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:80") - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) client, err := websocket.NewClient(config, conn) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) n, err := client.Write([]byte("OK")) - c.Assert(err, checker.IsNil) - c.Assert(n, checker.Equals, 2) + require.NoError(s.T(), err) + assert.Equal(s.T(), 2, n) msg := make([]byte, 2) n, err = client.Read(msg) - c.Assert(err, checker.IsNil) - c.Assert(n, checker.Equals, 2) - c.Assert(string(msg), checker.Equals, "OK") + require.NoError(s.T(), err) + assert.Equal(s.T(), 2, n) + assert.Equal(s.T(), "OK", string(msg)) } -func (s *WebsocketSuite) TestSSLTermination(c *check.C) { +func (s *WebsocketSuite) TestSSLTermination() { upgrader := gorillawebsocket.Upgrader{} // use default options srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -258,44 +239,38 @@ func (s *WebsocketSuite) TestSSLTermination(c *check.C) { } } })) - file := s.adaptFile(c, "fixtures/websocket/config_https.toml", struct { + file := s.adaptFile("fixtures/websocket/config_https.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) // Add client self-signed cert roots := x509.NewCertPool() certContent, err := os.ReadFile("./resources/tls/local.cert") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) roots.AppendCertsFromPEM(certContent) gorillawebsocket.DefaultDialer.TLSClientConfig = &tls.Config{ RootCAs: roots, } conn, _, err := gorillawebsocket.DefaultDialer.Dial("wss://127.0.0.1:8000/ws", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, msg, err := conn.ReadMessage() - c.Assert(err, checker.IsNil) - c.Assert(string(msg), checker.Equals, "OK") + require.NoError(s.T(), err) + assert.Equal(s.T(), "OK", string(msg)) } -func (s *WebsocketSuite) TestBasicAuth(c *check.C) { +func (s *WebsocketSuite) TestBasicAuth() { upgrader := gorillawebsocket.Upgrader{} // use default options srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -306,8 +281,8 @@ func (s *WebsocketSuite) TestBasicAuth(c *check.C) { defer conn.Close() user, password, _ := r.BasicAuth() - c.Assert(user, check.Equals, "traefiker") - c.Assert(password, check.Equals, "secret") + assert.Equal(s.T(), "traefiker", user) + assert.Equal(s.T(), "secret", password) for { mt, message, err := conn.ReadMessage() @@ -320,78 +295,66 @@ func (s *WebsocketSuite) TestBasicAuth(c *check.C) { } } })) - file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile("fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000") auth := "traefiker:secret" config.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) - c.Assert(err, check.IsNil) + assert.NoError(s.T(), err) conn, err := net.DialTimeout("tcp", "127.0.0.1:8000", time.Second) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) client, err := websocket.NewClient(config, conn) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) n, err := client.Write([]byte("OK")) - c.Assert(err, checker.IsNil) - c.Assert(n, checker.Equals, 2) + require.NoError(s.T(), err) + assert.Equal(s.T(), 2, n) msg := make([]byte, 2) n, err = client.Read(msg) - c.Assert(err, checker.IsNil) - c.Assert(n, checker.Equals, 2) - c.Assert(string(msg), checker.Equals, "OK") + require.NoError(s.T(), err) + assert.Equal(s.T(), 2, n) + assert.Equal(s.T(), "OK", string(msg)) } -func (s *WebsocketSuite) TestSpecificResponseFromBackend(c *check.C) { +func (s *WebsocketSuite) TestSpecificResponseFromBackend() { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) - file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile("fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) _, resp, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil) - c.Assert(err, checker.NotNil) - c.Assert(resp.StatusCode, check.Equals, http.StatusUnauthorized) + assert.Error(s.T(), err) + assert.Equal(s.T(), http.StatusUnauthorized, resp.StatusCode) } -func (s *WebsocketSuite) TestURLWithURLEncodedChar(c *check.C) { +func (s *WebsocketSuite) TestURLWithURLEncodedChar() { upgrader := gorillawebsocket.Upgrader{} // use default options srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Assert(r.URL.EscapedPath(), check.Equals, "/ws/http%3A%2F%2Ftest") + assert.Equal(s.T(), "/ws/http%3A%2F%2Ftest", r.URL.EscapedPath()) conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return @@ -409,36 +372,30 @@ func (s *WebsocketSuite) TestURLWithURLEncodedChar(c *check.C) { } })) - file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile("fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws/http%3A%2F%2Ftest", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, msg, err := conn.ReadMessage() - c.Assert(err, checker.IsNil) - c.Assert(string(msg), checker.Equals, "OK") + require.NoError(s.T(), err) + assert.Equal(s.T(), "OK", string(msg)) } -func (s *WebsocketSuite) TestSSLhttp2(c *check.C) { +func (s *WebsocketSuite) TestSSLhttp2() { upgrader := gorillawebsocket.Upgrader{} // use default options ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -463,48 +420,42 @@ func (s *WebsocketSuite) TestSSLhttp2(c *check.C) { ts.TLS.NextProtos = append(ts.TLS.NextProtos, `h2`, `http/1.1`) ts.StartTLS() - file := s.adaptFile(c, "fixtures/websocket/config_https.toml", struct { + file := s.adaptFile("fixtures/websocket/config_https.toml", struct { WebsocketServer string }{ WebsocketServer: ts.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG", "--accesslog") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG", "--accesslog") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) // Add client self-signed cert roots := x509.NewCertPool() certContent, err := os.ReadFile("./resources/tls/local.cert") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) roots.AppendCertsFromPEM(certContent) gorillawebsocket.DefaultDialer.TLSClientConfig = &tls.Config{ RootCAs: roots, } conn, _, err := gorillawebsocket.DefaultDialer.Dial("wss://127.0.0.1:8000/echo", nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, msg, err := conn.ReadMessage() - c.Assert(err, checker.IsNil) - c.Assert(string(msg), checker.Equals, "OK") + require.NoError(s.T(), err) + assert.Equal(s.T(), "OK", string(msg)) } -func (s *WebsocketSuite) TestHeaderAreForwarded(c *check.C) { +func (s *WebsocketSuite) TestHeaderAreForwarded() { upgrader := gorillawebsocket.Upgrader{} // use default options srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Assert(r.Header.Get("X-Token"), check.Equals, "my-token") + assert.Equal(s.T(), "my-token", r.Header.Get("X-Token")) c, err := upgrader.Upgrade(w, r, nil) if err != nil { return @@ -522,33 +473,27 @@ func (s *WebsocketSuite) TestHeaderAreForwarded(c *check.C) { } })) - file := s.adaptFile(c, "fixtures/websocket/config.toml", struct { + file := s.adaptFile("fixtures/websocket/config.toml", struct { WebsocketServer string }{ WebsocketServer: srv.URL, }) - defer os.Remove(file) - cmd, display := s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") - defer display(c) - - err := cmd.Start() - c.Assert(err, check.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG") // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) - c.Assert(err, checker.IsNil) + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) + require.NoError(s.T(), err) headers := http.Header{} headers.Add("X-Token", "my-token") conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", headers) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) err = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) _, msg, err := conn.ReadMessage() - c.Assert(err, checker.IsNil) - c.Assert(string(msg), checker.Equals, "OK") + require.NoError(s.T(), err) + assert.Equal(s.T(), "OK", string(msg)) } diff --git a/integration/zk_test.go b/integration/zk_test.go index 1e5820cc5..f971ca79c 100644 --- a/integration/zk_test.go +++ b/integration/zk_test.go @@ -8,16 +8,18 @@ import ( "net/http" "os" "path/filepath" + "testing" "time" - "github.com/go-check/check" "github.com/kvtools/valkeyrie" "github.com/kvtools/valkeyrie/store" "github.com/kvtools/zookeeper" "github.com/pmezard/go-difflib/difflib" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/api" - checker "github.com/vdemeester/shakers" ) // Zk test suites. @@ -27,11 +29,17 @@ type ZookeeperSuite struct { zookeeperAddr string } -func (s *ZookeeperSuite) setupStore(c *check.C) { - s.createComposeProject(c, "zookeeper") - s.composeUp(c) +func TestZookeeperSuite(t *testing.T) { + suite.Run(t, new(ZookeeperSuite)) +} - s.zookeeperAddr = net.JoinHostPort(s.getComposeServiceIP(c, "zookeeper"), "2181") +func (s *ZookeeperSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + s.createComposeProject("zookeeper") + s.composeUp() + + s.zookeeperAddr = net.JoinHostPort(s.getComposeServiceIP("zookeeper"), "2181") var err error s.kvClient, err = valkeyrie.NewStore( @@ -42,20 +50,19 @@ func (s *ZookeeperSuite) setupStore(c *check.C) { ConnectionTimeout: 10 * time.Second, }, ) - if err != nil { - c.Fatal("Cannot create store zookeeper") - } + require.NoError(s.T(), err, "Cannot create store zookeeper") // wait for zk err = try.Do(60*time.Second, try.KVExists(s.kvClient, "test")) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } -func (s *ZookeeperSuite) TestSimpleConfiguration(c *check.C) { - s.setupStore(c) +func (s *ZookeeperSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() +} - file := s.adaptFile(c, "fixtures/zookeeper/simple.toml", struct{ ZkAddress string }{s.zookeeperAddr}) - defer os.Remove(file) +func (s *ZookeeperSuite) TestSimpleConfiguration() { + file := s.adaptFile("fixtures/zookeeper/simple.toml", struct{ ZkAddress string }{s.zookeeperAddr}) data := map[string]string{ "traefik/http/routers/Router0/entryPoints/0": "web", @@ -104,39 +111,35 @@ func (s *ZookeeperSuite) TestSimpleConfiguration(c *check.C) { for k, v := range data { err := s.kvClient.Put(context.Background(), k, []byte(v), nil) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer s.killCmd(cmd) + s.traefikCmd(withConfigFile(file)) // wait for traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.BodyContains(`"striper@zookeeper":`, `"compressor@zookeeper":`, `"srvcA@zookeeper":`, `"srvcB@zookeeper":`), ) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) resp, err := http.Get("http://127.0.0.1:8080/api/rawdata") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) var obtained api.RunTimeRepresentation err = json.NewDecoder(resp.Body).Decode(&obtained) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) got, err := json.MarshalIndent(obtained, "", " ") - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) expectedJSON := filepath.FromSlash("testdata/rawdata-zk.json") if *updateExpected { err = os.WriteFile(expectedJSON, got, 0o666) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) } expected, err := os.ReadFile(expectedJSON) - c.Assert(err, checker.IsNil) + require.NoError(s.T(), err) if !bytes.Equal(expected, got) { diff := difflib.UnifiedDiff{ @@ -148,7 +151,7 @@ func (s *ZookeeperSuite) TestSimpleConfiguration(c *check.C) { } text, err := difflib.GetUnifiedDiffString(diff) - c.Assert(err, checker.IsNil) - c.Error(text) + require.NoError(s.T(), err) + log.Info().Msg(text) } } diff --git a/internal/gendoc.go b/internal/gendoc.go index d4fad3247..93c03366e 100644 --- a/internal/gendoc.go +++ b/internal/gendoc.go @@ -1,10 +1,13 @@ package main import ( + "bytes" "fmt" "io" "os" "path" + "path/filepath" + "reflect" "sort" "strconv" "strings" @@ -16,9 +19,65 @@ import ( "github.com/traefik/paerser/generator" "github.com/traefik/paerser/parser" "github.com/traefik/traefik/v3/cmd" + "github.com/traefik/traefik/v3/pkg/collector/hydratation" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/config/static" + "gopkg.in/yaml.v3" ) +var commentGenerated = `## CODE GENERATED AUTOMATICALLY +## THIS FILE MUST NOT BE EDITED BY HAND +` + func main() { + logger := log.With().Logger() + + dynConf := &dynamic.Configuration{} + + err := hydratation.Hydrate(dynConf) + if err != nil { + logger.Fatal().Err(err).Send() + } + + dynConf.HTTP.Models = map[string]*dynamic.Model{} + clean(dynConf.HTTP.Middlewares) + clean(dynConf.TCP.Middlewares) + clean(dynConf.HTTP.Services) + clean(dynConf.TCP.Services) + clean(dynConf.UDP.Services) + + err = tomlWrite("./docs/content/reference/dynamic-configuration/file.toml", dynConf) + if err != nil { + logger.Fatal().Err(err).Send() + } + err = yamlWrite("./docs/content/reference/dynamic-configuration/file.yaml", dynConf) + if err != nil { + logger.Fatal().Err(err).Send() + } + + err = labelsWrite("./docs/content/reference/dynamic-configuration", dynConf) + if err != nil { + logger.Fatal().Err(err).Send() + } + + staticConf := &static.Configuration{} + + err = hydratation.Hydrate(staticConf) + if err != nil { + logger.Fatal().Err(err).Send() + } + + delete(staticConf.EntryPoints, "EntryPoint1") + + err = tomlWrite("./docs/content/reference/static-configuration/file.toml", staticConf) + if err != nil { + logger.Fatal().Err(err).Send() + } + err = yamlWrite("./docs/content/reference/static-configuration/file.yaml", staticConf) + if err != nil { + logger.Fatal().Err(err).Send() + } + genStaticConfDoc("./docs/content/reference/static-configuration/env-ref.md", "", func(i interface{}) ([]parser.Flat, error) { return env.Encode(env.DefaultNamePrefix, i) }) @@ -26,6 +85,144 @@ func main() { genKVDynConfDoc("./docs/content/reference/dynamic-configuration/kv-ref.md") } +func labelsWrite(outputDir string, element *dynamic.Configuration) error { + cleanServers(element) + + etnOpts := parser.EncoderToNodeOpts{OmitEmpty: true, TagName: parser.TagLabel, AllowSliceAsStruct: true} + node, err := parser.EncodeToNode(element, parser.DefaultRootName, etnOpts) + if err != nil { + return err + } + + metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: true} + err = parser.AddMetadata(element, node, metaOpts) + if err != nil { + return err + } + + labels := make(map[string]string) + encodeNode(labels, node.Name, node) + + var keys []string + for k := range labels { + keys = append(keys, k) + } + + sort.Strings(keys) + + dockerLabels, err := os.Create(filepath.Join(outputDir, "docker-labels.yml")) + if err != nil { + return err + } + defer dockerLabels.Close() + + // Write the comment at the beginning of the file + if _, err := dockerLabels.WriteString(commentGenerated); err != nil { + return err + } + + for _, k := range keys { + v := labels[k] + if v != "" { + if v == "42000000000" { + v = "42s" + } + fmt.Fprintln(dockerLabels, `- "`+strings.ToLower(k)+`=`+v+`"`) + } + } + + return nil +} + +func cleanServers(element *dynamic.Configuration) { + for _, svc := range element.HTTP.Services { + if svc.LoadBalancer != nil { + server := svc.LoadBalancer.Servers[0] + svc.LoadBalancer.Servers = nil + svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, server) + } + } + + for _, svc := range element.TCP.Services { + if svc.LoadBalancer != nil { + server := svc.LoadBalancer.Servers[0] + svc.LoadBalancer.Servers = nil + svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, server) + } + } + + for _, svc := range element.UDP.Services { + if svc.LoadBalancer != nil { + server := svc.LoadBalancer.Servers[0] + svc.LoadBalancer.Servers = nil + svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, server) + } + } +} + +func yamlWrite(outputFile string, element any) error { + file, err := os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666) + if err != nil { + return err + } + defer file.Close() + + // Write the comment at the beginning of the file + if _, err := file.WriteString(commentGenerated); err != nil { + return err + } + + buf := new(bytes.Buffer) + encoder := yaml.NewEncoder(buf) + encoder.SetIndent(2) + err = encoder.Encode(element) + if err != nil { + return err + } + + _, err = file.Write(buf.Bytes()) + return err +} + +func tomlWrite(outputFile string, element any) error { + file, err := os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666) + if err != nil { + return err + } + defer file.Close() + + // Write the comment at the beginning of the file + if _, err := file.WriteString(commentGenerated); err != nil { + return err + } + + return toml.NewEncoder(file).Encode(element) +} + +func clean(element any) { + valSvcs := reflect.ValueOf(element) + + key := valSvcs.MapKeys()[0] + valueSvcRoot := valSvcs.MapIndex(key).Elem() + + var svcFieldNames []string + for i := 0; i < valueSvcRoot.NumField(); i++ { + svcFieldNames = append(svcFieldNames, valueSvcRoot.Type().Field(i).Name) + } + + sort.Strings(svcFieldNames) + + for i, fieldName := range svcFieldNames { + v := reflect.New(valueSvcRoot.Type()) + v.Elem().FieldByName(fieldName).Set(valueSvcRoot.FieldByName(fieldName)) + + valSvcs.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s%.2d", valueSvcRoot.Type().Name(), i+1)), v) + } + + valSvcs.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s0", valueSvcRoot.Type().Name())), reflect.Value{}) + valSvcs.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s1", valueSvcRoot.Type().Name())), reflect.Value{}) +} + func genStaticConfDoc(outputFile, prefix string, encodeFn func(interface{}) ([]parser.Flat, error)) { logger := log.With().Str("file", outputFile).Logger() @@ -117,6 +314,7 @@ func genKVDynConfDoc(outputFile string) { } store := storeWriter{data: map[string]string{}} + c := client{store: store} err = c.load("traefik", conf) if err != nil { @@ -130,6 +328,12 @@ func genKVDynConfDoc(outputFile string) { sort.Strings(keys) + _, _ = fmt.Fprintf(file, ` +`) + for _, k := range keys { _, _ = fmt.Fprintf(file, "| `%s` | `%s` |\n", k, store.data[k]) } diff --git a/internal/parser.go b/internal/parser.go new file mode 100644 index 000000000..3fa85617e --- /dev/null +++ b/internal/parser.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "reflect" + "strings" + + "github.com/traefik/paerser/parser" +) + +func encodeNode(labels map[string]string, root string, node *parser.Node) { + for _, child := range node.Children { + if child.Disabled { + continue + } + + var sep string + if child.Name[0] != '[' { + sep = "." + } + + childName := root + sep + child.Name + + if child.RawValue != nil { + encodeRawValue(labels, childName, child.RawValue) + continue + } + + if strings.Contains(child.Tag.Get(parser.TagLabel), parser.TagLabelAllowEmpty) { + labels[childName] = "true" + } + + if len(child.Children) > 0 { + encodeNode(labels, childName, child) + } else if len(child.Name) > 0 { + labels[childName] = child.Value + } + } +} + +func encodeRawValue(labels map[string]string, root string, rawValue interface{}) { + if rawValue == nil { + return + } + + tValue := reflect.TypeOf(rawValue) + + if tValue.Kind() == reflect.Map && tValue.Elem().Kind() == reflect.Interface { + r := reflect.ValueOf(rawValue). + Convert(reflect.TypeOf((map[string]interface{})(nil))). + Interface().(map[string]interface{}) + + for k, v := range r { + switch tv := v.(type) { + case string: + labels[root+"."+k] = tv + case []interface{}: + for i, e := range tv { + encodeRawValue(labels, fmt.Sprintf("%s.%s[%d]", root, k, i), e) + } + default: + encodeRawValue(labels, root+"."+k, v) + } + } + } +} diff --git a/internal/release/release.go b/internal/release/release.go new file mode 100644 index 000000000..d27eed270 --- /dev/null +++ b/internal/release/release.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "log" + "os" + "path" + "strings" + "text/template" +) + +func main() { + if len(os.Args) < 2 { + log.Fatal("GOOS should be provided as a CLI argument") + } + + goos := strings.TrimSpace(os.Args[1]) + if goos == "" { + log.Fatal("GOOS should be provided as a CLI argument") + } + + tmpl := template.Must( + template.New(".goreleaser.yml.tmpl"). + Delims("[[", "]]"). + ParseFiles("./.goreleaser.yml.tmpl"), + ) + + outputPath := path.Join(os.TempDir(), fmt.Sprintf(".goreleaser_%s.yml", goos)) + + output, err := os.Create(outputPath) + if err != nil { + log.Fatal(err) + } + + err = tmpl.Execute(output, map[string]string{"GOOS": goos}) + if err != nil { + log.Fatal(err) + } + + fmt.Print(outputPath) +} diff --git a/pkg/api/handler_entrypoint_test.go b/pkg/api/handler_entrypoint_test.go index f02821607..7d2f358e4 100644 --- a/pkg/api/handler_entrypoint_test.go +++ b/pkg/api/handler_entrypoint_test.go @@ -212,7 +212,7 @@ func TestHandler_EntryPoints(t *testing.T) { return } - assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") + assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) contents, err := io.ReadAll(resp.Body) require.NoError(t, err) diff --git a/pkg/api/handler_http_test.go b/pkg/api/handler_http_test.go index 0a9cedc5c..f9eac0705 100644 --- a/pkg/api/handler_http_test.go +++ b/pkg/api/handler_http_test.go @@ -951,7 +951,7 @@ func TestHandler_HTTP(t *testing.T) { return } - assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") + assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) contents, err := io.ReadAll(resp.Body) require.NoError(t, err) diff --git a/pkg/api/handler_overview_test.go b/pkg/api/handler_overview_test.go index 1f9ab4697..3e8743a81 100644 --- a/pkg/api/handler_overview_test.go +++ b/pkg/api/handler_overview_test.go @@ -18,7 +18,6 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress" "github.com/traefik/traefik/v3/pkg/provider/rest" - "github.com/traefik/traefik/v3/pkg/tracing/jaeger" "github.com/traefik/traefik/v3/pkg/types" ) @@ -259,9 +258,7 @@ func TestHandler_Overview(t *testing.T) { Metrics: &types.Metrics{ Prometheus: &types.Prometheus{}, }, - Tracing: &static.Tracing{ - Jaeger: &jaeger.Config{}, - }, + Tracing: &static.Tracing{}, }, confDyn: runtime.Configuration{}, expected: expected{ @@ -288,7 +285,7 @@ func TestHandler_Overview(t *testing.T) { return } - assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") + assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) contents, err := io.ReadAll(resp.Body) require.NoError(t, err) diff --git a/pkg/api/handler_tcp_test.go b/pkg/api/handler_tcp_test.go index f2fd35ad1..6c963bce5 100644 --- a/pkg/api/handler_tcp_test.go +++ b/pkg/api/handler_tcp_test.go @@ -833,7 +833,7 @@ func TestHandler_TCP(t *testing.T) { return } - assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") + assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) contents, err := io.ReadAll(resp.Body) require.NoError(t, err) diff --git a/pkg/api/handler_test.go b/pkg/api/handler_test.go index 02889161b..01ee922ef 100644 --- a/pkg/api/handler_test.go +++ b/pkg/api/handler_test.go @@ -143,7 +143,7 @@ func TestHandler_RawData(t *testing.T) { require.NoError(t, err) assert.Equal(t, test.expected.statusCode, resp.StatusCode) - assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") + assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) contents, err := io.ReadAll(resp.Body) require.NoError(t, err) diff --git a/pkg/api/handler_udp_test.go b/pkg/api/handler_udp_test.go index b875b2dc8..f9e2cb356 100644 --- a/pkg/api/handler_udp_test.go +++ b/pkg/api/handler_udp_test.go @@ -544,7 +544,7 @@ func TestHandler_UDP(t *testing.T) { return } - assert.Equal(t, resp.Header.Get("Content-Type"), "application/json") + assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) contents, err := io.ReadAll(resp.Body) require.NoError(t, err) diff --git a/pkg/api/testdata/overview-features.json b/pkg/api/testdata/overview-features.json index 5df280da0..a3db11a75 100644 --- a/pkg/api/testdata/overview-features.json +++ b/pkg/api/testdata/overview-features.json @@ -2,7 +2,7 @@ "features": { "accessLog": false, "metrics": "Prometheus", - "tracing": "Jaeger" + "tracing": "" }, "http": { "middlewares": { diff --git a/pkg/collector/collector_test.go b/pkg/collector/collector_test.go index 44c1ecef7..00034201a 100644 --- a/pkg/collector/collector_test.go +++ b/pkg/collector/collector_test.go @@ -5,13 +5,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/collector/hydratation" "github.com/traefik/traefik/v3/pkg/config/static" ) func Test_createBody(t *testing.T) { var staticConfiguration static.Configuration - err := hydrate(&staticConfiguration) + err := hydratation.Hydrate(&staticConfiguration) require.NoError(t, err) buffer, err := createBody(&staticConfiguration) diff --git a/pkg/collector/hydration_test.go b/pkg/collector/hydratation/hydration.go similarity index 94% rename from pkg/collector/hydration_test.go rename to pkg/collector/hydratation/hydration.go index 8814888a6..a5ec52dee 100644 --- a/pkg/collector/hydration_test.go +++ b/pkg/collector/hydratation/hydration.go @@ -1,4 +1,4 @@ -package collector +package hydratation import ( "fmt" @@ -17,7 +17,8 @@ const ( defaultMapKeyPrefix = "name" ) -func hydrate(element interface{}) error { +// Hydrate hydrates a configuration. +func Hydrate(element interface{}) error { field := reflect.ValueOf(element) return fill(field) } @@ -41,9 +42,7 @@ func fill(field reflect.Value) error { return err } case reflect.Interface: - if err := fill(field.Elem()); err != nil { - return err - } + setTyped(field, defaultString) case reflect.String: setTyped(field, defaultString) case reflect.Int: @@ -118,7 +117,7 @@ func makeKeyName(typ reflect.Type) string { case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, - reflect.Bool, reflect.Float32, reflect.Float64: + reflect.Bool, reflect.Float32, reflect.Float64, reflect.Interface: return defaultMapKeyPrefix default: return typ.Name() diff --git a/pkg/config/dynamic/config.go b/pkg/config/dynamic/config.go index af9f657af..621c004e8 100644 --- a/pkg/config/dynamic/config.go +++ b/pkg/config/dynamic/config.go @@ -24,7 +24,7 @@ type Configuration struct { HTTP *HTTPConfiguration `json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` TCP *TCPConfiguration `json:"tcp,omitempty" toml:"tcp,omitempty" yaml:"tcp,omitempty" export:"true"` UDP *UDPConfiguration `json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty" export:"true"` - TLS *TLSConfiguration `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + TLS *TLSConfiguration `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"-" export:"true"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/dynamic/fixtures/sample.toml b/pkg/config/dynamic/fixtures/sample.toml index 86242772c..f6ab56cf1 100644 --- a/pkg/config/dynamic/fixtures/sample.toml +++ b/pkg/config/dynamic/fixtures/sample.toml @@ -122,41 +122,29 @@ [tracing] serviceName = "foobar" - spanNameLimit = 42 - [tracing.jaeger] - samplingServerURL = "foobar" - samplingType = "foobar" - samplingParam = 42.0 - localAgentHostPort = "foobar" - gen128Bit = true - propagation = "foobar" - traceContextHeaderName = "foobar" - [tracing.zipkin] - httpEndpoint = "foobar" - sameSpan = true - id128Bit = true - debug = true - sampleRate = 42.0 - [tracing.datadog] - localAgentHostPort = "foobar" - localAgentSocket = "foobar" - debug = true - prioritySampling = true - traceIDHeaderName = "foobar" - parentIDHeaderName = "foobar" - samplingPriorityHeaderName = "foobar" - bagagePrefixHeaderName = "foobar" - [tracing.instana] - localAgentHost = "foobar" - localAgentPort = 42 - logLevel = "foobar" - [tracing.haystack] - localAgentHost = "foobar" - localAgentPort = 42 - globalTag = "foobar" - traceIDHeaderName = "foobar" - parentIDHeaderName = "foobar" - spanIDHeaderName = "foobar" + sampleRate = 42 + [tracing.headers] + header1 = "foobar" + header2 = "foobar" + [tracing.globalAttributes] + attr1 = "foobar" + attr2 = "foobar" + [tracing.otlp.grpc] + endpoint = "foobar" + insecure = true + [tracing.otlp.grpc.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [tracing.otlp.http] + endpoint = "foobar" + insecure = true + [tracing.otlp.http.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true [hostResolver] cnameFlattening = true diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index de8a2d955..a447e94eb 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -61,6 +61,7 @@ type Router struct { Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"` Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"` TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` + DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` } // +k8s:deepcopy-gen=true @@ -154,6 +155,10 @@ type Cookie struct { // SameSite defines the same site policy. // More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite SameSite string `json:"sameSite,omitempty" toml:"sameSite,omitempty" yaml:"sameSite,omitempty" export:"true"` + // MaxAge indicates the number of seconds until the cookie expires. + // When set to a negative number, the cookie expires immediately. + // When set to zero, the cookie never expires. + MaxAge int `json:"maxAge,omitempty" toml:"maxAge,omitempty" yaml:"maxAge,omitempty" export:"true"` } // +k8s:deepcopy-gen=true @@ -264,15 +269,15 @@ type HealthCheck struct{} // ServersTransport options to configure communication between Traefik and the servers. type ServersTransport struct { - ServerName string `description:"Defines the serverName used to contact the server." json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty"` - InsecureSkipVerify bool `description:"Disables SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` - RootCAs []traefiktls.FileOrContent `description:"Defines a list of CA secret used to validate self-signed certificate" json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` - Certificates traefiktls.Certificates `description:"Defines a list of secret storing client certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" export:"true"` - MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` - ForwardingTimeouts *ForwardingTimeouts `description:"Defines the timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` - DisableHTTP2 bool `description:"Disables HTTP/2 for connections with backend servers." json:"disableHTTP2,omitempty" toml:"disableHTTP2,omitempty" yaml:"disableHTTP2,omitempty" export:"true"` - PeerCertURI string `description:"Defines the URI used to match against SAN URI during the peer certificate verification." json:"peerCertURI,omitempty" toml:"peerCertURI,omitempty" yaml:"peerCertURI,omitempty" export:"true"` - Spiffe *Spiffe `description:"Defines the SPIFFE configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + ServerName string `description:"Defines the serverName used to contact the server." json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty"` + InsecureSkipVerify bool `description:"Disables SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` + RootCAs []types.FileOrContent `description:"Defines a list of CA secret used to validate self-signed certificate" json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` + Certificates traefiktls.Certificates `description:"Defines a list of secret storing client certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" export:"true"` + MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` + ForwardingTimeouts *ForwardingTimeouts `description:"Defines the timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` + DisableHTTP2 bool `description:"Disables HTTP/2 for connections with backend servers." json:"disableHTTP2,omitempty" toml:"disableHTTP2,omitempty" yaml:"disableHTTP2,omitempty" export:"true"` + PeerCertURI string `description:"Defines the URI used to match against SAN URI during the peer certificate verification." json:"peerCertURI,omitempty" toml:"peerCertURI,omitempty" yaml:"peerCertURI,omitempty" export:"true"` + Spiffe *Spiffe `description:"Defines the SPIFFE configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 6ab6e5a05..a2c1794eb 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -12,12 +12,14 @@ import ( // Middleware holds the Middleware configuration. type Middleware struct { - AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty" export:"true"` - StripPrefix *StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty" export:"true"` - StripPrefixRegex *StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty" export:"true"` - ReplacePath *ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty" export:"true"` - ReplacePathRegex *ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty" export:"true"` - Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty" export:"true"` + AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty" export:"true"` + StripPrefix *StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty" export:"true"` + StripPrefixRegex *StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty" export:"true"` + ReplacePath *ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty" export:"true"` + ReplacePathRegex *ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty" export:"true"` + Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty" export:"true"` + // Deprecated: please use IPAllowList instead. + IPWhiteList *IPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty" export:"true"` IPAllowList *IPAllowList `json:"ipAllowList,omitempty" toml:"ipAllowList,omitempty" yaml:"ipAllowList,omitempty" export:"true"` Headers *Headers `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` Errors *ErrorPage `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty" export:"true"` @@ -157,6 +159,8 @@ type Compress struct { // ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. // `application/grpc` is always excluded. ExcludedContentTypes []string `json:"excludedContentTypes,omitempty" toml:"excludedContentTypes,omitempty" yaml:"excludedContentTypes,omitempty" export:"true"` + // IncludedContentTypes defines the list of content types to compare the Content-Type header of the responses before compressing. + IncludedContentTypes []string `json:"includedContentTypes,omitempty" toml:"includedContentTypes,omitempty" yaml:"includedContentTypes,omitempty" export:"true"` // 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"` @@ -221,6 +225,8 @@ type ForwardAuth struct { // AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server. // If not set or empty then all request headers are passed. AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty" export:"true"` + // AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response. + AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty" toml:"addAuthCookiesToResponse,omitempty" yaml:"addAuthCookiesToResponse,omitempty" export:"true"` } // +k8s:deepcopy-gen=true @@ -376,6 +382,18 @@ func (s *IPStrategy) Get() (ip.Strategy, error) { // +k8s:deepcopy-gen=true +// IPWhiteList holds the IP whitelist middleware configuration. +// This middleware accepts / refuses requests based on the client IP. +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipwhitelist/ +// Deprecated: please use IPAllowList instead. +type IPWhiteList struct { + // SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). + SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` + IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` +} + +// +k8s:deepcopy-gen=true + // IPAllowList holds the IP allowlist middleware configuration. // This middleware accepts / refuses requests based on the client IP. // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/ @@ -383,6 +401,9 @@ type IPAllowList struct { // SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` + // RejectStatusCode defines the HTTP status code used for refused requests. + // If not set, the default is 403 (Forbidden). + RejectStatusCode int `json:"rejectStatusCode,omitempty" toml:"rejectStatusCode,omitempty" yaml:"rejectStatusCode,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/dynamic/tcp_config.go b/pkg/config/dynamic/tcp_config.go index 7789c5169..adedd0882 100644 --- a/pkg/config/dynamic/tcp_config.go +++ b/pkg/config/dynamic/tcp_config.go @@ -138,12 +138,12 @@ type TCPServersTransport struct { // TLSClientConfig options to configure TLS communication between Traefik and the servers. type TLSClientConfig struct { - ServerName string `description:"Defines the serverName used to contact the server." json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty"` - InsecureSkipVerify bool `description:"Disables SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` - RootCAs []traefiktls.FileOrContent `description:"Defines a list of CA secret used to validate self-signed certificate" json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` - Certificates traefiktls.Certificates `description:"Defines a list of secret storing client certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" export:"true"` - PeerCertURI string `description:"Defines the URI used to match against SAN URI during the peer certificate verification." json:"peerCertURI,omitempty" toml:"peerCertURI,omitempty" yaml:"peerCertURI,omitempty" export:"true"` - Spiffe *Spiffe `description:"Defines the SPIFFE TLS configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + ServerName string `description:"Defines the serverName used to contact the server." json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty"` + InsecureSkipVerify bool `description:"Disables SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` + RootCAs []types.FileOrContent `description:"Defines a list of CA secret used to validate self-signed certificate" json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` + Certificates traefiktls.Certificates `description:"Defines a list of secret storing client certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" export:"true"` + PeerCertURI string `description:"Defines the URI used to match against SAN URI during the peer certificate verification." json:"peerCertURI,omitempty" toml:"peerCertURI,omitempty" yaml:"peerCertURI,omitempty" export:"true"` + Spiffe *Spiffe `description:"Defines the SPIFFE TLS configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // SetDefaults sets the default values for a TCPServersTransport. diff --git a/pkg/config/dynamic/tcp_middlewares.go b/pkg/config/dynamic/tcp_middlewares.go index a688d4bd9..4368dc334 100644 --- a/pkg/config/dynamic/tcp_middlewares.go +++ b/pkg/config/dynamic/tcp_middlewares.go @@ -5,7 +5,9 @@ package dynamic // TCPMiddleware holds the TCPMiddleware configuration. type TCPMiddleware struct { InFlightConn *TCPInFlightConn `json:"inFlightConn,omitempty" toml:"inFlightConn,omitempty" yaml:"inFlightConn,omitempty" export:"true"` - IPAllowList *TCPIPAllowList `json:"ipAllowList,omitempty" toml:"ipAllowList,omitempty" yaml:"ipAllowList,omitempty" export:"true"` + // Deprecated: please use IPAllowList instead. + IPWhiteList *TCPIPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty" export:"true"` + IPAllowList *TCPIPAllowList `json:"ipAllowList,omitempty" toml:"ipAllowList,omitempty" yaml:"ipAllowList,omitempty" export:"true"` } // +k8s:deepcopy-gen=true @@ -22,8 +24,16 @@ type TCPInFlightConn struct { // +k8s:deepcopy-gen=true +// TCPIPWhiteList holds the TCP IPWhiteList middleware configuration. +// Deprecated: please use IPAllowList instead. +type TCPIPWhiteList struct { + // SourceRange defines the allowed IPs (or ranges of allowed IPs by using CIDR notation). + SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` +} + +// +k8s:deepcopy-gen=true + // TCPIPAllowList holds the TCP IPAllowList middleware configuration. -// This middleware accepts/refuses connections based on the client IP. type TCPIPAllowList struct { // SourceRange defines the allowed IPs (or ranges of allowed IPs by using CIDR notation). SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 29251247e..847e350ba 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -4,7 +4,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -132,6 +132,11 @@ func (in *Compress) DeepCopyInto(out *Compress) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.IncludedContentTypes != nil { + in, out := &in.IncludedContentTypes, &out.IncludedContentTypes + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -324,6 +329,11 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.AddAuthCookiesToResponse != nil { + in, out := &in.AddAuthCookiesToResponse, &out.AddAuthCookiesToResponse + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -600,6 +610,32 @@ func (in *IPStrategy) DeepCopy() *IPStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPWhiteList) DeepCopyInto(out *IPWhiteList) { + *out = *in + if in.SourceRange != nil { + in, out := &in.SourceRange, &out.SourceRange + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IPStrategy != nil { + in, out := &in.IPStrategy, &out.IPStrategy + *out = new(IPStrategy) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPWhiteList. +func (in *IPWhiteList) DeepCopy() *IPWhiteList { + if in == nil { + return nil + } + out := new(IPWhiteList) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InFlightReq) DeepCopyInto(out *InFlightReq) { *out = *in @@ -675,6 +711,11 @@ func (in *Middleware) DeepCopyInto(out *Middleware) { *out = new(Chain) (*in).DeepCopyInto(*out) } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(IPWhiteList) + (*in).DeepCopyInto(*out) + } if in.IPAllowList != nil { in, out := &in.IPAllowList, &out.IPAllowList *out = new(IPAllowList) @@ -1174,7 +1215,7 @@ func (in *ServersTransport) DeepCopyInto(out *ServersTransport) { *out = *in if in.RootCAs != nil { in, out := &in.RootCAs, &out.RootCAs - *out = make([]tls.FileOrContent, len(*in)) + *out = make([]types.FileOrContent, len(*in)) copy(*out, *in) } if in.Certificates != nil { @@ -1443,6 +1484,27 @@ func (in *TCPIPAllowList) DeepCopy() *TCPIPAllowList { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPIPWhiteList) DeepCopyInto(out *TCPIPWhiteList) { + *out = *in + if in.SourceRange != nil { + in, out := &in.SourceRange, &out.SourceRange + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPIPWhiteList. +func (in *TCPIPWhiteList) DeepCopy() *TCPIPWhiteList { + if in == nil { + return nil + } + out := new(TCPIPWhiteList) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPInFlightConn) DeepCopyInto(out *TCPInFlightConn) { *out = *in @@ -1467,6 +1529,11 @@ func (in *TCPMiddleware) DeepCopyInto(out *TCPMiddleware) { *out = new(TCPInFlightConn) **out = **in } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(TCPIPWhiteList) + (*in).DeepCopyInto(*out) + } if in.IPAllowList != nil { in, out := &in.IPAllowList, &out.IPAllowList *out = new(TCPIPAllowList) @@ -1712,7 +1779,7 @@ func (in *TLSClientConfig) DeepCopyInto(out *TLSClientConfig) { *out = *in if in.RootCAs != nil { in, out := &in.RootCAs, &out.RootCAs - *out = make([]tls.FileOrContent, len(*in)) + *out = make([]types.FileOrContent, len(*in)) copy(*out, *in) } if in.Certificates != nil { diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index 334297edc..28606cdbc 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -1254,6 +1254,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds": "42", "traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.Depth": "42", "traefik.HTTP.Middlewares.Middleware9.IPAllowList.IPStrategy.ExcludedIPs": "foobar, fiibar", + "traefik.HTTP.Middlewares.Middleware9.IPAllowList.RejectStatusCode": "0", "traefik.HTTP.Middlewares.Middleware9.IPAllowList.SourceRange": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware10.InFlightReq.Amount": "42", "traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.Depth": "42", @@ -1333,6 +1334,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.Name": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.HTTPOnly": "true", "traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.Secure": "false", + "traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.MaxAge": "0", "traefik.HTTP.Services.Service0.LoadBalancer.ServersTransport": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name0": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name1": "foobar", diff --git a/pkg/config/runtime/runtime.go b/pkg/config/runtime/runtime.go index 2738b7c60..d67233887 100644 --- a/pkg/config/runtime/runtime.go +++ b/pkg/config/runtime/runtime.go @@ -228,15 +228,6 @@ func (c *Configuration) PopulateUsedBy() { } } -func contains(entryPoints []string, entryPointName string) bool { - for _, name := range entryPoints { - if name == entryPointName { - return true - } - } - return false -} - func getProviderName(elementName string) string { parts := strings.Split(elementName, "@") if len(parts) > 1 { diff --git a/pkg/config/runtime/runtime_http.go b/pkg/config/runtime/runtime_http.go index 6b9a4a3fb..1ea5eebd1 100644 --- a/pkg/config/runtime/runtime_http.go +++ b/pkg/config/runtime/runtime_http.go @@ -3,6 +3,7 @@ package runtime import ( "context" "fmt" + "slices" "sort" "sync" @@ -24,7 +25,7 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints entryPointsCount := 0 for _, entryPointName := range rt.EntryPoints { - if !contains(entryPoints, entryPointName) { + if !slices.Contains(entryPoints, entryPointName) { rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false) logger.Error().Str(logs.EntryPointName, entryPointName). Msg("EntryPoint doesn't exist") diff --git a/pkg/config/runtime/runtime_tcp.go b/pkg/config/runtime/runtime_tcp.go index c188cd2d0..a5826c77d 100644 --- a/pkg/config/runtime/runtime_tcp.go +++ b/pkg/config/runtime/runtime_tcp.go @@ -3,6 +3,7 @@ package runtime import ( "context" "fmt" + "slices" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" @@ -18,7 +19,7 @@ func (c *Configuration) GetTCPRoutersByEntryPoints(ctx context.Context, entryPoi entryPointsCount := 0 for _, entryPointName := range rt.EntryPoints { - if !contains(entryPoints, entryPointName) { + if !slices.Contains(entryPoints, entryPointName) { rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false) logger.Error().Str(logs.EntryPointName, entryPointName). Msg("EntryPoint doesn't exist") diff --git a/pkg/config/runtime/runtime_udp.go b/pkg/config/runtime/runtime_udp.go index b0eb30e50..47d88c84f 100644 --- a/pkg/config/runtime/runtime_udp.go +++ b/pkg/config/runtime/runtime_udp.go @@ -3,6 +3,7 @@ package runtime import ( "context" "fmt" + "slices" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" @@ -24,7 +25,7 @@ func (c *Configuration) GetUDPRoutersByEntryPoints(ctx context.Context, entryPoi entryPointsCount := 0 for _, entryPointName := range eps { - if !contains(entryPoints, entryPointName) { + if !slices.Contains(entryPoints, entryPointName) { rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false) logger.Error().Str(logs.EntryPointName, entryPointName). Msg("EntryPoint doesn't exist") diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 47976f68d..2c1187ac5 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -123,8 +123,10 @@ type EntryPoints map[string]*EntryPoint // EntryPointsTransport configures communication between clients and Traefik. type EntryPointsTransport struct { - LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle." json:"lifeCycle,omitempty" toml:"lifeCycle,omitempty" yaml:"lifeCycle,omitempty" export:"true"` - RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance." json:"respondingTimeouts,omitempty" toml:"respondingTimeouts,omitempty" yaml:"respondingTimeouts,omitempty" export:"true"` + LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle." json:"lifeCycle,omitempty" toml:"lifeCycle,omitempty" yaml:"lifeCycle,omitempty" export:"true"` + RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance." json:"respondingTimeouts,omitempty" toml:"respondingTimeouts,omitempty" yaml:"respondingTimeouts,omitempty" export:"true"` + KeepAliveMaxTime ptypes.Duration `description:"Maximum duration before closing a keep-alive connection." json:"keepAliveMaxTime,omitempty" toml:"keepAliveMaxTime,omitempty" yaml:"keepAliveMaxTime,omitempty" export:"true"` + KeepAliveMaxRequests int `description:"Maximum number of requests before closing a keep-alive connection." json:"keepAliveMaxRequests,omitempty" toml:"keepAliveMaxRequests,omitempty" yaml:"keepAliveMaxRequests,omitempty" export:"true"` } // SetDefaults sets the default values. diff --git a/pkg/config/static/experimental.go b/pkg/config/static/experimental.go index 936dfd534..7428f6a08 100644 --- a/pkg/config/static/experimental.go +++ b/pkg/config/static/experimental.go @@ -8,5 +8,4 @@ type Experimental struct { LocalPlugins map[string]plugins.LocalDescriptor `description:"Local plugins configuration." json:"localPlugins,omitempty" toml:"localPlugins,omitempty" yaml:"localPlugins,omitempty" export:"true"` KubernetesGateway bool `description:"Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"` - HTTP3 bool `description:"Enable HTTP3." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" export:"true"` } diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 49fa37569..9f0ce294f 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -1,6 +1,7 @@ package static import ( + "errors" "fmt" "strings" "time" @@ -26,14 +27,7 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kv/zk" "github.com/traefik/traefik/v3/pkg/provider/nomad" "github.com/traefik/traefik/v3/pkg/provider/rest" - "github.com/traefik/traefik/v3/pkg/tls" - "github.com/traefik/traefik/v3/pkg/tracing/datadog" - "github.com/traefik/traefik/v3/pkg/tracing/elastic" - "github.com/traefik/traefik/v3/pkg/tracing/haystack" - "github.com/traefik/traefik/v3/pkg/tracing/instana" - "github.com/traefik/traefik/v3/pkg/tracing/jaeger" "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" - "github.com/traefik/traefik/v3/pkg/tracing/zipkin" "github.com/traefik/traefik/v3/pkg/types" ) @@ -96,16 +90,16 @@ type CertificateResolver struct { // Global holds the global configuration. type Global struct { CheckNewVersion bool `description:"Periodically check if a new version has been released." json:"checkNewVersion,omitempty" toml:"checkNewVersion,omitempty" yaml:"checkNewVersion,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - SendAnonymousUsage bool `description:"Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default." json:"sendAnonymousUsage,omitempty" toml:"sendAnonymousUsage,omitempty" yaml:"sendAnonymousUsage,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + SendAnonymousUsage bool `description:"Periodically send anonymous usage statistics. If the option is not specified, it will be disabled by default." json:"sendAnonymousUsage,omitempty" toml:"sendAnonymousUsage,omitempty" yaml:"sendAnonymousUsage,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // ServersTransport options to configure communication between Traefik and the servers. type ServersTransport struct { - InsecureSkipVerify bool `description:"Disable SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` - RootCAs []tls.FileOrContent `description:"Add cert file for self-signed certificate." json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` - MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` - ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` - Spiffe *Spiffe `description:"Defines the SPIFFE configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + InsecureSkipVerify bool `description:"Disable SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` + RootCAs []types.FileOrContent `description:"Add cert file for self-signed certificate." json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` + MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` + ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` + Spiffe *Spiffe `description:"Defines the SPIFFE configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // Spiffe holds the SPIFFE configuration. @@ -129,9 +123,9 @@ type TCPServersTransport struct { // TLSClientConfig options to configure TLS communication between Traefik and the servers. type TLSClientConfig struct { - InsecureSkipVerify bool `description:"Disables SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` - RootCAs []tls.FileOrContent `description:"Defines a list of CA secret used to validate self-signed certificate" json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` - Spiffe *Spiffe `description:"Defines the SPIFFE TLS configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + InsecureSkipVerify bool `description:"Disables SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` + RootCAs []types.FileOrContent `description:"Defines a list of CA secret used to validate self-signed certificate" json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` + Spiffe *Spiffe `description:"Defines the SPIFFE TLS configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // API holds the API configuration. @@ -187,21 +181,18 @@ func (a *LifeCycle) SetDefaults() { // Tracing holds the tracing configuration. type Tracing struct { - ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` - SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)." json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty" export:"true"` - Jaeger *jaeger.Config `description:"Settings for Jaeger." json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Zipkin *zipkin.Config `description:"Settings for Zipkin." json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Datadog *datadog.Config `description:"Settings for Datadog." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Instana *instana.Config `description:"Settings for Instana." json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Haystack *haystack.Config `description:"Settings for Haystack." json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Elastic *elastic.Config `description:"Settings for Elastic." json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - OpenTelemetry *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"openTelemetry,omitempty" toml:"openTelemetry,omitempty" yaml:"openTelemetry,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` + Headers map[string]string `description:"Defines additional headers to be sent with the payloads." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` + GlobalAttributes map[string]string `description:"Defines additional attributes (key:value) on all spans." json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"` + SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"` + + OTLP *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // SetDefaults sets the default values. func (t *Tracing) SetDefaults() { t.ServiceName = "traefik" - t.SpanNameLimit = 0 + t.SampleRate = 1.0 } // Providers contains providers configuration. @@ -326,6 +317,20 @@ func (c *Configuration) ValidateConfiguration() error { acmeEmail = resolver.ACME.Email } + if c.Tracing != nil && c.Tracing.OTLP != nil { + if c.Tracing.OTLP.HTTP == nil && c.Tracing.OTLP.GRPC == nil { + return errors.New("tracing OTLP: at least one of HTTP and gRPC options should be defined") + } + + if c.Tracing.OTLP.HTTP != nil && c.Tracing.OTLP.GRPC != nil { + return errors.New("tracing OTLP: HTTP and gRPC options are mutually exclusive") + } + + if c.Tracing.OTLP.GRPC != nil && c.Tracing.OTLP.GRPC.TLS != nil && c.Tracing.OTLP.GRPC.Insecure { + return errors.New("tracing OTLP GRPC: TLS and Insecure options are mutually exclusive") + } + } + return nil } diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go index 1452bfa66..b0e02e7a3 100644 --- a/pkg/healthcheck/healthcheck.go +++ b/pkg/healthcheck/healthcheck.go @@ -69,11 +69,6 @@ func NewServiceHealthChecker(ctx context.Context, metrics metricsHealthCheck, co timeout = time.Duration(dynamic.DefaultHealthCheckTimeout) } - if timeout >= interval { - logger.Warn().Msgf("Health check timeout should be lower than the health check interval. Interval set to timeout + 1 second (%s).", interval) - interval = timeout + time.Second - } - client := &http.Client{ Transport: transport, } @@ -260,8 +255,8 @@ func (shc *ServiceHealthChecker) checkHealthGRPC(ctx context.Context, serverURL return fmt.Errorf("gRPC health check failed: %w", err) } - if resp.Status != healthpb.HealthCheckResponse_SERVING { - return fmt.Errorf("received gRPC status code: %v", resp.Status) + if resp.GetStatus() != healthpb.HealthCheckResponse_SERVING { + return fmt.Errorf("received gRPC status code: %v", resp.GetStatus()) } return nil diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go index c7813fae0..fe8f4b7a4 100644 --- a/pkg/healthcheck/healthcheck_test.go +++ b/pkg/healthcheck/healthcheck_test.go @@ -18,6 +18,59 @@ import ( healthpb "google.golang.org/grpc/health/grpc_health_v1" ) +const delta float64 = 1e-10 + +func TestNewServiceHealthChecker_durations(t *testing.T) { + testCases := []struct { + desc string + config *dynamic.ServerHealthCheck + expInterval time.Duration + expTimeout time.Duration + }{ + { + desc: "default values", + config: &dynamic.ServerHealthCheck{}, + expInterval: time.Duration(dynamic.DefaultHealthCheckInterval), + expTimeout: time.Duration(dynamic.DefaultHealthCheckTimeout), + }, + { + desc: "out of range values", + config: &dynamic.ServerHealthCheck{ + Interval: ptypes.Duration(-time.Second), + Timeout: ptypes.Duration(-time.Second), + }, + expInterval: time.Duration(dynamic.DefaultHealthCheckInterval), + expTimeout: time.Duration(dynamic.DefaultHealthCheckTimeout), + }, + { + desc: "custom durations", + config: &dynamic.ServerHealthCheck{ + Interval: ptypes.Duration(time.Second * 10), + Timeout: ptypes.Duration(time.Second * 5), + }, + expInterval: time.Second * 10, + expTimeout: time.Second * 5, + }, + { + desc: "interval shorter than timeout", + config: &dynamic.ServerHealthCheck{ + Interval: ptypes.Duration(time.Second), + Timeout: ptypes.Duration(time.Second * 5), + }, + expInterval: time.Second, + expTimeout: time.Second * 5, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + healthChecker := NewServiceHealthChecker(context.Background(), nil, test.config, nil, nil, http.DefaultTransport, nil) + assert.Equal(t, test.expInterval, healthChecker.interval) + assert.Equal(t, test.expTimeout, healthChecker.timeout) + }) + } +} + func TestServiceHealthChecker_newRequest(t *testing.T) { testCases := []struct { desc string @@ -397,8 +450,8 @@ func TestServiceHealthChecker_Launch(t *testing.T) { assert.Equal(t, test.expNumRemovedServers, lb.numRemovedServers, "removed servers") assert.Equal(t, test.expNumUpsertedServers, lb.numUpsertedServers, "upserted servers") - assert.Equal(t, test.expGaugeValue, gauge.GaugeValue, "ServerUp Gauge") - assert.Equal(t, serviceInfo.GetAllStatus(), map[string]string{targetURL.String(): test.targetStatus}) + assert.InDelta(t, test.expGaugeValue, gauge.GaugeValue, delta, "ServerUp Gauge") + assert.Equal(t, map[string]string{targetURL.String(): test.targetStatus}, serviceInfo.GetAllStatus()) }) } } diff --git a/pkg/logs/haystack.go b/pkg/logs/haystack.go deleted file mode 100644 index 6f3db7361..000000000 --- a/pkg/logs/haystack.go +++ /dev/null @@ -1,28 +0,0 @@ -package logs - -import ( - "github.com/rs/zerolog" -) - -type HaystackLogger struct { - logger zerolog.Logger -} - -func NewHaystackLogger(logger zerolog.Logger) *HaystackLogger { - return &HaystackLogger{logger: logger} -} - -// Error prints the error message. -func (l HaystackLogger) Error(format string, v ...interface{}) { - l.logger.Error().CallerSkipFrame(1).Msgf(format, v...) -} - -// Info prints the info message. -func (l HaystackLogger) Info(format string, v ...interface{}) { - l.logger.Info().CallerSkipFrame(1).Msgf(format, v...) -} - -// Debug prints the info message. -func (l HaystackLogger) Debug(format string, v ...interface{}) { - l.logger.Debug().CallerSkipFrame(1).Msgf(format, v...) -} diff --git a/pkg/logs/haystack_test.go b/pkg/logs/haystack_test.go deleted file mode 100644 index a6342bd99..000000000 --- a/pkg/logs/haystack_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package logs - -import ( - "bytes" - "os" - "testing" - "time" - - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" -) - -func TestNewHaystackLogger(t *testing.T) { - buf := bytes.NewBuffer(nil) - cwb := zerolog.ConsoleWriter{Out: buf, TimeFormat: time.RFC3339, NoColor: true} - - out := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}, cwb) - - logger := NewHaystackLogger(zerolog.New(out).With().Caller().Logger()) - - logger.Info("foo") - - assert.Equal(t, " INF haystack_test.go:21 > foo\n", buf.String()) -} diff --git a/pkg/logs/jaeger.go b/pkg/logs/jaeger.go deleted file mode 100644 index 2faedc2fb..000000000 --- a/pkg/logs/jaeger.go +++ /dev/null @@ -1,23 +0,0 @@ -package logs - -import ( - "github.com/rs/zerolog" -) - -// JaegerLogger is an implementation of the Logger interface that delegates to traefik log. -type JaegerLogger struct { - logger zerolog.Logger -} - -func NewJaegerLogger(logger zerolog.Logger) *JaegerLogger { - return &JaegerLogger{logger: logger} -} - -func (l *JaegerLogger) Error(msg string) { - l.logger.Error().CallerSkipFrame(1).Msg(msg) -} - -// Infof logs a message at debug priority. -func (l *JaegerLogger) Infof(msg string, args ...interface{}) { - l.logger.Debug().CallerSkipFrame(1).Msgf(msg, args...) -} diff --git a/pkg/logs/jaeger_test.go b/pkg/logs/jaeger_test.go deleted file mode 100644 index 7ab6fe53d..000000000 --- a/pkg/logs/jaeger_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package logs - -import ( - "bytes" - "os" - "testing" - "time" - - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" -) - -func TestNewJaegerLogger(t *testing.T) { - buf := bytes.NewBuffer(nil) - cwb := zerolog.ConsoleWriter{Out: buf, TimeFormat: time.RFC3339, NoColor: true} - - out := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}, cwb) - - logger := NewJaegerLogger(zerolog.New(out).With().Caller().Logger()) - - logger.Infof("foo") - - assert.Equal(t, " DBG jaeger_test.go:21 > foo\n", buf.String()) -} diff --git a/pkg/logs/wasm.go b/pkg/logs/wasm.go new file mode 100644 index 000000000..11d3e634f --- /dev/null +++ b/pkg/logs/wasm.go @@ -0,0 +1,32 @@ +package logs + +import ( + "context" + + "github.com/http-wasm/http-wasm-host-go/api" + "github.com/rs/zerolog" +) + +// compile-time check to ensure ConsoleLogger implements api.Logger. +var _ api.Logger = WasmLogger{} + +// WasmLogger is a convenience which writes anything above LogLevelInfo to os.Stdout. +type WasmLogger struct { + logger *zerolog.Logger +} + +func NewWasmLogger(logger *zerolog.Logger) *WasmLogger { + return &WasmLogger{ + logger: logger, + } +} + +// IsEnabled implements the same method as documented on api.Logger. +func (w WasmLogger) IsEnabled(level api.LogLevel) bool { + return true +} + +// Log implements the same method as documented on api.Logger. +func (w WasmLogger) Log(_ context.Context, level api.LogLevel, message string) { + w.logger.WithLevel(zerolog.Level(level + 1)).Msg(message) +} diff --git a/pkg/metrics/opentelemetry.go b/pkg/metrics/opentelemetry.go index 960a585e4..e866ff5e0 100644 --- a/pkg/metrics/opentelemetry.go +++ b/pkg/metrics/opentelemetry.go @@ -12,16 +12,14 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" sdkmetric "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/aggregation" "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.12.0" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding/gzip" ) @@ -41,12 +39,11 @@ func RegisterOpenTelemetry(ctx context.Context, config *types.OpenTelemetry) Reg return nil } } - if openTelemetryGaugeCollector == nil { openTelemetryGaugeCollector = newOpenTelemetryGaugeCollector() } - meter := global.Meter("github.com/traefik/traefik", + meter := otel.Meter("github.com/traefik/traefik", metric.WithInstrumentationVersion(version.Version)) reg := &standardRegistry{ @@ -160,13 +157,13 @@ func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OpenTeleme // View to customize histogram buckets and rename a single histogram instrument. sdkmetric.WithView(sdkmetric.NewView( sdkmetric.Instrument{Name: "traefik_*_request_duration_seconds"}, - sdkmetric.Stream{Aggregation: aggregation.ExplicitBucketHistogram{ + sdkmetric.Stream{Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: config.ExplicitBoundaries, }}, )), ) - global.SetMeterProvider(meterProvider) + otel.SetMeterProvider(meterProvider) return meterProvider, nil } @@ -233,8 +230,8 @@ func newGRPCExporter(ctx context.Context, config *types.OpenTelemetry) (sdkmetri func newOTLPCounterFrom(meter metric.Meter, name, desc string) *otelCounter { c, _ := meter.Float64Counter(name, - instrument.WithDescription(desc), - instrument.WithUnit("1"), + metric.WithDescription(desc), + metric.WithUnit("1"), ) return &otelCounter{ @@ -244,7 +241,7 @@ func newOTLPCounterFrom(meter metric.Meter, name, desc string) *otelCounter { type otelCounter struct { labelNamesValues otelLabelNamesValues - ip instrument.Float64Counter + ip metric.Float64Counter } func (c *otelCounter) With(labelValues ...string) metrics.Counter { @@ -255,7 +252,7 @@ func (c *otelCounter) With(labelValues ...string) metrics.Counter { } func (c *otelCounter) Add(delta float64) { - c.ip.Add(context.Background(), delta, c.labelNamesValues.ToLabels()...) + c.ip.Add(context.Background(), delta, metric.WithAttributes(c.labelNamesValues.ToLabels()...)) } type gaugeValue struct { @@ -323,8 +320,8 @@ func newOTLPGaugeFrom(meter metric.Meter, name, desc string, unit string) *otelG openTelemetryGaugeCollector.values[name] = make(map[string]gaugeValue) c, _ := meter.Float64ObservableGauge(name, - instrument.WithDescription(desc), - instrument.WithUnit(unit), + metric.WithDescription(desc), + metric.WithUnit(unit), ) _, err := meter.RegisterCallback(func(ctx context.Context, observer metric.Observer) error { @@ -337,7 +334,7 @@ func newOTLPGaugeFrom(meter metric.Meter, name, desc string, unit string) *otelG } for _, value := range values { - observer.ObserveFloat64(c, value.value, value.attributes.ToLabels()...) + observer.ObserveFloat64(c, value.value, metric.WithAttributes(value.attributes.ToLabels()...)) } return nil @@ -354,7 +351,7 @@ func newOTLPGaugeFrom(meter metric.Meter, name, desc string, unit string) *otelG type otelGauge struct { labelNamesValues otelLabelNamesValues - ip instrument.Float64ObservableGauge + ip metric.Float64ObservableGauge name string } @@ -376,8 +373,8 @@ func (g *otelGauge) Set(value float64) { func newOTLPHistogramFrom(meter metric.Meter, name, desc string, unit string) *otelHistogram { c, _ := meter.Float64Histogram(name, - instrument.WithDescription(desc), - instrument.WithUnit(unit), + metric.WithDescription(desc), + metric.WithUnit(unit), ) return &otelHistogram{ @@ -387,7 +384,7 @@ func newOTLPHistogramFrom(meter metric.Meter, name, desc string, unit string) *o type otelHistogram struct { labelNamesValues otelLabelNamesValues - ip instrument.Float64Histogram + ip metric.Float64Histogram } func (h *otelHistogram) With(labelValues ...string) metrics.Histogram { @@ -398,7 +395,7 @@ func (h *otelHistogram) With(labelValues ...string) metrics.Histogram { } func (h *otelHistogram) Observe(incr float64) { - h.ip.Record(context.Background(), incr, h.labelNamesValues.ToLabels()...) + h.ip.Record(context.Background(), incr, metric.WithAttributes(h.labelNamesValues.ToLabels()...)) } // otelLabelNamesValues is the equivalent of prometheus' labelNamesValues diff --git a/pkg/metrics/opentelemetry_test.go b/pkg/metrics/opentelemetry_test.go index 4c6b49563..c6bfe99eb 100644 --- a/pkg/metrics/opentelemetry_test.go +++ b/pkg/metrics/opentelemetry_test.go @@ -340,8 +340,8 @@ func TestOpenTelemetry(t *testing.T) { // TODO: the len of startUnixNano is no supposed to be 20, it should be 19 expected = append(expected, `({"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":\[{"startTimeUnixNano":"[\d]{20}","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]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, + `({"name":"traefik_config_last_reload_success","description":"Last config reload success","unit":"ms","gauge":{"dataPoints":\[{"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, + `({"name":"traefik_open_connections","description":"How many open connections exist, by entryPoint and protocol","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test"}},{"key":"protocol","value":{"stringValue":"TCP"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, ) registry.ConfigReloadsCounter().Add(1) @@ -352,7 +352,7 @@ func TestOpenTelemetry(t *testing.T) { assertMessage(t, *msgServer, expected) expected = append(expected, - `({"name":"traefik_tls_certs_not_after","description":"Certificate expiration timestamp","unit":"ms","gauge":{"dataPoints":\[{"attributes":\[{"key":"key","value":{"stringValue":"value"}}\],"startTimeUnixNano":"[\d]{20}","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"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, ) registry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1) @@ -399,7 +399,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","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`, - `({"name":"traefik_service_server_up","description":"service server is up, described by gauge value of 0 or 1.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"url","value":{"stringValue":"http://127.0.0.1"}}\],"startTimeUnixNano":"[\d]{20}","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"}}\],"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}})`, ) diff --git a/pkg/metrics/prometheus_test.go b/pkg/metrics/prometheus_test.go index 9a5bfeba7..4fcb56b90 100644 --- a/pkg/metrics/prometheus_test.go +++ b/pkg/metrics/prometheus_test.go @@ -389,12 +389,12 @@ func TestPrometheus(t *testing.T) { return } - for _, label := range family.Metric[0].Label { - val, ok := test.labels[*label.Name] + for _, label := range family.GetMetric()[0].GetLabel() { + val, ok := test.labels[label.GetName()] if !ok { - t.Errorf("%q metric contains unexpected label %q", test.name, *label.Name) - } else if val != *label.Value { - t.Errorf("label %q in metric %q has wrong value %q, expected %q", *label.Name, test.name, *label.Value, val) + t.Errorf("%q metric contains unexpected label %q", test.name, label.GetName()) + } else if val != label.GetValue() { + t.Errorf("label %q in metric %q has wrong value %q, expected %q", label.GetName(), test.name, label.GetValue(), val) } } test.assert(family) @@ -645,7 +645,7 @@ func findMetricByLabelNamesValues(family *dto.MetricFamily, labelNamesValues ... return nil } - for _, metric := range family.Metric { + for _, metric := range family.GetMetric() { if hasMetricAllLabelPairs(metric, labelNamesValues...) { return metric } @@ -665,7 +665,7 @@ func hasMetricAllLabelPairs(metric *dto.Metric, labelNamesValues ...string) bool } func hasMetricLabelPair(metric *dto.Metric, labelName, labelValue string) bool { - for _, labelPair := range metric.Label { + for _, labelPair := range metric.GetLabel() { if labelPair.GetName() == labelName && labelPair.GetValue() == labelValue { return true } @@ -682,12 +682,12 @@ func assertCounterValue(t *testing.T, want float64, family *dto.MetricFamily, la t.Error("metric must not be nil") return } - if metric.Counter == nil { + if metric.GetCounter() == nil { t.Errorf("metric %s must be a counter", family.GetName()) return } - if cv := metric.Counter.GetValue(); cv != want { + if cv := metric.GetCounter().GetValue(); cv != want { t.Errorf("metric %s has value %v, want %v", family.GetName(), cv, want) } } @@ -696,7 +696,7 @@ func buildCounterAssert(t *testing.T, metricName string, expectedValue int) func t.Helper() return func(family *dto.MetricFamily) { - if cv := int(family.Metric[0].Counter.GetValue()); cv != expectedValue { + if cv := int(family.GetMetric()[0].GetCounter().GetValue()); cv != expectedValue { t.Errorf("metric %s has value %d, want %d", metricName, cv, expectedValue) } } @@ -706,7 +706,7 @@ func buildGreaterThanCounterAssert(t *testing.T, metricName string, expectedMinV t.Helper() return func(family *dto.MetricFamily) { - if cv := int(family.Metric[0].Counter.GetValue()); cv < expectedMinValue { + if cv := int(family.GetMetric()[0].GetCounter().GetValue()); cv < expectedMinValue { t.Errorf("metric %s has value %d, want at least %d", metricName, cv, expectedMinValue) } } @@ -716,7 +716,7 @@ func buildHistogramAssert(t *testing.T, metricName string, expectedSampleCount i t.Helper() return func(family *dto.MetricFamily) { - if sc := int(family.Metric[0].Histogram.GetSampleCount()); sc != expectedSampleCount { + if sc := int(family.GetMetric()[0].GetHistogram().GetSampleCount()); sc != expectedSampleCount { t.Errorf("metric %s has sample count value %d, want %d", metricName, sc, expectedSampleCount) } } @@ -726,7 +726,7 @@ func buildGaugeAssert(t *testing.T, metricName string, expectedValue int) func(f t.Helper() return func(family *dto.MetricFamily) { - if gv := int(family.Metric[0].Gauge.GetValue()); gv != expectedValue { + if gv := int(family.GetMetric()[0].GetGauge().GetValue()); gv != expectedValue { t.Errorf("metric %s has value %d, want %d", metricName, gv, expectedValue) } } @@ -736,7 +736,7 @@ func buildTimestampAssert(t *testing.T, metricName string) func(family *dto.Metr t.Helper() return func(family *dto.MetricFamily) { - if ts := time.Unix(int64(family.Metric[0].Gauge.GetValue()), 0); time.Since(ts) > time.Minute { + if ts := time.Unix(int64(family.GetMetric()[0].GetGauge().GetValue()), 0); time.Since(ts) > time.Minute { t.Errorf("metric %s has wrong timestamp %v", metricName, ts) } } diff --git a/pkg/middlewares/accesslog/field_middleware.go b/pkg/middlewares/accesslog/field_middleware.go index 81f8cf606..d1b39438f 100644 --- a/pkg/middlewares/accesslog/field_middleware.go +++ b/pkg/middlewares/accesslog/field_middleware.go @@ -42,8 +42,8 @@ func (f *FieldHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } -// AddOriginFields add origin fields. -func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) { +// AddServiceFields add service fields. +func AddServiceFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) { start := time.Now().UTC() next.ServeHTTP(rw, req) @@ -65,3 +65,14 @@ func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handle data.Core[OriginStatus] = capt.StatusCode() data.Core[OriginContentSize] = capt.ResponseSize() } + +// InitServiceFields init service fields. +func InitServiceFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) { + // Because they are expected to be initialized when the logger is processing the data table, + // the origin fields are initialized in case the response is returned by Traefik itself, and not a service. + data.Core[OriginDuration] = time.Duration(0) + data.Core[OriginStatus] = 0 + data.Core[OriginContentSize] = int64(0) + + next.ServeHTTP(rw, req) +} diff --git a/pkg/middlewares/accesslog/logger_formatters.go b/pkg/middlewares/accesslog/logger_formatters.go index 5bda8eef9..c714ef146 100644 --- a/pkg/middlewares/accesslog/logger_formatters.go +++ b/pkg/middlewares/accesslog/logger_formatters.go @@ -40,8 +40,8 @@ func (f *CommonLogFormatter) Format(entry *logrus.Entry) ([]byte, error) { toLog(entry.Data, RequestMethod, defaultValue, false), toLog(entry.Data, RequestPath, defaultValue, false), toLog(entry.Data, RequestProtocol, defaultValue, false), - toLog(entry.Data, OriginStatus, defaultValue, true), - toLog(entry.Data, OriginContentSize, defaultValue, true), + toLog(entry.Data, DownstreamStatus, defaultValue, true), + toLog(entry.Data, DownstreamContentSize, defaultValue, true), toLog(entry.Data, "request_Referer", `"-"`, true), toLog(entry.Data, "request_User-Agent", `"-"`, true), toLog(entry.Data, RequestCount, defaultValue, true), diff --git a/pkg/middlewares/accesslog/logger_formatters_test.go b/pkg/middlewares/accesslog/logger_formatters_test.go index 74a86b2a4..cfa4281ec 100644 --- a/pkg/middlewares/accesslog/logger_formatters_test.go +++ b/pkg/middlewares/accesslog/logger_formatters_test.go @@ -18,7 +18,7 @@ func TestCommonLogFormatter_Format(t *testing.T) { expectedLog string }{ { - name: "OriginStatus & OriginContentSize are nil", + name: "DownstreamStatus & DownstreamContentSize are nil", data: map[string]interface{}{ StartUTC: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), Duration: 123 * time.Second, @@ -27,8 +27,8 @@ func TestCommonLogFormatter_Format(t *testing.T) { RequestMethod: http.MethodGet, RequestPath: "/foo", RequestProtocol: "http", - OriginStatus: nil, - OriginContentSize: nil, + DownstreamStatus: nil, + DownstreamContentSize: nil, RequestRefererHeader: "", RequestUserAgentHeader: "", RequestCount: 0, @@ -48,8 +48,8 @@ func TestCommonLogFormatter_Format(t *testing.T) { RequestMethod: http.MethodGet, RequestPath: "/foo", RequestProtocol: "http", - OriginStatus: 123, - OriginContentSize: 132, + DownstreamStatus: 123, + DownstreamContentSize: 132, RequestRefererHeader: "referer", RequestUserAgentHeader: "agent", RequestCount: nil, @@ -69,8 +69,8 @@ func TestCommonLogFormatter_Format(t *testing.T) { RequestMethod: http.MethodGet, RequestPath: "/foo", RequestProtocol: "http", - OriginStatus: 123, - OriginContentSize: 132, + DownstreamStatus: 123, + DownstreamContentSize: 132, RequestRefererHeader: "referer", RequestUserAgentHeader: "agent", RequestCount: nil, diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 0f3c63607..6f7d3a422 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -14,6 +14,7 @@ import ( "os" "path/filepath" "regexp" + "strconv" "strings" "testing" "time" @@ -323,7 +324,7 @@ func TestLoggerJSON(t *testing.T) { ServiceURL: assertString(testServiceName), ClientUsername: assertString(testUsername), ClientHost: assertString(testHostname), - ClientPort: assertString(fmt.Sprintf("%d", testPort)), + ClientPort: assertString(strconv.Itoa(testPort)), ClientAddr: assertString(fmt.Sprintf("%s:%d", testHostname, testPort)), "level": assertString("info"), "msg": assertString(""), @@ -363,7 +364,7 @@ func TestLoggerJSON(t *testing.T) { ServiceURL: assertString(testServiceName), ClientUsername: assertString(testUsername), ClientHost: assertString(testHostname), - ClientPort: assertString(fmt.Sprintf("%d", testPort)), + ClientPort: assertString(strconv.Itoa(testPort)), ClientAddr: assertString(fmt.Sprintf("%s:%d", testHostname, testPort)), "level": assertString("info"), "msg": assertString(""), diff --git a/pkg/middlewares/addprefix/add_prefix.go b/pkg/middlewares/addprefix/add_prefix.go index 4363da9b9..e91facaf9 100644 --- a/pkg/middlewares/addprefix/add_prefix.go +++ b/pkg/middlewares/addprefix/add_prefix.go @@ -5,10 +5,9 @@ import ( "fmt" "net/http" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const ( @@ -40,8 +39,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name return result, nil } -func (a *addPrefix) GetTracingInformation() (string, ext.SpanKindEnum) { - return a.name, tracing.SpanKindNoneEnum +func (a *addPrefix) GetTracingInformation() (string, string, trace.SpanKind) { + return a.name, typeName, trace.SpanKindInternal } func (a *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/auth/basic_auth.go b/pkg/middlewares/auth/basic_auth.go index 70e25d3a1..5a3caa26d 100644 --- a/pkg/middlewares/auth/basic_auth.go +++ b/pkg/middlewares/auth/basic_auth.go @@ -8,15 +8,15 @@ import ( "strings" goauth "github.com/abbot/go-http-auth" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const ( - basicTypeName = "BasicAuth" + typeNameBasic = "BasicAuth" ) type basicAuth struct { @@ -30,7 +30,7 @@ type basicAuth struct { // NewBasic creates a basicAuth middleware. func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAuth, name string) (http.Handler, error) { - middlewares.GetLogger(ctx, name, basicTypeName).Debug().Msg("Creating middleware") + middlewares.GetLogger(ctx, name, typeNameBasic).Debug().Msg("Creating middleware") users, err := getUsers(authConfig.UsersFile, authConfig.Users, basicUserParser) if err != nil { @@ -55,12 +55,12 @@ func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAu return ba, nil } -func (b *basicAuth) GetTracingInformation() (string, ext.SpanKindEnum) { - return b.name, tracing.SpanKindNoneEnum +func (b *basicAuth) GetTracingInformation() (string, string, trace.SpanKind) { + return b.name, typeNameBasic, trace.SpanKindInternal } func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - logger := middlewares.GetLogger(req.Context(), b.name, basicTypeName) + logger := middlewares.GetLogger(req.Context(), b.name, typeNameBasic) user, password, ok := req.BasicAuth() if ok { @@ -77,7 +77,7 @@ func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if !ok { logger.Debug().Msg("Authentication failed") - tracing.SetErrorWithEvent(req, "Authentication failed") + tracing.SetStatusErrorf(req.Context(), "Authentication failed") b.auth.RequireAuth(rw, req) return diff --git a/pkg/middlewares/auth/digest_auth.go b/pkg/middlewares/auth/digest_auth.go index ea772aeb5..e02bc5883 100644 --- a/pkg/middlewares/auth/digest_auth.go +++ b/pkg/middlewares/auth/digest_auth.go @@ -8,15 +8,15 @@ import ( "strings" goauth "github.com/abbot/go-http-auth" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const ( - digestTypeName = "digestAuth" + typeNameDigest = "digestAuth" ) type digestAuth struct { @@ -30,7 +30,7 @@ type digestAuth struct { // NewDigest creates a digest auth middleware. func NewDigest(ctx context.Context, next http.Handler, authConfig dynamic.DigestAuth, name string) (http.Handler, error) { - middlewares.GetLogger(ctx, name, digestTypeName).Debug().Msg("Creating middleware") + middlewares.GetLogger(ctx, name, typeNameDigest).Debug().Msg("Creating middleware") users, err := getUsers(authConfig.UsersFile, authConfig.Users, digestUserParser) if err != nil { @@ -54,12 +54,12 @@ func NewDigest(ctx context.Context, next http.Handler, authConfig dynamic.Digest return da, nil } -func (d *digestAuth) GetTracingInformation() (string, ext.SpanKindEnum) { - return d.name, tracing.SpanKindNoneEnum +func (d *digestAuth) GetTracingInformation() (string, string, trace.SpanKind) { + return d.name, typeNameDigest, trace.SpanKindInternal } func (d *digestAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - logger := middlewares.GetLogger(req.Context(), d.name, digestTypeName) + logger := middlewares.GetLogger(req.Context(), d.name, typeNameDigest) username, authinfo := d.auth.CheckAuth(req) if username == "" { @@ -78,13 +78,13 @@ func (d *digestAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if authinfo != nil && *authinfo == "stale" { logger.Debug().Msg("Digest authentication failed, possibly because out of order requests") - tracing.SetErrorWithEvent(req, "Digest authentication failed, possibly because out of order requests") + tracing.SetStatusErrorf(req.Context(), "Digest authentication failed, possibly because out of order requests") d.auth.RequireAuthStale(rw, req) return } logger.Debug().Msg("Digest authentication failed") - tracing.SetErrorWithEvent(req, "Digest authentication failed") + tracing.SetStatusErrorf(req.Context(), "Digest authentication failed") d.auth.RequireAuth(rw, req) return } diff --git a/pkg/middlewares/auth/digest_auth_request_test.go b/pkg/middlewares/auth/digest_auth_request_test.go index 7101016c8..95916d36f 100644 --- a/pkg/middlewares/auth/digest_auth_request_test.go +++ b/pkg/middlewares/auth/digest_auth_request_test.go @@ -137,5 +137,5 @@ func (r *digestRequest) makeAuthorization(req *http.Request, parts map[string]st func generateRandom(n int) string { b := make([]byte, 8) _, _ = io.ReadFull(rand.Reader, b) - return fmt.Sprintf("%x", b)[:n] + return hex.EncodeToString(b)[:n] } diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index 6ad5f628f..6e3b3ac84 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -11,21 +11,22 @@ import ( "strings" "time" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/connectionheader" "github.com/traefik/traefik/v3/pkg/tracing" "github.com/vulcand/oxy/v2/forward" "github.com/vulcand/oxy/v2/utils" + "go.opentelemetry.io/otel/trace" ) const ( - xForwardedURI = "X-Forwarded-Uri" - xForwardedMethod = "X-Forwarded-Method" - forwardedTypeName = "ForwardedAuthType" + xForwardedURI = "X-Forwarded-Uri" + xForwardedMethod = "X-Forwarded-Method" ) +const typeNameForward = "ForwardAuth" + // hopHeaders Hop-by-hop headers to be removed in the authentication request. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html // Proxy-Authorization header is forwarded to the authentication server (see https://tools.ietf.org/html/rfc7235#section-4.4). @@ -47,19 +48,26 @@ type forwardAuth struct { client http.Client trustForwardHeader bool authRequestHeaders []string + addAuthCookiesToResponse map[string]struct{} } // NewForward creates a forward auth middleware. func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAuth, name string) (http.Handler, error) { - middlewares.GetLogger(ctx, name, forwardedTypeName).Debug().Msg("Creating middleware") + middlewares.GetLogger(ctx, name, typeNameForward).Debug().Msg("Creating middleware") + + addAuthCookiesToResponse := make(map[string]struct{}) + for _, cookieName := range config.AddAuthCookiesToResponse { + addAuthCookiesToResponse[cookieName] = struct{}{} + } fa := &forwardAuth{ - address: config.Address, - authResponseHeaders: config.AuthResponseHeaders, - next: next, - name: name, - trustForwardHeader: config.TrustForwardHeader, - authRequestHeaders: config.AuthRequestHeaders, + address: config.Address, + authResponseHeaders: config.AuthResponseHeaders, + next: next, + name: name, + trustForwardHeader: config.TrustForwardHeader, + authRequestHeaders: config.AuthRequestHeaders, + addAuthCookiesToResponse: addAuthCookiesToResponse, } // Ensure our request client does not follow redirects @@ -89,30 +97,38 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu fa.authResponseHeadersRegex = re } - return connectionheader.Remover(fa), nil + return fa, nil } -func (fa *forwardAuth) GetTracingInformation() (string, ext.SpanKindEnum) { - return fa.name, ext.SpanKindRPCClientEnum +func (fa *forwardAuth) GetTracingInformation() (string, string, trace.SpanKind) { + return fa.name, typeNameForward, trace.SpanKindInternal } func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - logger := middlewares.GetLogger(req.Context(), fa.name, forwardedTypeName) + logger := middlewares.GetLogger(req.Context(), fa.name, typeNameForward) - forwardReq, err := http.NewRequest(http.MethodGet, fa.address, nil) - tracing.LogRequest(tracing.GetSpan(req), forwardReq) + req = connectionheader.Remove(req) + + forwardReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, fa.address, nil) if err != nil { logMessage := fmt.Sprintf("Error calling %s. Cause %s", fa.address, err) logger.Debug().Msg(logMessage) - tracing.SetErrorWithEvent(req, logMessage) - + tracing.SetStatusErrorf(req.Context(), logMessage) rw.WriteHeader(http.StatusInternalServerError) return } - // Ensure tracing headers are in the request before we copy the headers to the - // forwardReq. - tracing.InjectRequestHeaders(req) + var forwardSpan trace.Span + if tracer := tracing.TracerFromContext(req.Context()); tracer != nil { + var tracingCtx context.Context + tracingCtx, forwardSpan = tracer.Start(req.Context(), "AuthRequest", trace.WithSpanKind(trace.SpanKindClient)) + defer forwardSpan.End() + + forwardReq = forwardReq.WithContext(tracingCtx) + + tracing.InjectContextIntoCarrier(forwardReq) + tracing.LogClientRequest(forwardSpan, forwardReq) + } writeHeader(req, forwardReq, fa.trustForwardHeader, fa.authRequestHeaders) @@ -120,23 +136,29 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if forwardErr != nil { logMessage := fmt.Sprintf("Error calling %s. Cause: %s", fa.address, forwardErr) logger.Debug().Msg(logMessage) - tracing.SetErrorWithEvent(req, logMessage) - - rw.WriteHeader(http.StatusInternalServerError) - return - } - - body, readError := io.ReadAll(forwardResponse.Body) - if readError != nil { - logMessage := fmt.Sprintf("Error reading body %s. Cause: %s", fa.address, readError) - logger.Debug().Msg(logMessage) - tracing.SetErrorWithEvent(req, logMessage) + tracing.SetStatusErrorf(forwardReq.Context(), logMessage) rw.WriteHeader(http.StatusInternalServerError) return } defer forwardResponse.Body.Close() + body, readError := io.ReadAll(forwardResponse.Body) + if readError != nil { + logMessage := fmt.Sprintf("Error reading body %s. Cause: %s", fa.address, readError) + logger.Debug().Msg(logMessage) + tracing.SetStatusErrorf(forwardReq.Context(), logMessage) + + rw.WriteHeader(http.StatusInternalServerError) + return + } + + // Ending the forward request span as soon as the response is handled. + // If any errors happen earlier, this span will be close by the defer instruction. + if forwardSpan != nil { + forwardSpan.End() + } + // Pass the forward response's body and selected headers if it // didn't return a response within the range of [200, 300). if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices { @@ -152,7 +174,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if !errors.Is(err, http.ErrNoLocation) { logMessage := fmt.Sprintf("Error reading response location header %s. Cause: %s", fa.address, err) logger.Debug().Msg(logMessage) - tracing.SetErrorWithEvent(req, logMessage) + tracing.SetStatusErrorf(forwardReq.Context(), logMessage) rw.WriteHeader(http.StatusInternalServerError) return @@ -162,7 +184,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("Location", redirectURL.String()) } - tracing.LogResponseCode(tracing.GetSpan(req), forwardResponse.StatusCode) + tracing.LogResponseCode(forwardSpan, forwardResponse.StatusCode, trace.SpanKindClient) rw.WriteHeader(forwardResponse.StatusCode) if _, err = rw.Write(body); err != nil { @@ -193,8 +215,38 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } + tracing.LogResponseCode(forwardSpan, forwardResponse.StatusCode, trace.SpanKindClient) + req.RequestURI = req.URL.RequestURI() - fa.next.ServeHTTP(rw, req) + + authCookies := forwardResponse.Cookies() + if len(authCookies) == 0 { + fa.next.ServeHTTP(rw, req) + return + } + + fa.next.ServeHTTP(middlewares.NewResponseModifier(rw, req, fa.buildModifier(authCookies)), req) +} + +func (fa *forwardAuth) buildModifier(authCookies []*http.Cookie) func(res *http.Response) error { + return func(res *http.Response) error { + cookies := res.Cookies() + res.Header.Del("Set-Cookie") + + for _, cookie := range cookies { + if _, found := fa.addAuthCookiesToResponse[cookie.Name]; !found { + res.Header.Add("Set-Cookie", cookie.String()) + } + } + + for _, cookie := range authCookies { + if _, found := fa.addAuthCookiesToResponse[cookie.Name]; found { + res.Header.Add("Set-Cookie", cookie.String()) + } + } + + return nil + } } func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool, allowedHeaders []string) { diff --git a/pkg/middlewares/auth/forward_test.go b/pkg/middlewares/auth/forward_test.go index 0f300fc71..461d9b329 100644 --- a/pkg/middlewares/auth/forward_test.go +++ b/pkg/middlewares/auth/forward_test.go @@ -8,15 +8,22 @@ import ( "net/http/httptest" "testing" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/mocktracer" + "github.com/containous/alice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/config/static" tracingMiddleware "github.com/traefik/traefik/v3/pkg/middlewares/tracing" "github.com/traefik/traefik/v3/pkg/testhelpers" "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" + "github.com/traefik/traefik/v3/pkg/version" "github.com/vulcand/oxy/v2/forward" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) func TestForwardAuthFail(t *testing.T) { @@ -59,6 +66,8 @@ func TestForwardAuthSuccess(t *testing.T) { w.Header().Add("X-Auth-Group", "group1") w.Header().Add("X-Auth-Group", "group2") w.Header().Add("Foo-Bar", "auth-value") + w.Header().Add("Set-Cookie", "authCookie=Auth") + w.Header().Add("Set-Cookie", "authCookieNotAdded=Auth") fmt.Fprintln(w, "Success") })) t.Cleanup(server.Close) @@ -69,6 +78,9 @@ func TestForwardAuthSuccess(t *testing.T) { assert.Equal(t, []string{"group1", "group2"}, r.Header["X-Auth-Group"]) assert.Equal(t, "auth-value", r.Header.Get("Foo-Bar")) assert.Empty(t, r.Header.Get("Foo-Baz")) + w.Header().Add("Set-Cookie", "authCookie=Backend") + w.Header().Add("Set-Cookie", "backendCookie=Backend") + w.Header().Add("Other-Header", "BackendHeaderValue") fmt.Fprintln(w, "traefik") }) @@ -76,6 +88,7 @@ func TestForwardAuthSuccess(t *testing.T) { Address: server.URL, AuthResponseHeaders: []string{"X-Auth-User", "X-Auth-Group"}, AuthResponseHeadersRegex: "^Foo-", + AddAuthCookiesToResponse: []string{"authCookie"}, } middleware, err := NewForward(context.Background(), next, auth, "authTest") require.NoError(t, err) @@ -90,6 +103,8 @@ func TestForwardAuthSuccess(t *testing.T) { res, err := http.DefaultClient.Do(req) require.NoError(t, err) assert.Equal(t, http.StatusOK, res.StatusCode) + assert.Equal(t, []string{"backendCookie=Backend", "authCookie=Auth"}, res.Header["Set-Cookie"]) + assert.Equal(t, []string{"BackendHeaderValue"}, res.Header["Other-Header"]) body, err := io.ReadAll(res.Body) require.NoError(t, err) @@ -452,8 +467,8 @@ func Test_writeHeader(t *testing.T) { func TestForwardAuthUsesTracing(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Mockpfx-Ids-Traceid") == "" { - t.Errorf("expected Mockpfx-Ids-Traceid header to be present in request") + if r.Header.Get("Traceparent") == "" { + t.Errorf("expected Traceparent header to be present in request") } })) t.Cleanup(server.Close) @@ -464,15 +479,44 @@ func TestForwardAuthUsesTracing(t *testing.T) { Address: server.URL, } - tracer := mocktracer.New() - opentracing.SetGlobalTracer(tracer) + exporter := tracetest.NewInMemoryExporter() - tr, _ := tracing.NewTracing("testApp", 100, &mockBackend{tracer}) - - next, err := NewForward(context.Background(), next, auth, "authTest") + tres, err := resource.New(context.Background(), + resource.WithAttributes(semconv.ServiceNameKey.String("traefik")), + resource.WithAttributes(semconv.ServiceVersionKey.String(version.Version)), + resource.WithFromEnv(), + resource.WithTelemetrySDK(), + ) require.NoError(t, err) - next = tracingMiddleware.NewEntryPoint(context.Background(), tr, "tracingTest", next) + tracerProvider := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithResource(tres), + sdktrace.WithBatcher(exporter), + ) + otel.SetTracerProvider(tracerProvider) + + config := &static.Tracing{ + ServiceName: "testApp", + SampleRate: 1, + OTLP: &opentelemetry.Config{ + HTTP: &opentelemetry.HTTP{ + Endpoint: "http://127.0.0.1:8080", + }, + }, + } + tr, closer, err := tracing.NewTracing(config) + require.NoError(t, err) + t.Cleanup(func() { + _ = closer.Close() + }) + + next, err = NewForward(context.Background(), next, auth, "authTest") + require.NoError(t, err) + + chain := alice.New(tracingMiddleware.WrapEntryPointHandler(context.Background(), tr, "tracingTest")) + next, err = chain.Then(next) + require.NoError(t, err) ts := httptest.NewServer(next) t.Cleanup(ts.Close) @@ -482,11 +526,3 @@ func TestForwardAuthUsesTracing(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusOK, res.StatusCode) } - -type mockBackend struct { - opentracing.Tracer -} - -func (b *mockBackend) Setup(componentName string) (opentracing.Tracer, io.Closer, error) { - return b.Tracer, io.NopCloser(nil), nil -} diff --git a/pkg/middlewares/buffering/buffering.go b/pkg/middlewares/buffering/buffering.go index 6aa2ed562..ec9100a98 100644 --- a/pkg/middlewares/buffering/buffering.go +++ b/pkg/middlewares/buffering/buffering.go @@ -4,13 +4,12 @@ import ( "context" "net/http" - "github.com/opentracing/opentracing-go/ext" "github.com/rs/zerolog" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" oxybuffer "github.com/vulcand/oxy/v2/buffer" + "go.opentelemetry.io/otel/trace" ) const ( @@ -49,8 +48,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.Buffering, name }, nil } -func (b *buffer) GetTracingInformation() (string, ext.SpanKindEnum) { - return b.name, tracing.SpanKindNoneEnum +func (b *buffer) GetTracingInformation() (string, string, trace.SpanKind) { + return b.name, typeName, trace.SpanKindInternal } func (b *buffer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/capture/capture_test.go b/pkg/middlewares/capture/capture_test.go index c9c98e5a2..5c4a437cb 100644 --- a/pkg/middlewares/capture/capture_test.go +++ b/pkg/middlewares/capture/capture_test.go @@ -165,7 +165,7 @@ func runBenchmark(b *testing.B, size int, req *http.Request, handler http.Handle b.Fatalf("Expected 200 but got %d", code) } - assert.Equal(b, size, len(recorder.Body.String())) + assert.Len(b, recorder.Body.String(), size) } func generateBytes(length int) []byte { diff --git a/pkg/middlewares/circuitbreaker/circuit_breaker.go b/pkg/middlewares/circuitbreaker/circuit_breaker.go index 8445b45b8..3fa06ba23 100644 --- a/pkg/middlewares/circuitbreaker/circuit_breaker.go +++ b/pkg/middlewares/circuitbreaker/circuit_breaker.go @@ -5,7 +5,6 @@ import ( "net/http" "time" - "github.com/opentracing/opentracing-go/ext" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" @@ -13,6 +12,7 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/tracing" "github.com/vulcand/oxy/v2/cbreaker" + "go.opentelemetry.io/otel/trace" ) const typeName = "CircuitBreaker" @@ -32,7 +32,7 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ cbOpts := []cbreaker.Option{ cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - tracing.SetErrorWithEvent(req, "blocked by circuit-breaker (%q)", expression) + tracing.SetStatusErrorf(req.Context(), "blocked by circuit-breaker (%q)", expression) rw.WriteHeader(http.StatusServiceUnavailable) if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil { @@ -66,8 +66,8 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ }, nil } -func (c *circuitBreaker) GetTracingInformation() (string, ext.SpanKindEnum) { - return c.name, tracing.SpanKindNoneEnum +func (c *circuitBreaker) GetTracingInformation() (string, string, trace.SpanKind) { + return c.name, typeName, trace.SpanKindInternal } func (c *circuitBreaker) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/compress/brotli/brotli.go b/pkg/middlewares/compress/brotli/brotli.go index ebc93b5b3..c95a5ff66 100644 --- a/pkg/middlewares/compress/brotli/brotli.go +++ b/pkg/middlewares/compress/brotli/brotli.go @@ -22,7 +22,11 @@ const ( // Config is the Brotli handler configuration. type Config struct { // ExcludedContentTypes is the list of content types for which we should not compress. + // Mutually exclusive with the IncludedContentTypes option. ExcludedContentTypes []string + // IncludedContentTypes is the list of content types for which compression should be exclusively enabled. + // Mutually exclusive with the ExcludedContentTypes option. + IncludedContentTypes []string // MinSize is the minimum size (in bytes) required to enable compression. MinSize int } @@ -33,14 +37,28 @@ func NewWrapper(cfg Config) (func(http.Handler) http.HandlerFunc, error) { return nil, fmt.Errorf("minimum size must be greater than or equal to zero") } - var contentTypes []parsedContentType + if len(cfg.ExcludedContentTypes) > 0 && len(cfg.IncludedContentTypes) > 0 { + return nil, fmt.Errorf("excludedContentTypes and includedContentTypes options are mutually exclusive") + } + + var excludedContentTypes []parsedContentType for _, v := range cfg.ExcludedContentTypes { mediaType, params, err := mime.ParseMediaType(v) if err != nil { - return nil, fmt.Errorf("parsing media type: %w", err) + return nil, fmt.Errorf("parsing excluded media type: %w", err) } - contentTypes = append(contentTypes, parsedContentType{mediaType, params}) + excludedContentTypes = append(excludedContentTypes, parsedContentType{mediaType, params}) + } + + var includedContentTypes []parsedContentType + for _, v := range cfg.IncludedContentTypes { + mediaType, params, err := mime.ParseMediaType(v) + if err != nil { + return nil, fmt.Errorf("parsing included media type: %w", err) + } + + includedContentTypes = append(includedContentTypes, parsedContentType{mediaType, params}) } return func(h http.Handler) http.HandlerFunc { @@ -52,7 +70,8 @@ func NewWrapper(cfg Config) (func(http.Handler) http.HandlerFunc, error) { bw: brotli.NewWriter(rw), minSize: cfg.MinSize, statusCode: http.StatusOK, - excludedContentTypes: contentTypes, + excludedContentTypes: excludedContentTypes, + includedContentTypes: includedContentTypes, } defer brw.close() @@ -69,6 +88,7 @@ type responseWriter struct { minSize int excludedContentTypes []parsedContentType + includedContentTypes []parsedContentType buf []byte hijacked bool @@ -121,11 +141,25 @@ func (r *responseWriter) Write(p []byte) (int, error) { return r.rw.Write(p) } - // Disable compression according to user wishes in excludedContentTypes. + // Disable compression according to user wishes in excludedContentTypes or includedContentTypes. if ct := r.rw.Header().Get(contentType); ct != "" { mediaType, params, err := mime.ParseMediaType(ct) if err != nil { - return 0, fmt.Errorf("parsing media type: %w", err) + return 0, fmt.Errorf("parsing content-type media type: %w", err) + } + + if len(r.includedContentTypes) > 0 { + var found bool + for _, includedContentType := range r.includedContentTypes { + if includedContentType.equals(mediaType, params) { + found = true + break + } + } + if !found { + r.compressionDisabled = true + return r.rw.Write(p) + } } for _, excludedContentType := range r.excludedContentTypes { diff --git a/pkg/middlewares/compress/brotli/brotli_test.go b/pkg/middlewares/compress/brotli/brotli_test.go index 373fe71ca..5e1bfeea7 100644 --- a/pkg/middlewares/compress/brotli/brotli_test.go +++ b/pkg/middlewares/compress/brotli/brotli_test.go @@ -291,10 +291,9 @@ func Test_ExcludedContentTypes(t *testing.T) { expCompression bool }{ { - desc: "Always compress when content types are empty", - contentType: "", - excludedContentTypes: []string{}, - expCompression: true, + desc: "Always compress when content types are empty", + contentType: "", + expCompression: true, }, { desc: "MIME match", @@ -376,13 +375,118 @@ func Test_ExcludedContentTypes(t *testing.T) { assert.Equal(t, "br", rw.Header().Get(contentEncoding)) got, err := io.ReadAll(brotli.NewReader(rw.Body)) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, bigTestBody, got) } else { assert.NotEqual(t, "br", rw.Header().Get("Content-Encoding")) got, err := io.ReadAll(rw.Body) - assert.Nil(t, err) + assert.NoError(t, err) + assert.Equal(t, bigTestBody, got) + } + }) + } +} + +func Test_IncludedContentTypes(t *testing.T) { + testCases := []struct { + desc string + contentType string + includedContentTypes []string + expCompression bool + }{ + { + desc: "Always compress when content types are empty", + contentType: "", + expCompression: true, + }, + { + desc: "MIME match", + contentType: "application/json", + includedContentTypes: []string{"application/json"}, + expCompression: true, + }, + { + desc: "MIME no match", + contentType: "text/xml", + includedContentTypes: []string{"application/json"}, + expCompression: false, + }, + { + desc: "MIME match with no other directive ignores non-MIME directives", + contentType: "application/json; charset=utf-8", + includedContentTypes: []string{"application/json"}, + expCompression: true, + }, + { + desc: "MIME match with other directives requires all directives be equal, different charset", + contentType: "application/json; charset=ascii", + includedContentTypes: []string{"application/json; charset=utf-8"}, + expCompression: false, + }, + { + desc: "MIME match with other directives requires all directives be equal, same charset", + contentType: "application/json; charset=utf-8", + includedContentTypes: []string{"application/json; charset=utf-8"}, + expCompression: true, + }, + { + desc: "MIME match with other directives requires all directives be equal, missing charset", + contentType: "application/json", + includedContentTypes: []string{"application/json; charset=ascii"}, + expCompression: false, + }, + { + desc: "MIME match case insensitive", + contentType: "Application/Json", + includedContentTypes: []string{"application/json"}, + expCompression: true, + }, + { + desc: "MIME match ignore whitespace", + contentType: "application/json;charset=utf-8", + includedContentTypes: []string{"application/json; charset=utf-8"}, + expCompression: true, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + cfg := Config{ + MinSize: 1024, + IncludedContentTypes: test.includedContentTypes, + } + h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set(contentType, test.contentType) + + rw.WriteHeader(http.StatusOK) + + _, err := rw.Write(bigTestBody) + require.NoError(t, err) + })) + + req, _ := http.NewRequest(http.MethodGet, "/whatever", nil) + req.Header.Set(acceptEncoding, "br") + + rw := httptest.NewRecorder() + h.ServeHTTP(rw, req) + + assert.Equal(t, http.StatusOK, rw.Code) + + if test.expCompression { + assert.Equal(t, "br", rw.Header().Get(contentEncoding)) + + got, err := io.ReadAll(brotli.NewReader(rw.Body)) + assert.NoError(t, err) + assert.Equal(t, bigTestBody, got) + } else { + assert.NotEqual(t, "br", rw.Header().Get("Content-Encoding")) + + got, err := io.ReadAll(rw.Body) + assert.NoError(t, err) assert.Equal(t, bigTestBody, got) } }) @@ -397,10 +501,9 @@ func Test_FlushExcludedContentTypes(t *testing.T) { expCompression bool }{ { - desc: "Always compress when content types are empty", - contentType: "", - excludedContentTypes: []string{}, - expCompression: true, + desc: "Always compress when content types are empty", + contentType: "", + expCompression: true, }, { desc: "MIME match", @@ -496,13 +599,132 @@ func Test_FlushExcludedContentTypes(t *testing.T) { assert.Equal(t, "br", rw.Header().Get(contentEncoding)) got, err := io.ReadAll(brotli.NewReader(rw.Body)) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, bigTestBody, got) } else { assert.NotEqual(t, "br", rw.Header().Get(contentEncoding)) got, err := io.ReadAll(rw.Body) - assert.Nil(t, err) + assert.NoError(t, err) + assert.Equal(t, bigTestBody, got) + } + }) + } +} + +func Test_FlushIncludedContentTypes(t *testing.T) { + testCases := []struct { + desc string + contentType string + includedContentTypes []string + expCompression bool + }{ + { + desc: "Always compress when content types are empty", + contentType: "", + expCompression: true, + }, + { + desc: "MIME match", + contentType: "application/json", + includedContentTypes: []string{"application/json"}, + expCompression: true, + }, + { + desc: "MIME no match", + contentType: "text/xml", + includedContentTypes: []string{"application/json"}, + expCompression: false, + }, + { + desc: "MIME match with no other directive ignores non-MIME directives", + contentType: "application/json; charset=utf-8", + includedContentTypes: []string{"application/json"}, + expCompression: true, + }, + { + desc: "MIME match with other directives requires all directives be equal, different charset", + contentType: "application/json; charset=ascii", + includedContentTypes: []string{"application/json; charset=utf-8"}, + expCompression: false, + }, + { + desc: "MIME match with other directives requires all directives be equal, same charset", + contentType: "application/json; charset=utf-8", + includedContentTypes: []string{"application/json; charset=utf-8"}, + expCompression: true, + }, + { + desc: "MIME match with other directives requires all directives be equal, missing charset", + contentType: "application/json", + includedContentTypes: []string{"application/json; charset=ascii"}, + expCompression: false, + }, + { + desc: "MIME match case insensitive", + contentType: "Application/Json", + includedContentTypes: []string{"application/json"}, + expCompression: true, + }, + { + desc: "MIME match ignore whitespace", + contentType: "application/json;charset=utf-8", + includedContentTypes: []string{"application/json; charset=utf-8"}, + expCompression: true, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + cfg := Config{ + MinSize: 1024, + IncludedContentTypes: test.includedContentTypes, + } + h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set(contentType, test.contentType) + rw.WriteHeader(http.StatusOK) + + tb := bigTestBody + for len(tb) > 0 { + // Write 100 bytes per run + // Detection should not be affected (we send 100 bytes) + toWrite := 100 + if toWrite > len(tb) { + toWrite = len(tb) + } + + _, err := rw.Write(tb[:toWrite]) + require.NoError(t, err) + + // Flush between each write + rw.(http.Flusher).Flush() + tb = tb[toWrite:] + } + })) + + req, _ := http.NewRequest(http.MethodGet, "/whatever", nil) + req.Header.Set(acceptEncoding, "br") + + // This doesn't allow checking flushes, but we validate if content is correct. + rw := httptest.NewRecorder() + h.ServeHTTP(rw, req) + + assert.Equal(t, http.StatusOK, rw.Code) + + if test.expCompression { + assert.Equal(t, "br", rw.Header().Get(contentEncoding)) + + got, err := io.ReadAll(brotli.NewReader(rw.Body)) + assert.NoError(t, err) + assert.Equal(t, bigTestBody, got) + } else { + assert.NotEqual(t, "br", rw.Header().Get(contentEncoding)) + + got, err := io.ReadAll(rw.Body) + assert.NoError(t, err) assert.Equal(t, bigTestBody, got) } }) diff --git a/pkg/middlewares/compress/compress.go b/pkg/middlewares/compress/compress.go index ab9bdc71b..0cdd29883 100644 --- a/pkg/middlewares/compress/compress.go +++ b/pkg/middlewares/compress/compress.go @@ -5,14 +5,14 @@ import ( "fmt" "mime" "net/http" + "slices" "strings" "github.com/klauspost/compress/gzhttp" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/compress/brotli" - "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const typeName = "Compress" @@ -26,6 +26,7 @@ type compress struct { next http.Handler name string excludes []string + includes []string minSize int brotliHandler http.Handler @@ -36,16 +37,30 @@ type compress struct { func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name string) (http.Handler, error) { middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware") + if len(conf.ExcludedContentTypes) > 0 && len(conf.IncludedContentTypes) > 0 { + return nil, fmt.Errorf("excludedContentTypes and includedContentTypes options are mutually exclusive") + } + excludes := []string{"application/grpc"} for _, v := range conf.ExcludedContentTypes { mediaType, _, err := mime.ParseMediaType(v) if err != nil { - return nil, err + return nil, fmt.Errorf("parsing excluded media type: %w", err) } excludes = append(excludes, mediaType) } + var includes []string + for _, v := range conf.IncludedContentTypes { + mediaType, _, err := mime.ParseMediaType(v) + if err != nil { + return nil, fmt.Errorf("parsing included media type: %w", err) + } + + includes = append(includes, mediaType) + } + minSize := DefaultMinSize if conf.MinResponseBodyBytes > 0 { minSize = conf.MinResponseBodyBytes @@ -55,6 +70,7 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str next: next, name: name, excludes: excludes, + includes: includes, minSize: minSize, } @@ -87,16 +103,16 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // Notably for text/event-stream requests the response should not be compressed. // See https://github.com/traefik/traefik/issues/2576 - if contains(c.excludes, mediaType) { + if slices.Contains(c.excludes, mediaType) { c.next.ServeHTTP(rw, req) return } - // Client allows us to do whatever we want, so we br compress. - // See https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.3 + // 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"] if !ok { - c.brotliHandler.ServeHTTP(rw, req) + c.next.ServeHTTP(rw, req) return } @@ -113,15 +129,26 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) { c.next.ServeHTTP(rw, req) } -func (c *compress) GetTracingInformation() (string, ext.SpanKindEnum) { - return c.name, tracing.SpanKindNoneEnum +func (c *compress) GetTracingInformation() (string, string, trace.SpanKind) { + return c.name, typeName, trace.SpanKindInternal } func (c *compress) newGzipHandler() (http.Handler, error) { - wrapper, err := gzhttp.NewWrapper( - gzhttp.ExceptContentTypes(c.excludes), - gzhttp.MinSize(c.minSize), - ) + var wrapper func(http.Handler) http.HandlerFunc + var err error + + if len(c.includes) > 0 { + wrapper, err = gzhttp.NewWrapper( + gzhttp.ContentTypes(c.includes), + gzhttp.MinSize(c.minSize), + ) + } else { + wrapper, err = gzhttp.NewWrapper( + gzhttp.ExceptContentTypes(c.excludes), + gzhttp.MinSize(c.minSize), + ) + } + if err != nil { return nil, fmt.Errorf("new gzip wrapper: %w", err) } @@ -130,9 +157,11 @@ func (c *compress) newGzipHandler() (http.Handler, error) { } func (c *compress) newBrotliHandler() (http.Handler, error) { - cfg := brotli.Config{ - ExcludedContentTypes: c.excludes, - MinSize: c.minSize, + cfg := brotli.Config{MinSize: c.minSize} + if len(c.includes) > 0 { + cfg.IncludedContentTypes = c.includes + } else { + cfg.ExcludedContentTypes = c.excludes } wrapper, err := brotli.NewWrapper(cfg) @@ -158,13 +187,3 @@ func encodingAccepts(acceptEncoding []string, typ string) bool { return false } - -func contains(values []string, val string) bool { - for _, v := range values { - if v == val { - return true - } - } - - return false -} diff --git a/pkg/middlewares/compress/compress_test.go b/pkg/middlewares/compress/compress_test.go index ed637030c..722001da3 100644 --- a/pkg/middlewares/compress/compress_test.go +++ b/pkg/middlewares/compress/compress_test.go @@ -10,7 +10,6 @@ import ( "net/textproto" "testing" - "github.com/andybalholm/brotli" "github.com/klauspost/compress/gzhttp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,7 +34,7 @@ func TestNegotiation(t *testing.T) { }{ { desc: "no accept header", - expEncoding: "br", + expEncoding: "", }, { desc: "unsupported accept header", @@ -151,7 +150,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) { assert.EqualValues(t, rw.Body.Bytes(), fakeCompressedBody) } -func TestShouldCompressWhenNoAcceptEncodingHeader(t *testing.T) { +func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) { req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) fakeBody := generateBytes(gzhttp.DefaultMinSize) @@ -167,12 +166,9 @@ func TestShouldCompressWhenNoAcceptEncodingHeader(t *testing.T) { rw := httptest.NewRecorder() handler.ServeHTTP(rw, req) - assert.Equal(t, brotliValue, rw.Header().Get(contentEncodingHeader)) - assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader)) - - got, err := io.ReadAll(brotli.NewReader(rw.Body)) - require.NoError(t, err) - assert.Equal(t, got, fakeBody) + assert.Empty(t, rw.Header().Get(contentEncodingHeader)) + assert.Empty(t, rw.Header().Get(varyHeader)) + assert.EqualValues(t, rw.Body.Bytes(), fakeBody) } func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) { @@ -275,7 +271,28 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) { respContentType: "text/event-stream", }, { - desc: "application/grpc", + desc: "Include Response Content-Type", + conf: dynamic.Compress{ + IncludedContentTypes: []string{"text/plain"}, + }, + respContentType: "text/html", + }, + { + desc: "Ignoring application/grpc with exclude option", + conf: dynamic.Compress{ + ExcludedContentTypes: []string{"application/json"}, + }, + reqContentType: "application/grpc", + }, + { + desc: "Ignoring application/grpc with include option", + conf: dynamic.Compress{ + IncludedContentTypes: []string{"application/json"}, + }, + reqContentType: "application/grpc", + }, + { + desc: "Ignoring application/grpc with no option", conf: dynamic.Compress{}, reqContentType: "application/grpc", }, @@ -316,6 +333,52 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) { } } +func TestShouldCompressWhenSpecificContentType(t *testing.T) { + baseBody := generateBytes(gzhttp.DefaultMinSize) + + testCases := []struct { + desc string + conf dynamic.Compress + respContentType string + }{ + { + desc: "Include Response Content-Type", + conf: dynamic.Compress{ + IncludedContentTypes: []string{"text/html"}, + }, + respContentType: "text/html", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) + req.Header.Add(acceptEncodingHeader, gzipValue) + + next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.Header().Set(contentTypeHeader, test.respContentType) + + if _, err := rw.Write(baseBody); err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + } + }) + + handler, err := New(context.Background(), next, test.conf, "test") + require.NoError(t, err) + + rw := httptest.NewRecorder() + handler.ServeHTTP(rw, req) + + assert.Equal(t, gzipValue, rw.Header().Get(contentEncodingHeader)) + assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader)) + assert.NotEqualValues(t, rw.Body.Bytes(), baseBody) + }) + } +} + func TestIntegrationShouldNotCompress(t *testing.T) { fakeCompressedBody := generateBytes(100000) @@ -582,7 +645,7 @@ func Test1xxResponses(t *testing.T) { req.Header.Add(acceptEncodingHeader, gzipValue) res, err := frontendClient.Do(req) - assert.Nil(t, err) + assert.NoError(t, err) defer res.Body.Close() diff --git a/pkg/middlewares/connectionheader/connectionheader.go b/pkg/middlewares/connectionheader/connectionheader.go index b7a64910a..12a994f17 100644 --- a/pkg/middlewares/connectionheader/connectionheader.go +++ b/pkg/middlewares/connectionheader/connectionheader.go @@ -17,24 +17,29 @@ const ( // See RFC 7230, section 6.1. func Remover(next http.Handler) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { - var reqUpType string - if httpguts.HeaderValuesContainsToken(req.Header[connectionHeader], upgradeHeader) { - reqUpType = req.Header.Get(upgradeHeader) - } - - removeConnectionHeaders(req.Header) - - if reqUpType != "" { - req.Header.Set(connectionHeader, upgradeHeader) - req.Header.Set(upgradeHeader, reqUpType) - } else { - req.Header.Del(connectionHeader) - } - - next.ServeHTTP(rw, req) + next.ServeHTTP(rw, Remove(req)) } } +// Remove removes hop-by-hop header on the request. +func Remove(req *http.Request) *http.Request { + var reqUpType string + if httpguts.HeaderValuesContainsToken(req.Header[connectionHeader], upgradeHeader) { + reqUpType = req.Header.Get(upgradeHeader) + } + + removeConnectionHeaders(req.Header) + + if reqUpType != "" { + req.Header.Set(connectionHeader, upgradeHeader) + req.Header.Set(upgradeHeader, reqUpType) + } else { + req.Header.Del(connectionHeader) + } + + return req +} + func removeConnectionHeaders(h http.Header) { for _, f := range h[connectionHeader] { for _, sf := range strings.Split(f, ",") { diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index 2d71e751d..dd1f77e33 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -10,12 +10,12 @@ import ( "strconv" "strings" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/types" "github.com/vulcand/oxy/v2/utils" + "go.opentelemetry.io/otel/trace" ) // Compile time validation that the response recorder implements http interfaces correctly. @@ -24,7 +24,7 @@ var ( _ middlewares.Stateful = &codeCatcher{} ) -const typeName = "customError" +const typeName = "CustomError" type serviceBuilder interface { BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) @@ -62,8 +62,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi }, nil } -func (c *customErrors) GetTracingInformation() (string, ext.SpanKindEnum) { - return c.name, tracing.SpanKindNoneEnum +func (c *customErrors) GetTracingInformation() (string, string, trace.SpanKind) { + return c.name, typeName, trace.SpanKindInternal } func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) { @@ -71,7 +71,7 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if c.backendHandler == nil { logger.Error().Msg("Error pages: no backend handler.") - tracing.SetErrorWithEvent(req, "Error pages: no backend handler.") + tracing.SetStatusErrorf(req.Context(), "Error pages: no backend handler.") c.next.ServeHTTP(rw, req) return } @@ -96,12 +96,12 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) { pageReq, err := newRequest("http://" + req.Host + query) if err != nil { logger.Error().Err(err).Send() + tracing.SetStatusErrorf(req.Context(), err.Error()) http.Error(rw, http.StatusText(code), code) return } utils.CopyHeaders(pageReq.Header, req.Header) - c.backendHandler.ServeHTTP(newCodeModifier(rw, code), pageReq.WithContext(req.Context())) } diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go index 2e2020ef2..8a6c65882 100644 --- a/pkg/middlewares/customerrors/custom_errors_test.go +++ b/pkg/middlewares/customerrors/custom_errors_test.go @@ -253,7 +253,7 @@ func Test1xxResponses(t *testing.T) { req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) - assert.Nil(t, err) + assert.NoError(t, err) defer res.Body.Close() diff --git a/pkg/middlewares/denyrouterrecursion/deny_router_recursion.go b/pkg/middlewares/denyrouterrecursion/deny_router_recursion.go new file mode 100644 index 000000000..34c6cd12d --- /dev/null +++ b/pkg/middlewares/denyrouterrecursion/deny_router_recursion.go @@ -0,0 +1,64 @@ +package denyrouterrecursion + +import ( + "errors" + "hash/fnv" + "net/http" + "strconv" + + "github.com/containous/alice" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/logs" +) + +const xTraefikRouter = "X-Traefik-Router" + +type DenyRouterRecursion struct { + routerName string + routerNameHash string + next http.Handler +} + +// WrapHandler Wraps router to alice.Constructor. +func WrapHandler(routerName string) alice.Constructor { + return func(next http.Handler) (http.Handler, error) { + return New(routerName, next) + } +} + +// New creates a new DenyRouterRecursion. +// DenyRouterRecursion middleware is an internal middleware used to avoid infinite requests loop on the same router. +func New(routerName string, next http.Handler) (*DenyRouterRecursion, error) { + if routerName == "" { + return nil, errors.New("routerName cannot be empty") + } + + return &DenyRouterRecursion{ + routerName: routerName, + routerNameHash: makeHash(routerName), + next: next, + }, nil +} + +// ServeHTTP implements http.Handler. +func (l *DenyRouterRecursion) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if req.Header.Get(xTraefikRouter) == l.routerNameHash { + logger := log.With().Str(logs.MiddlewareType, "DenyRouterRecursion").Logger() + logger.Debug().Msgf("Rejecting request in provenance of the same router (%q) to stop potential infinite loop.", l.routerName) + + rw.WriteHeader(http.StatusBadRequest) + + return + } + + req.Header.Set(xTraefikRouter, l.routerNameHash) + + l.next.ServeHTTP(rw, req) +} + +func makeHash(routerName string) string { + hasher := fnv.New64() + // purposely ignoring the error, as no error can be returned from the implementation. + _, _ = hasher.Write([]byte(routerName)) + return strconv.FormatUint(hasher.Sum64(), 16) +} diff --git a/pkg/middlewares/denyrouterrecursion/deny_router_recursion_test.go b/pkg/middlewares/denyrouterrecursion/deny_router_recursion_test.go new file mode 100644 index 000000000..c93653db7 --- /dev/null +++ b/pkg/middlewares/denyrouterrecursion/deny_router_recursion_test.go @@ -0,0 +1,38 @@ +package denyrouterrecursion + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestServeHTTP(t *testing.T) { + req, err := http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + + _, err = New("", http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) + require.Error(t, err) + + next := 0 + m, err := New("myRouter", http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + next++ + })) + require.NoError(t, err) + + recorder := httptest.NewRecorder() + m.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, "995d26092d19a224", m.routerNameHash) + assert.Equal(t, m.routerNameHash, req.Header.Get(xTraefikRouter)) + assert.Equal(t, 1, next) + + recorder = httptest.NewRecorder() + m.ServeHTTP(recorder, req) + + assert.Equal(t, 1, next) + assert.Equal(t, http.StatusBadRequest, recorder.Code) +} diff --git a/pkg/middlewares/grpcweb/grpcweb.go b/pkg/middlewares/grpcweb/grpcweb.go index 896ac7148..1d32b5273 100644 --- a/pkg/middlewares/grpcweb/grpcweb.go +++ b/pkg/middlewares/grpcweb/grpcweb.go @@ -4,12 +4,12 @@ import ( "context" "net/http" - "github.com/improbable-eng/grpc-web/go/grpcweb" + "github.com/traefik/grpc-web/go/grpcweb" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" ) -const typeName = "grpc-web" +const typeName = "GRPCWeb" // New builds a new gRPC web request converter. func New(ctx context.Context, next http.Handler, config dynamic.GrpcWeb, name string) http.Handler { diff --git a/pkg/middlewares/headers/header.go b/pkg/middlewares/headers/header.go index a62fc097e..000cf4977 100644 --- a/pkg/middlewares/headers/header.go +++ b/pkg/middlewares/headers/header.go @@ -8,6 +8,8 @@ import ( "strings" "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/vulcand/oxy/v2/forward" ) // Header is a middleware that helps setup a few basic security features. @@ -47,6 +49,7 @@ func NewHeader(next http.Handler, cfg dynamic.Headers) (*Header, error) { func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // Handle Cors headers and preflight if configured. if isPreflight := s.processCorsHeaders(rw, req); isPreflight { + rw.WriteHeader(http.StatusOK) return } @@ -56,7 +59,7 @@ func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // If there is a next, call it. if s.next != nil { - s.next.ServeHTTP(newResponseModifier(rw, req, s.PostRequestModifyResponseHeaders), req) + s.next.ServeHTTP(middlewares.NewResponseModifier(rw, req, s.PostRequestModifyResponseHeaders), req) } } @@ -65,6 +68,10 @@ func (s *Header) modifyCustomRequestHeaders(req *http.Request) { // Loop through Custom request headers for header, value := range s.headers.CustomRequestHeaders { switch { + // Handling https://github.com/golang/go/commit/ecdbffd4ec68b509998792f120868fec319de59b. + case value == "" && header == forward.XForwardedFor: + req.Header[header] = nil + case value == "": req.Header.Del(header) diff --git a/pkg/middlewares/headers/header_test.go b/pkg/middlewares/headers/header_test.go index 269756b46..b10a748e7 100644 --- a/pkg/middlewares/headers/header_test.go +++ b/pkg/middlewares/headers/header_test.go @@ -29,11 +29,14 @@ func TestNewHeader_customRequestHeader(t *testing.T) { desc: "delete a header", cfg: dynamic.Headers{ CustomRequestHeaders: map[string]string{ + "X-Forwarded-For": "", "X-Custom-Request-Header": "", "Foo": "", }, }, - expected: http.Header{}, + expected: http.Header{ + "X-Forwarded-For": nil, + }, }, { desc: "override a header", diff --git a/pkg/middlewares/headers/headers.go b/pkg/middlewares/headers/headers.go index 1210f6711..e393aa1a6 100644 --- a/pkg/middlewares/headers/headers.go +++ b/pkg/middlewares/headers/headers.go @@ -6,11 +6,10 @@ import ( "errors" "net/http" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/connectionheader" - "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const ( @@ -61,8 +60,8 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin }, nil } -func (h *headers) GetTracingInformation() (string, ext.SpanKindEnum) { - return h.name, tracing.SpanKindNoneEnum +func (h *headers) GetTracingInformation() (string, string, trace.SpanKind) { + return h.name, typeName, trace.SpanKindInternal } func (h *headers) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/headers/headers_test.go b/pkg/middlewares/headers/headers_test.go index 00726c04c..8e365ab54 100644 --- a/pkg/middlewares/headers/headers_test.go +++ b/pkg/middlewares/headers/headers_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) func TestNew_withoutOptions(t *testing.T) { @@ -109,10 +109,11 @@ func Test_headers_getTracingInformation(t *testing.T) { name: "testing", } - name, trace := mid.GetTracingInformation() + name, typeName, spanKind := mid.GetTracingInformation() assert.Equal(t, "testing", name) - assert.Equal(t, tracing.SpanKindNoneEnum, trace) + assert.Equal(t, "Headers", typeName) + assert.Equal(t, trace.SpanKindInternal, spanKind) } // This test is an adapted version of net/http/httputil.Test1xxResponses test. @@ -182,7 +183,7 @@ func Test1xxResponses(t *testing.T) { req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) - assert.Nil(t, err) + assert.NoError(t, err) defer res.Body.Close() diff --git a/pkg/middlewares/headers/secure.go b/pkg/middlewares/headers/secure.go index 99b03a489..1766e6356 100644 --- a/pkg/middlewares/headers/secure.go +++ b/pkg/middlewares/headers/secure.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/unrolled/secure" ) @@ -45,6 +46,6 @@ func newSecure(next http.Handler, cfg dynamic.Headers, contextKey string) *secur func (s secureHeader) ServeHTTP(rw http.ResponseWriter, req *http.Request) { s.secure.HandlerFuncWithNextForRequestOnly(rw, req, func(writer http.ResponseWriter, request *http.Request) { - s.next.ServeHTTP(newResponseModifier(writer, request, s.secure.ModifyResponseHeaders), request) + s.next.ServeHTTP(middlewares.NewResponseModifier(writer, request, s.secure.ModifyResponseHeaders), request) }) } diff --git a/pkg/middlewares/inflightreq/inflight_req.go b/pkg/middlewares/inflightreq/inflight_req.go index a6fcdf101..c05193607 100644 --- a/pkg/middlewares/inflightreq/inflight_req.go +++ b/pkg/middlewares/inflightreq/inflight_req.go @@ -5,13 +5,12 @@ import ( "fmt" "net/http" - "github.com/opentracing/opentracing-go/ext" "github.com/rs/zerolog" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" "github.com/vulcand/oxy/v2/connlimit" + "go.opentelemetry.io/otel/trace" ) const ( @@ -54,8 +53,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.InFlightReq, nam return &inFlightReq{handler: handler, name: name}, nil } -func (i *inFlightReq) GetTracingInformation() (string, ext.SpanKindEnum) { - return i.name, tracing.SpanKindNoneEnum +func (i *inFlightReq) GetTracingInformation() (string, string, trace.SpanKind) { + return i.name, typeName, trace.SpanKindInternal } func (i *inFlightReq) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/ipallowlist/ip_allowlist.go b/pkg/middlewares/ipallowlist/ip_allowlist.go index e888d5d92..3841f3f23 100644 --- a/pkg/middlewares/ipallowlist/ip_allowlist.go +++ b/pkg/middlewares/ipallowlist/ip_allowlist.go @@ -6,12 +6,12 @@ import ( "fmt" "net/http" - "github.com/opentracing/opentracing-go/ext" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/ip" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const ( @@ -20,10 +20,11 @@ const ( // ipAllowLister is a middleware that provides Checks of the Requesting IP against a set of Allowlists. type ipAllowLister struct { - next http.Handler - allowLister *ip.Checker - strategy ip.Strategy - name string + next http.Handler + allowLister *ip.Checker + strategy ip.Strategy + name string + rejectStatusCode int } // New builds a new IPAllowLister given a list of CIDR-Strings to allow. @@ -35,6 +36,14 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPAllowList, nam return nil, errors.New("sourceRange is empty, IPAllowLister not created") } + rejectStatusCode := config.RejectStatusCode + // If RejectStatusCode is not given, default to Forbidden (403). + if rejectStatusCode == 0 { + rejectStatusCode = http.StatusForbidden + } else if http.StatusText(rejectStatusCode) == "" { + return nil, fmt.Errorf("invalid HTTP status code %d", rejectStatusCode) + } + checker, err := ip.NewChecker(config.SourceRange) if err != nil { return nil, fmt.Errorf("cannot parse CIDRs %s: %w", config.SourceRange, err) @@ -48,15 +57,16 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPAllowList, nam logger.Debug().Msgf("Setting up IPAllowLister with sourceRange: %s", config.SourceRange) return &ipAllowLister{ - strategy: strategy, - allowLister: checker, - next: next, - name: name, + strategy: strategy, + allowLister: checker, + next: next, + name: name, + rejectStatusCode: rejectStatusCode, }, nil } -func (al *ipAllowLister) GetTracingInformation() (string, ext.SpanKindEnum) { - return al.name, tracing.SpanKindNoneEnum +func (al *ipAllowLister) GetTracingInformation() (string, string, trace.SpanKind) { + return al.name, typeName, trace.SpanKindInternal } func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) { @@ -68,8 +78,8 @@ func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if err != nil { msg := fmt.Sprintf("Rejecting IP %s: %v", clientIP, err) logger.Debug().Msg(msg) - tracing.SetErrorWithEvent(req, msg) - reject(ctx, rw) + tracing.SetStatusErrorf(req.Context(), msg) + reject(ctx, al.rejectStatusCode, rw) return } logger.Debug().Msgf("Accepting IP %s", clientIP) @@ -77,9 +87,7 @@ func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) { al.next.ServeHTTP(rw, req) } -func reject(ctx context.Context, rw http.ResponseWriter) { - statusCode := http.StatusForbidden - +func reject(ctx context.Context, statusCode int, rw http.ResponseWriter) { rw.WriteHeader(statusCode) _, err := rw.Write([]byte(http.StatusText(statusCode))) if err != nil { diff --git a/pkg/middlewares/ipallowlist/ip_allowlist_test.go b/pkg/middlewares/ipallowlist/ip_allowlist_test.go index 8bd26b3eb..cea46e401 100644 --- a/pkg/middlewares/ipallowlist/ip_allowlist_test.go +++ b/pkg/middlewares/ipallowlist/ip_allowlist_test.go @@ -30,6 +30,14 @@ func TestNewIPAllowLister(t *testing.T) { SourceRange: []string{"10.10.10.10"}, }, }, + { + desc: "invalid HTTP status code", + allowList: dynamic.IPAllowList{ + SourceRange: []string{"10.10.10.10"}, + RejectStatusCode: 600, + }, + expectedError: true, + }, } for _, test := range testCases { @@ -73,6 +81,24 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) { remoteAddr: "20.20.20.21:1234", expected: 403, }, + { + desc: "authorized with remote address, reject 404", + allowList: dynamic.IPAllowList{ + SourceRange: []string{"20.20.20.20"}, + RejectStatusCode: 404, + }, + remoteAddr: "20.20.20.20:1234", + expected: 200, + }, + { + desc: "non authorized with remote address, reject 404", + allowList: dynamic.IPAllowList{ + SourceRange: []string{"20.20.20.20"}, + RejectStatusCode: 404, + }, + remoteAddr: "20.20.20.21:1234", + expected: 404, + }, } for _, test := range testCases { diff --git a/pkg/middlewares/ipwhitelist/ip_whitelist.go b/pkg/middlewares/ipwhitelist/ip_whitelist.go new file mode 100644 index 000000000..937bde420 --- /dev/null +++ b/pkg/middlewares/ipwhitelist/ip_whitelist.go @@ -0,0 +1,88 @@ +package ipwhitelist + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/ip" + "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +const ( + typeName = "IPWhiteLister" +) + +// ipWhiteLister is a middleware that provides Checks of the Requesting IP against a set of Whitelists. +type ipWhiteLister struct { + next http.Handler + whiteLister *ip.Checker + strategy ip.Strategy + name string +} + +// New builds a new IPWhiteLister given a list of CIDR-Strings to whitelist. +func New(ctx context.Context, next http.Handler, config dynamic.IPWhiteList, name string) (http.Handler, error) { + logger := middlewares.GetLogger(ctx, name, typeName) + logger.Debug().Msg("Creating middleware") + + if len(config.SourceRange) == 0 { + return nil, errors.New("sourceRange is empty, IPWhiteLister not created") + } + + checker, err := ip.NewChecker(config.SourceRange) + if err != nil { + return nil, fmt.Errorf("cannot parse CIDR whitelist %s: %w", config.SourceRange, err) + } + + strategy, err := config.IPStrategy.Get() + if err != nil { + return nil, err + } + + logger.Debug().Msgf("Setting up IPWhiteLister with sourceRange: %s", config.SourceRange) + + return &ipWhiteLister{ + strategy: strategy, + whiteLister: checker, + next: next, + name: name, + }, nil +} + +func (wl *ipWhiteLister) GetTracingInformation() (string, string, trace.SpanKind) { + return wl.name, typeName, trace.SpanKindInternal +} + +func (wl *ipWhiteLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + logger := middlewares.GetLogger(req.Context(), wl.name, typeName) + ctx := logger.WithContext(req.Context()) + + clientIP := wl.strategy.GetIP(req) + err := wl.whiteLister.IsAuthorized(clientIP) + if err != nil { + msg := fmt.Sprintf("Rejecting IP %s: %v", clientIP, err) + logger.Debug().Msg(msg) + tracing.SetStatusErrorf(req.Context(), msg) + reject(ctx, rw) + return + } + logger.Debug().Msgf("Accepting IP %s", clientIP) + + wl.next.ServeHTTP(rw, req) +} + +func reject(ctx context.Context, rw http.ResponseWriter) { + statusCode := http.StatusForbidden + + rw.WriteHeader(statusCode) + _, err := rw.Write([]byte(http.StatusText(statusCode))) + if err != nil { + log.Ctx(ctx).Error().Err(err).Send() + } +} diff --git a/pkg/middlewares/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go new file mode 100644 index 000000000..5e040016a --- /dev/null +++ b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go @@ -0,0 +1,100 @@ +package ipwhitelist + +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" +) + +func TestNewIPWhiteLister(t *testing.T) { + testCases := []struct { + desc string + whiteList dynamic.IPWhiteList + expectedError bool + }{ + { + desc: "invalid IP", + whiteList: dynamic.IPWhiteList{ + SourceRange: []string{"foo"}, + }, + expectedError: true, + }, + { + desc: "valid IP", + whiteList: dynamic.IPWhiteList{ + SourceRange: []string{"10.10.10.10"}, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + + if test.expectedError { + assert.Error(t, err) + } else { + require.NoError(t, err) + assert.NotNil(t, whiteLister) + } + }) + } +} + +func TestIPWhiteLister_ServeHTTP(t *testing.T) { + testCases := []struct { + desc string + whiteList dynamic.IPWhiteList + remoteAddr string + expected int + }{ + { + desc: "authorized with remote address", + whiteList: dynamic.IPWhiteList{ + SourceRange: []string{"20.20.20.20"}, + }, + remoteAddr: "20.20.20.20:1234", + expected: 200, + }, + { + desc: "non authorized with remote address", + whiteList: dynamic.IPWhiteList{ + SourceRange: []string{"20.20.20.20"}, + }, + remoteAddr: "20.20.20.21:1234", + expected: 403, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + require.NoError(t, err) + + recorder := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, "http://10.10.10.10", nil) + + if len(test.remoteAddr) > 0 { + req.RemoteAddr = test.remoteAddr + } + + whiteLister.ServeHTTP(recorder, req) + + assert.Equal(t, test.expected, recorder.Code) + }) + } +} diff --git a/pkg/middlewares/metrics/metrics.go b/pkg/middlewares/metrics/metrics.go index 64a09db17..a61da207e 100644 --- a/pkg/middlewares/metrics/metrics.go +++ b/pkg/middlewares/metrics/metrics.go @@ -16,6 +16,8 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares/capture" "github.com/traefik/traefik/v3/pkg/middlewares/retry" traefiktls "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/codes" ) @@ -39,6 +41,7 @@ type metricsMiddleware struct { reqsBytesCounter gokitmetrics.Counter respsBytesCounter gokitmetrics.Counter baseLabels []string + name string } // NewEntryPointMiddleware creates a new metrics middleware for an Entrypoint. @@ -53,6 +56,7 @@ func NewEntryPointMiddleware(ctx context.Context, next http.Handler, registry me reqsBytesCounter: registry.EntryPointReqsBytesCounter(), respsBytesCounter: registry.EntryPointRespsBytesCounter(), baseLabels: []string{"entrypoint", entryPointName}, + name: nameEntrypoint, } } @@ -68,6 +72,7 @@ func NewRouterMiddleware(ctx context.Context, next http.Handler, registry metric reqsBytesCounter: registry.RouterReqsBytesCounter(), respsBytesCounter: registry.RouterRespsBytesCounter(), baseLabels: []string{"router", routerName, "service", serviceName}, + name: nameRouter, } } @@ -83,6 +88,7 @@ func NewServiceMiddleware(ctx context.Context, next http.Handler, registry metri reqsBytesCounter: registry.ServiceReqsBytesCounter(), respsBytesCounter: registry.ServiceRespsBytesCounter(), baseLabels: []string{"service", serviceName}, + name: nameService, } } @@ -100,6 +106,17 @@ func WrapRouterHandler(ctx context.Context, registry metrics.Registry, routerNam } } +// WrapServiceHandler Wraps metrics service to alice.Constructor. +func WrapServiceHandler(ctx context.Context, registry metrics.Registry, serviceName string) alice.Constructor { + return func(next http.Handler) (http.Handler, error) { + return NewServiceMiddleware(ctx, next, registry, serviceName), nil + } +} + +func (m *metricsMiddleware) GetTracingInformation() (string, string, trace.SpanKind) { + return m.name, typeName, trace.SpanKindInternal +} + func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) { proto := getRequestProtocol(req) @@ -127,6 +144,7 @@ func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) } logger := with.Logger() logger.Error().Err(err).Msg("Could not get Capture") + tracing.SetStatusErrorf(req.Context(), "Could not get Capture") return } diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go index 94367ce28..77f1cd09d 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go @@ -11,11 +11,10 @@ import ( "net/url" "strings" - "github.com/opentracing/opentracing-go/ext" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const typeName = "PassClientTLSCert" @@ -140,8 +139,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.PassTLSClientCer }, nil } -func (p *passTLSClientCert) GetTracingInformation() (string, ext.SpanKindEnum) { - return p.name, tracing.SpanKindNoneEnum +func (p *passTLSClientCert) GetTracingInformation() (string, string, trace.SpanKind) { + return p.name, typeName, trace.SpanKindInternal } func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go index 1c3582c4e..fcede7dce 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter.go +++ b/pkg/middlewares/ratelimiter/rate_limiter.go @@ -9,17 +9,17 @@ import ( "time" "github.com/mailgun/ttlmap" - "github.com/opentracing/opentracing-go/ext" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/tracing" "github.com/vulcand/oxy/v2/utils" + "go.opentelemetry.io/otel/trace" "golang.org/x/time/rate" ) const ( - typeName = "RateLimiterType" + typeName = "RateLimiter" maxSources = 65536 ) @@ -122,8 +122,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name }, nil } -func (rl *rateLimiter) GetTracingInformation() (string, ext.SpanKindEnum) { - return rl.name, tracing.SpanKindNoneEnum +func (rl *rateLimiter) GetTracingInformation() (string, string, trace.SpanKind) { + return rl.name, typeName, trace.SpanKindInternal } func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) { @@ -153,12 +153,14 @@ func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // as the expiryTime is supposed to reflect the activity (or lack thereof) on that source. if err := rl.buckets.Set(source, bucket, rl.ttl); err != nil { logger.Error().Err(err).Msg("Could not insert/update bucket") + tracing.SetStatusErrorf(req.Context(), "Could not insert/update bucket") http.Error(rw, "could not insert/update bucket", http.StatusInternalServerError) return } res := bucket.Reserve() if !res.OK() { + tracing.SetStatusErrorf(req.Context(), "No bursty traffic allowed") http.Error(rw, "No bursty traffic allowed", http.StatusTooManyRequests) return } diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go index 5187d5d63..411879c40 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter_test.go +++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go @@ -18,6 +18,8 @@ import ( "golang.org/x/time/rate" ) +const delta float64 = 1e-10 + func TestNewRateLimiter(t *testing.T) { testCases := []struct { desc string @@ -131,7 +133,7 @@ func TestNewRateLimiter(t *testing.T) { assert.Equal(t, test.requestHeader, hd) } if test.expectedRTL != 0 { - assert.Equal(t, test.expectedRTL, rtl.rate) + assert.InDelta(t, float64(test.expectedRTL), float64(rtl.rate), delta) } }) } diff --git a/pkg/middlewares/redirect/redirect.go b/pkg/middlewares/redirect/redirect.go index d64d8cd70..25ca6a2ac 100644 --- a/pkg/middlewares/redirect/redirect.go +++ b/pkg/middlewares/redirect/redirect.go @@ -5,9 +5,8 @@ import ( "net/url" "regexp" - "github.com/opentracing/opentracing-go/ext" - "github.com/traefik/traefik/v3/pkg/tracing" "github.com/vulcand/oxy/v2/utils" + "go.opentelemetry.io/otel/trace" ) const ( @@ -15,6 +14,8 @@ const ( schemeHTTPS = "https" ) +const typeName = "Redirect" + var uriRegexp = regexp.MustCompile(`^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`) type redirect struct { @@ -45,8 +46,8 @@ func newRedirect(next http.Handler, regex, replacement string, permanent bool, r }, nil } -func (r *redirect) GetTracingInformation() (string, ext.SpanKindEnum) { - return r.name, tracing.SpanKindNoneEnum +func (r *redirect) GetTracingInformation() (string, string, trace.SpanKind) { + return r.name, typeName, trace.SpanKindInternal } func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/replacepath/replace_path.go b/pkg/middlewares/replacepath/replace_path.go index 9f6fea443..3419d7b83 100644 --- a/pkg/middlewares/replacepath/replace_path.go +++ b/pkg/middlewares/replacepath/replace_path.go @@ -5,10 +5,10 @@ import ( "net/http" "net/url" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const ( @@ -35,8 +35,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePath, nam }, nil } -func (r *replacePath) GetTracingInformation() (string, ext.SpanKindEnum) { - return r.name, tracing.SpanKindNoneEnum +func (r *replacePath) GetTracingInformation() (string, string, trace.SpanKind) { + return r.name, typeName, trace.SpanKindInternal } func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) { @@ -52,6 +52,7 @@ func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) { req.URL.Path, err = url.PathUnescape(req.URL.RawPath) if err != nil { middlewares.GetLogger(context.Background(), r.name, typeName).Error().Err(err).Send() + tracing.SetStatusErrorf(req.Context(), err.Error()) http.Error(rw, err.Error(), http.StatusInternalServerError) return } diff --git a/pkg/middlewares/replacepathregex/replace_path_regex.go b/pkg/middlewares/replacepathregex/replace_path_regex.go index 1fffb6bd4..ff800d354 100644 --- a/pkg/middlewares/replacepathregex/replace_path_regex.go +++ b/pkg/middlewares/replacepathregex/replace_path_regex.go @@ -8,11 +8,11 @@ import ( "regexp" "strings" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/replacepath" "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const typeName = "ReplacePathRegex" @@ -42,8 +42,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePathRegex }, nil } -func (rp *replacePathRegex) GetTracingInformation() (string, ext.SpanKindEnum) { - return rp.name, tracing.SpanKindNoneEnum +func (rp *replacePathRegex) GetTracingInformation() (string, string, trace.SpanKind) { + return rp.name, typeName, trace.SpanKindInternal } func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) { @@ -63,6 +63,7 @@ func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) req.URL.Path, err = url.PathUnescape(req.URL.RawPath) if err != nil { middlewares.GetLogger(context.Background(), rp.name, typeName).Error().Err(err).Send() + tracing.SetStatusErrorf(req.Context(), err.Error()) http.Error(rw, err.Error(), http.StatusInternalServerError) return } diff --git a/pkg/middlewares/requestdecorator/hostresolver.go b/pkg/middlewares/requestdecorator/hostresolver.go index d600360a7..028231f1f 100644 --- a/pkg/middlewares/requestdecorator/hostresolver.go +++ b/pkg/middlewares/requestdecorator/hostresolver.go @@ -2,6 +2,7 @@ package requestdecorator import ( "context" + "errors" "fmt" "net" "sort" @@ -65,9 +66,7 @@ func (hr *Resolver) CNAMEFlatten(ctx context.Context, host string) string { request = resolv.Record } - if err := hr.cache.Add(host, result, cacheDuration); err != nil { - logger.Error().Err(err).Send() - } + hr.cache.Set(host, result, cacheDuration) return result } @@ -79,6 +78,10 @@ func cnameResolve(ctx context.Context, host, resolvPath string) (*cnameResolv, e return nil, fmt.Errorf("invalid resolver configuration file: %s", resolvPath) } + if net.ParseIP(host) != nil { + return nil, nil + } + client := &dns.Client{Timeout: 30 * time.Second} m := &dns.Msg{} @@ -88,7 +91,11 @@ func cnameResolve(ctx context.Context, host, resolvPath string) (*cnameResolv, e for _, server := range config.Servers { tempRecord, err := getRecord(client, m, server, config.Port) if err != nil { - log.Ctx(ctx).Error().Err(err).Msgf("Failed to resolve host %s", host) + if errors.Is(err, errNoCNAMERecord) { + log.Ctx(ctx).Debug().Err(err).Msgf("CNAME lookup for hostname %q", host) + continue + } + log.Ctx(ctx).Error().Err(err).Msgf("CNAME lookup for hostname %q", host) continue } result = append(result, tempRecord) @@ -102,6 +109,8 @@ func cnameResolve(ctx context.Context, host, resolvPath string) (*cnameResolv, e return result[0], nil } +var errNoCNAMERecord = errors.New("no CNAME record for host") + func getRecord(client *dns.Client, msg *dns.Msg, server, port string) (*cnameResolv, error) { resp, _, err := client.Exchange(msg, net.JoinHostPort(server, port)) if err != nil { @@ -109,7 +118,7 @@ func getRecord(client *dns.Client, msg *dns.Msg, server, port string) (*cnameRes } if resp == nil || len(resp.Answer) == 0 { - return nil, fmt.Errorf("empty answer for server %s", server) + return nil, fmt.Errorf("%w: %s", errNoCNAMERecord, server) } rr, ok := resp.Answer[0].(*dns.CNAME) diff --git a/pkg/middlewares/headers/responsewriter.go b/pkg/middlewares/response_modifier.go similarity index 76% rename from pkg/middlewares/headers/responsewriter.go rename to pkg/middlewares/response_modifier.go index 121389e59..77b76c740 100644 --- a/pkg/middlewares/headers/responsewriter.go +++ b/pkg/middlewares/response_modifier.go @@ -1,4 +1,4 @@ -package headers +package middlewares import ( "bufio" @@ -9,7 +9,8 @@ import ( "github.com/rs/zerolog/log" ) -type responseModifier struct { +// ResponseModifier is a ResponseWriter to modify the response headers before sending them. +type ResponseModifier struct { req *http.Request rw http.ResponseWriter @@ -21,9 +22,10 @@ type responseModifier struct { modifierErr error // returned by modifier call } -// modifier can be nil. -func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) http.ResponseWriter { - return &responseModifier{ +// NewResponseModifier returns a new ResponseModifier instance. +// The given modifier can be nil. +func NewResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) http.ResponseWriter { + return &ResponseModifier{ req: r, rw: w, modifier: modifier, @@ -33,7 +35,7 @@ func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(* // WriteHeader is, in the specific case of 1xx status codes, a direct call to the wrapped ResponseWriter, without marking headers as sent, // allowing so further calls. -func (r *responseModifier) WriteHeader(code int) { +func (r *ResponseModifier) WriteHeader(code int) { if r.headersSent { return } @@ -73,11 +75,11 @@ func (r *responseModifier) WriteHeader(code int) { r.rw.WriteHeader(code) } -func (r *responseModifier) Header() http.Header { +func (r *ResponseModifier) Header() http.Header { return r.rw.Header() } -func (r *responseModifier) Write(b []byte) (int, error) { +func (r *ResponseModifier) Write(b []byte) (int, error) { r.WriteHeader(r.code) if r.modifierErr != nil { return 0, r.modifierErr @@ -87,7 +89,7 @@ func (r *responseModifier) Write(b []byte) (int, error) { } // Hijack hijacks the connection. -func (r *responseModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) { +func (r *ResponseModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) { if h, ok := r.rw.(http.Hijacker); ok { return h.Hijack() } @@ -96,7 +98,7 @@ func (r *responseModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) { } // Flush sends any buffered data to the client. -func (r *responseModifier) Flush() { +func (r *ResponseModifier) Flush() { if flusher, ok := r.rw.(http.Flusher); ok { flusher.Flush() } diff --git a/pkg/middlewares/retry/retry.go b/pkg/middlewares/retry/retry.go index 22acd02f1..f8945a989 100644 --- a/pkg/middlewares/retry/retry.go +++ b/pkg/middlewares/retry/retry.go @@ -12,10 +12,12 @@ import ( "time" "github.com/cenkalti/backoff/v4" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" ) // Compile time validation that the response writer implements http interfaces correctly. @@ -60,10 +62,6 @@ func New(ctx context.Context, next http.Handler, config dynamic.Retry, listener }, nil } -func (r *retry) GetTracingInformation() (string, ext.SpanKindEnum) { - return r.name, tracing.SpanKindNoneEnum -} - func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if r.attempts == 1 { r.next.ServeHTTP(rw, req) @@ -79,12 +77,35 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) { attempts := 1 + initialCtx := req.Context() + tracer := tracing.TracerFromContext(initialCtx) + + var currentSpan trace.Span operation := func() error { + if tracer != nil { + if currentSpan != nil { + currentSpan.End() + } + // Because multiple tracing spans may need to be created, + // the Retry middleware does not implement trace.Traceable, + // and creates directly a new span for each retry operation. + var tracingCtx context.Context + tracingCtx, currentSpan = tracer.Start(initialCtx, typeName, trace.WithSpanKind(trace.SpanKindInternal)) + + currentSpan.SetAttributes(attribute.String("traefik.middleware.name", r.name)) + // Only add the attribute "http.resend_count" defined by semantic conventions starting from second attempt. + if attempts > 1 { + currentSpan.SetAttributes(semconv.HTTPResendCount(attempts - 1)) + } + + req = req.WithContext(tracingCtx) + } + shouldRetry := attempts < r.attempts retryResponseWriter := newResponseWriter(rw, shouldRetry) // Disable retries when the backend already received request data - trace := &httptrace.ClientTrace{ + clientTrace := &httptrace.ClientTrace{ WroteHeaders: func() { retryResponseWriter.DisableRetries() }, @@ -92,9 +113,9 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) { retryResponseWriter.DisableRetries() }, } - newCtx := httptrace.WithClientTrace(req.Context(), trace) + newCtx := httptrace.WithClientTrace(req.Context(), clientTrace) - r.next.ServeHTTP(retryResponseWriter, req.WithContext(newCtx)) + r.next.ServeHTTP(retryResponseWriter, req.Clone(newCtx)) if !retryResponseWriter.ShouldRetry() { return nil @@ -119,6 +140,10 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if err != nil { logger.Debug().Err(err).Msg("Final retry attempt failed") } + + if currentSpan != nil { + currentSpan.End() + } } func (r *retry) newBackOff() backoff.BackOff { diff --git a/pkg/middlewares/retry/retry_test.go b/pkg/middlewares/retry/retry_test.go index 0ba6b01b0..eaeeea1ed 100644 --- a/pkg/middlewares/retry/retry_test.go +++ b/pkg/middlewares/retry/retry_test.go @@ -373,7 +373,7 @@ func Test1xxResponses(t *testing.T) { req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) - assert.Nil(t, err) + assert.NoError(t, err) defer res.Body.Close() diff --git a/pkg/middlewares/stripprefix/strip_prefix.go b/pkg/middlewares/stripprefix/strip_prefix.go index a017c051f..18717875e 100644 --- a/pkg/middlewares/stripprefix/strip_prefix.go +++ b/pkg/middlewares/stripprefix/strip_prefix.go @@ -5,10 +5,9 @@ import ( "net/http" "strings" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const ( @@ -34,8 +33,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, nam }, nil } -func (s *stripPrefix) GetTracingInformation() (string, ext.SpanKindEnum) { - return s.name, tracing.SpanKindNoneEnum +func (s *stripPrefix) GetTracingInformation() (string, string, trace.SpanKind) { + return s.name, typeName, trace.SpanKindUnspecified } func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go index 383ce1f01..71de1ad28 100644 --- a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go +++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go @@ -6,11 +6,10 @@ import ( "regexp" "strings" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/stripprefix" - "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) const ( @@ -44,8 +43,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefixRegex return &stripPrefix, nil } -func (s *stripPrefixRegex) GetTracingInformation() (string, ext.SpanKindEnum) { - return s.name, tracing.SpanKindNoneEnum +func (s *stripPrefixRegex) GetTracingInformation() (string, string, trace.SpanKind) { + return s.name, typeName, trace.SpanKindInternal } func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist.go b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist.go new file mode 100644 index 000000000..86f3b6ac0 --- /dev/null +++ b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist.go @@ -0,0 +1,63 @@ +package ipwhitelist + +import ( + "context" + "errors" + "fmt" + + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/ip" + "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/traefik/traefik/v3/pkg/tcp" +) + +const ( + typeName = "IPWhiteListerTCP" +) + +// ipWhiteLister is a middleware that provides Checks of the Requesting IP against a set of Whitelists. +type ipWhiteLister struct { + next tcp.Handler + whiteLister *ip.Checker + name string +} + +// New builds a new TCP IPWhiteLister given a list of CIDR-Strings to whitelist. +func New(ctx context.Context, next tcp.Handler, config dynamic.TCPIPWhiteList, name string) (tcp.Handler, error) { + logger := middlewares.GetLogger(ctx, name, typeName) + logger.Debug().Msg("Creating middleware") + + if len(config.SourceRange) == 0 { + return nil, errors.New("sourceRange is empty, IPWhiteLister not created") + } + + checker, err := ip.NewChecker(config.SourceRange) + if err != nil { + return nil, fmt.Errorf("cannot parse CIDR whitelist %s: %w", config.SourceRange, err) + } + + logger.Debug().Msgf("Setting up IPWhiteLister with sourceRange: %s", config.SourceRange) + + return &ipWhiteLister{ + whiteLister: checker, + next: next, + name: name, + }, nil +} + +func (wl *ipWhiteLister) ServeTCP(conn tcp.WriteCloser) { + logger := middlewares.GetLogger(context.Background(), wl.name, typeName) + + addr := conn.RemoteAddr().String() + + err := wl.whiteLister.IsAuthorized(addr) + if err != nil { + logger.Error().Err(err).Msgf("Connection from %s rejected", addr) + conn.Close() + return + } + + logger.Debug().Msgf("Connection from %s accepted", addr) + + wl.next.ServeTCP(conn) +} diff --git a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go new file mode 100644 index 000000000..72fa33d0c --- /dev/null +++ b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go @@ -0,0 +1,139 @@ +package ipwhitelist + +import ( + "context" + "io" + "net" + "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/tcp" +) + +func TestNewIPWhiteLister(t *testing.T) { + testCases := []struct { + desc string + whiteList dynamic.TCPIPWhiteList + expectedError bool + }{ + { + desc: "Empty config", + whiteList: dynamic.TCPIPWhiteList{}, + expectedError: true, + }, + { + desc: "invalid IP", + whiteList: dynamic.TCPIPWhiteList{ + SourceRange: []string{"foo"}, + }, + expectedError: true, + }, + { + desc: "valid IP", + whiteList: dynamic.TCPIPWhiteList{ + SourceRange: []string{"10.10.10.10"}, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + next := tcp.HandlerFunc(func(conn tcp.WriteCloser) {}) + whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + + if test.expectedError { + assert.Error(t, err) + } else { + require.NoError(t, err) + assert.NotNil(t, whiteLister) + } + }) + } +} + +func TestIPWhiteLister_ServeHTTP(t *testing.T) { + testCases := []struct { + desc string + whiteList dynamic.TCPIPWhiteList + remoteAddr string + expected string + }{ + { + desc: "authorized with remote address", + whiteList: dynamic.TCPIPWhiteList{ + SourceRange: []string{"20.20.20.20"}, + }, + remoteAddr: "20.20.20.20:1234", + expected: "OK", + }, + { + desc: "non authorized with remote address", + whiteList: dynamic.TCPIPWhiteList{ + SourceRange: []string{"20.20.20.20"}, + }, + remoteAddr: "20.20.20.21:1234", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + next := tcp.HandlerFunc(func(conn tcp.WriteCloser) { + write, err := conn.Write([]byte("OK")) + require.NoError(t, err) + assert.Equal(t, 2, write) + + err = conn.Close() + require.NoError(t, err) + }) + + whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + require.NoError(t, err) + + server, client := net.Pipe() + + go func() { + whiteLister.ServeTCP(&contextWriteCloser{client, addr{test.remoteAddr}}) + }() + + read, err := io.ReadAll(server) + require.NoError(t, err) + + assert.Equal(t, test.expected, string(read)) + }) + } +} + +type contextWriteCloser struct { + net.Conn + addr +} + +type addr struct { + remoteAddr string +} + +func (a addr) Network() string { + panic("implement me") +} + +func (a addr) String() string { + return a.remoteAddr +} + +func (c contextWriteCloser) CloseWrite() error { + panic("implement me") +} + +func (c contextWriteCloser) RemoteAddr() net.Addr { return c.addr } + +func (c contextWriteCloser) Context() context.Context { + return context.Background() +} diff --git a/pkg/middlewares/tracing/entrypoint.go b/pkg/middlewares/tracing/entrypoint.go index b17d8c2ce..5ea32ee8f 100644 --- a/pkg/middlewares/tracing/entrypoint.go +++ b/pkg/middlewares/tracing/entrypoint.go @@ -5,57 +5,53 @@ import ( "net/http" "github.com/containous/alice" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) const ( entryPointTypeName = "TracingEntryPoint" ) -// NewEntryPoint creates a new middleware that the incoming request. -func NewEntryPoint(ctx context.Context, t *tracing.Tracing, entryPointName string, next http.Handler) http.Handler { - middlewares.GetLogger(ctx, "tracing", entryPointTypeName).Debug().Msg("Creating middleware") - - return &entryPointMiddleware{ - entryPoint: entryPointName, - Tracing: t, - next: next, - } -} - -type entryPointMiddleware struct { - *tracing.Tracing +type entryPointTracing struct { + tracer trace.Tracer entryPoint string next http.Handler } -func (e *entryPointMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - spanCtx, err := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) - if err != nil { - middlewares.GetLogger(req.Context(), "tracing", entryPointTypeName). - Debug().Err(err).Msg("Failed to extract the context") +// WrapEntryPointHandler Wraps tracing to alice.Constructor. +func WrapEntryPointHandler(ctx context.Context, tracer trace.Tracer, entryPointName string) alice.Constructor { + return func(next http.Handler) (http.Handler, error) { + return newEntryPoint(ctx, tracer, entryPointName, next), nil } +} - span, req, finish := e.StartSpanf(req, ext.SpanKindRPCServerEnum, "EntryPoint", []string{e.entryPoint, req.Host}, " ", ext.RPCServerOption(spanCtx)) - defer finish() +// newEntryPoint creates a new tracing middleware for incoming requests. +func newEntryPoint(ctx context.Context, tracer trace.Tracer, entryPointName string, next http.Handler) http.Handler { + middlewares.GetLogger(ctx, "tracing", entryPointTypeName).Debug().Msg("Creating middleware") - ext.Component.Set(span, e.ServiceName) - tracing.LogRequest(span, req) + return &entryPointTracing{ + entryPoint: entryPointName, + tracer: tracer, + next: next, + } +} - req = req.WithContext(tracing.WithTracing(req.Context(), e.Tracing)) +func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + tracingCtx := tracing.ExtractCarrierIntoContext(req.Context(), req.Header) + tracingCtx, span := e.tracer.Start(tracingCtx, "EntryPoint", trace.WithSpanKind(trace.SpanKindServer)) + defer span.End() + + req = req.WithContext(tracingCtx) + + span.SetAttributes(attribute.String("entry_point", e.entryPoint)) + + tracing.LogServerRequest(span, req) recorder := newStatusCodeRecorder(rw, http.StatusOK) e.next.ServeHTTP(recorder, req) - tracing.LogResponseCode(span, recorder.Status()) -} - -// WrapEntryPointHandler Wraps tracing to alice.Constructor. -func WrapEntryPointHandler(ctx context.Context, tracer *tracing.Tracing, entryPointName string) alice.Constructor { - return func(next http.Handler) (http.Handler, error) { - return NewEntryPoint(ctx, tracer, entryPointName, next), nil - } + tracing.LogResponseCode(span, recorder.Status(), trace.SpanKindServer) } diff --git a/pkg/middlewares/tracing/entrypoint_test.go b/pkg/middlewares/tracing/entrypoint_test.go index e8528e048..b3cb60d67 100644 --- a/pkg/middlewares/tracing/entrypoint_test.go +++ b/pkg/middlewares/tracing/entrypoint_test.go @@ -6,81 +6,69 @@ import ( "net/http/httptest" "testing" - "github.com/opentracing/opentracing-go/ext" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/attribute" ) func TestEntryPointMiddleware(t *testing.T) { type expected struct { - Tags map[string]interface{} - OperationName string + name string + attributes []attribute.KeyValue } testCases := []struct { - desc string - entryPoint string - spanNameLimit int - tracing *trackingBackenMock - expected expected + desc string + entryPoint string + expected expected }{ { - desc: "no truncation test", - entryPoint: "test", - spanNameLimit: 0, - tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, - }, + desc: "basic test", + entryPoint: "test", expected: expected{ - Tags: map[string]interface{}{ - "span.kind": ext.SpanKindRPCServerEnum, - "http.method": http.MethodGet, - "component": "", - "http.url": "http://www.test.com", - "http.host": "www.test.com", + name: "EntryPoint", + attributes: []attribute.KeyValue{ + attribute.String("span.kind", "server"), + attribute.String("entry_point", "test"), + attribute.String("http.request.method", "GET"), + attribute.String("network.protocol.version", "1.1"), + attribute.Int64("http.request.body.size", int64(0)), + attribute.String("url.path", "/search"), + attribute.String("url.query", "q=Opentelemetry"), + attribute.String("url.scheme", "http"), + attribute.String("user_agent.original", "entrypoint-test"), + attribute.String("server.address", "www.test.com"), + attribute.String("network.peer.address", "10.0.0.1"), + attribute.String("network.peer.port", "1234"), + attribute.String("client.address", "10.0.0.1"), + attribute.Int64("client.port", int64(1234)), + attribute.String("client.socket.address", ""), + attribute.Int64("http.response.status_code", int64(404)), }, - OperationName: "EntryPoint test www.test.com", - }, - }, - { - desc: "basic test", - entryPoint: "test", - spanNameLimit: 25, - tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, - }, - expected: expected{ - Tags: map[string]interface{}{ - "span.kind": ext.SpanKindRPCServerEnum, - "http.method": http.MethodGet, - "component": "", - "http.url": "http://www.test.com", - "http.host": "www.test.com", - }, - OperationName: "EntryPoint te... ww... 0c15301b", }, }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - newTracing, err := tracing.NewTracing("", test.spanNameLimit, test.tracing) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodGet, "http://www.test.com", nil) + req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil) rw := httptest.NewRecorder() + req.RemoteAddr = "10.0.0.1:1234" + req.Header.Set("User-Agent", "entrypoint-test") + req.Header.Set("X-Forwarded-Proto", "http") - next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { - span := test.tracing.tracer.(*MockTracer).Span - - tags := span.Tags - assert.Equal(t, test.expected.Tags, tags) - assert.Equal(t, test.expected.OperationName, span.OpName) + next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(http.StatusNotFound) }) - handler := NewEntryPoint(context.Background(), newTracing, test.entryPoint, next) + tracer := &mockTracer{} + + handler := newEntryPoint(context.Background(), tracer, test.entryPoint, next) handler.ServeHTTP(rw, req) + + for _, span := range tracer.spans { + assert.Equal(t, test.expected.name, span.name) + assert.Equal(t, test.expected.attributes, span.attributes) + } }) } } diff --git a/pkg/middlewares/tracing/forwarder.go b/pkg/middlewares/tracing/forwarder.go deleted file mode 100644 index e2ee76a10..000000000 --- a/pkg/middlewares/tracing/forwarder.go +++ /dev/null @@ -1,59 +0,0 @@ -package tracing - -import ( - "context" - "net/http" - - "github.com/opentracing/opentracing-go/ext" - "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" -) - -const ( - forwarderTypeName = "TracingForwarder" -) - -type forwarderMiddleware struct { - router string - service string - next http.Handler -} - -// NewForwarder creates a new forwarder middleware that traces the outgoing request. -func NewForwarder(ctx context.Context, router, service string, next http.Handler) http.Handler { - middlewares.GetLogger(ctx, "tracing", forwarderTypeName). - Debug().Str(logs.ServiceName, service).Msg("Added outgoing tracing middleware") - - return &forwarderMiddleware{ - router: router, - service: service, - next: next, - } -} - -func (f *forwarderMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - tr, err := tracing.FromContext(req.Context()) - if err != nil { - f.next.ServeHTTP(rw, req) - return - } - - opParts := []string{f.service, f.router} - span, req, finish := tr.StartSpanf(req, ext.SpanKindRPCClientEnum, "forward", opParts, "/") - defer finish() - - span.SetTag("traefik.service.name", f.service) - span.SetTag("traefik.router.name", f.router) - ext.HTTPMethod.Set(span, req.Method) - ext.HTTPUrl.Set(span, req.URL.String()) - span.SetTag("http.host", req.Host) - - tracing.InjectRequestHeaders(req) - - recorder := newStatusCodeRecorder(rw, 200) - - f.next.ServeHTTP(recorder, req) - - tracing.LogResponseCode(span, recorder.Status()) -} diff --git a/pkg/middlewares/tracing/forwarder_test.go b/pkg/middlewares/tracing/forwarder_test.go deleted file mode 100644 index 785e729cb..000000000 --- a/pkg/middlewares/tracing/forwarder_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package tracing - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/opentracing/opentracing-go/ext" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/traefik/traefik/v3/pkg/tracing" -) - -func TestNewForwarder(t *testing.T) { - type expected struct { - Tags map[string]interface{} - OperationName string - } - - testCases := []struct { - desc string - spanNameLimit int - tracing *trackingBackenMock - service string - router string - expected expected - }{ - { - desc: "Simple Forward Tracer without truncation and hashing", - spanNameLimit: 101, - tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, - }, - service: "some-service.domain.tld", - router: "some-service.domain.tld", - expected: expected{ - Tags: map[string]interface{}{ - "http.host": "www.test.com", - "http.method": "GET", - "http.url": "http://www.test.com/toto", - "traefik.service.name": "some-service.domain.tld", - "traefik.router.name": "some-service.domain.tld", - "span.kind": ext.SpanKindRPCClientEnum, - }, - OperationName: "forward some-service.domain.tld/some-service.domain.tld", - }, - }, - { - desc: "Simple Forward Tracer with truncation and hashing", - spanNameLimit: 101, - tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, - }, - service: "some-service-100.slug.namespace.environment.domain.tld", - router: "some-service-100.slug.namespace.environment.domain.tld", - expected: expected{ - Tags: map[string]interface{}{ - "http.host": "www.test.com", - "http.method": "GET", - "http.url": "http://www.test.com/toto", - "traefik.service.name": "some-service-100.slug.namespace.environment.domain.tld", - "traefik.router.name": "some-service-100.slug.namespace.environment.domain.tld", - "span.kind": ext.SpanKindRPCClientEnum, - }, - OperationName: "forward some-service-100.slug.namespace.enviro.../some-service-100.slug.namespace.enviro.../bc4a0d48", - }, - }, - { - desc: "Exactly 101 chars", - spanNameLimit: 101, - tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, - }, - service: "some-service1.namespace.environment.domain.tld", - router: "some-service1.namespace.environment.domain.tld", - expected: expected{ - Tags: map[string]interface{}{ - "http.host": "www.test.com", - "http.method": "GET", - "http.url": "http://www.test.com/toto", - "traefik.service.name": "some-service1.namespace.environment.domain.tld", - "traefik.router.name": "some-service1.namespace.environment.domain.tld", - "span.kind": ext.SpanKindRPCClientEnum, - }, - OperationName: "forward some-service1.namespace.environment.domain.tld/some-service1.namespace.environment.domain.tld", - }, - }, - { - desc: "More than 101 chars", - spanNameLimit: 101, - tracing: &trackingBackenMock{ - tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, - }, - service: "some-service1.frontend.namespace.environment.domain.tld", - router: "some-service1.backend.namespace.environment.domain.tld", - expected: expected{ - Tags: map[string]interface{}{ - "http.host": "www.test.com", - "http.method": "GET", - "http.url": "http://www.test.com/toto", - "traefik.service.name": "some-service1.frontend.namespace.environment.domain.tld", - "traefik.router.name": "some-service1.backend.namespace.environment.domain.tld", - "span.kind": ext.SpanKindRPCClientEnum, - }, - OperationName: "forward some-service1.frontend.namespace.envir.../some-service1.backend.namespace.enviro.../fa49dd23", - }, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - newTracing, err := tracing.NewTracing("", test.spanNameLimit, test.tracing) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodGet, "http://www.test.com/toto", nil) - req = req.WithContext(tracing.WithTracing(req.Context(), newTracing)) - - rw := httptest.NewRecorder() - - next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { - span := test.tracing.tracer.(*MockTracer).Span - - tags := span.Tags - assert.Equal(t, test.expected.Tags, tags) - assert.True(t, len(test.expected.OperationName) <= test.spanNameLimit, - "the len of the operation name %q [len: %d] doesn't respect limit %d", - test.expected.OperationName, len(test.expected.OperationName), test.spanNameLimit) - assert.Equal(t, test.expected.OperationName, span.OpName) - }) - - handler := NewForwarder(context.Background(), test.router, test.service, next) - handler.ServeHTTP(rw, req) - }) - } -} diff --git a/pkg/middlewares/tracing/middleware.go b/pkg/middlewares/tracing/middleware.go new file mode 100644 index 000000000..47a6af502 --- /dev/null +++ b/pkg/middlewares/tracing/middleware.go @@ -0,0 +1,71 @@ +package tracing + +import ( + "context" + "net/http" + + "github.com/containous/alice" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +// Traceable embeds tracing information. +type Traceable interface { + GetTracingInformation() (name string, typeName string, spanKind trace.SpanKind) +} + +// WrapMiddleware adds traceability to an alice.Constructor. +func WrapMiddleware(ctx context.Context, constructor alice.Constructor) alice.Constructor { + return func(next http.Handler) (http.Handler, error) { + if constructor == nil { + return nil, nil + } + handler, err := constructor(next) + if err != nil { + return nil, err + } + + if traceableHandler, ok := handler.(Traceable); ok { + name, typeName, spanKind := traceableHandler.GetTracingInformation() + log.Ctx(ctx).Debug().Str(logs.MiddlewareName, name).Msg("Adding tracing to middleware") + return NewMiddleware(handler, name, typeName, spanKind), nil + } + return handler, nil + } +} + +// NewMiddleware returns a http.Handler struct. +func NewMiddleware(next http.Handler, name string, typeName string, spanKind trace.SpanKind) http.Handler { + return &middlewareTracing{ + next: next, + name: name, + typeName: typeName, + spanKind: spanKind, + } +} + +// middlewareTracing is used to wrap http handler middleware. +type middlewareTracing struct { + next http.Handler + name string + typeName string + spanKind trace.SpanKind +} + +func (w *middlewareTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if tracer := tracing.TracerFromContext(req.Context()); tracer != nil { + tracingCtx, span := tracer.Start(req.Context(), w.typeName, trace.WithSpanKind(w.spanKind)) + defer span.End() + + req = req.WithContext(tracingCtx) + + span.SetAttributes(attribute.String("traefik.middleware.name", w.name)) + } + + if w.next != nil { + w.next.ServeHTTP(rw, req) + } +} diff --git a/pkg/middlewares/tracing/mock_tracing_test.go b/pkg/middlewares/tracing/mock_tracing_test.go index 33aac1ee6..a44345f6a 100644 --- a/pkg/middlewares/tracing/mock_tracing_test.go +++ b/pkg/middlewares/tracing/mock_tracing_test.go @@ -1,70 +1,62 @@ package tracing import ( - "io" + "context" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/log" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/embedded" ) -type MockTracer struct { - Span *MockSpan +type mockTracerProvider struct { + embedded.TracerProvider } -// StartSpan belongs to the Tracer interface. -func (n MockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span { - n.Span.OpName = operationName - return n.Span +var _ trace.TracerProvider = mockTracerProvider{} + +func (p mockTracerProvider) Tracer(string, ...trace.TracerOption) trace.Tracer { + return &mockTracer{} } -// Inject belongs to the Tracer interface. -func (n MockTracer) Inject(sp opentracing.SpanContext, format, carrier interface{}) error { - return nil +type mockTracer struct { + embedded.Tracer + + spans []*mockSpan } -// Extract belongs to the Tracer interface. -func (n MockTracer) Extract(format, carrier interface{}) (opentracing.SpanContext, error) { - return nil, opentracing.ErrSpanContextNotFound +var _ trace.Tracer = &mockTracer{} + +func (t *mockTracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { + config := trace.NewSpanStartConfig(opts...) + span := &mockSpan{} + span.SetName(name) + span.SetAttributes(attribute.String("span.kind", config.SpanKind().String())) + span.SetAttributes(config.Attributes()...) + t.spans = append(t.spans, span) + return trace.ContextWithSpan(ctx, span), span } -// MockSpanContext a span context mock. -type MockSpanContext struct{} +// mockSpan is an implementation of Span that preforms no operations. +type mockSpan struct { + embedded.Span -func (n MockSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} - -// MockSpan a span mock. -type MockSpan struct { - OpName string - Tags map[string]interface{} + name string + attributes []attribute.KeyValue } -func (n MockSpan) Context() opentracing.SpanContext { return MockSpanContext{} } -func (n MockSpan) SetBaggageItem(key, val string) opentracing.Span { - return MockSpan{Tags: make(map[string]interface{})} -} -func (n MockSpan) BaggageItem(key string) string { return "" } -func (n MockSpan) SetTag(key string, value interface{}) opentracing.Span { - n.Tags[key] = value - return n -} -func (n MockSpan) LogFields(fields ...log.Field) {} -func (n MockSpan) LogKV(keyVals ...interface{}) {} -func (n MockSpan) Finish() {} -func (n MockSpan) FinishWithOptions(opts opentracing.FinishOptions) {} -func (n MockSpan) SetOperationName(operationName string) opentracing.Span { return n } -func (n MockSpan) Tracer() opentracing.Tracer { return MockTracer{} } -func (n MockSpan) LogEvent(event string) {} -func (n MockSpan) LogEventWithPayload(event string, payload interface{}) {} -func (n MockSpan) Log(data opentracing.LogData) {} -func (n *MockSpan) Reset() { - n.Tags = make(map[string]interface{}) -} +var _ trace.Span = &mockSpan{} -type trackingBackenMock struct { - tracer opentracing.Tracer +func (*mockSpan) SpanContext() trace.SpanContext { return trace.SpanContext{} } +func (*mockSpan) IsRecording() bool { return false } +func (s *mockSpan) SetStatus(_ codes.Code, _ string) {} +func (s *mockSpan) SetAttributes(kv ...attribute.KeyValue) { + s.attributes = append(s.attributes, kv...) } +func (s *mockSpan) End(...trace.SpanEndOption) {} +func (s *mockSpan) RecordError(_ error, _ ...trace.EventOption) {} +func (s *mockSpan) AddEvent(_ string, _ ...trace.EventOption) {} -func (t *trackingBackenMock) Setup(componentName string) (opentracing.Tracer, io.Closer, error) { - opentracing.SetGlobalTracer(t.tracer) - return t.tracer, nil, nil -} +func (s *mockSpan) SetName(name string) { s.name = name } + +func (*mockSpan) TracerProvider() trace.TracerProvider { return mockTracerProvider{} } diff --git a/pkg/middlewares/tracing/router.go b/pkg/middlewares/tracing/router.go new file mode 100644 index 000000000..e964be12c --- /dev/null +++ b/pkg/middlewares/tracing/router.go @@ -0,0 +1,60 @@ +package tracing + +import ( + "context" + "net/http" + + "github.com/containous/alice" + "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" +) + +const ( + routerTypeName = "TracingRouter" +) + +type routerTracing struct { + router string + routerRule string + service string + next http.Handler +} + +// WrapRouterHandler Wraps tracing to alice.Constructor. +func WrapRouterHandler(ctx context.Context, router, routerRule, service string) alice.Constructor { + return func(next http.Handler) (http.Handler, error) { + return newRouter(ctx, router, routerRule, service, next), nil + } +} + +// newRouter creates a new tracing middleware that traces the internal requests. +func newRouter(ctx context.Context, router, routerRule, service string, next http.Handler) http.Handler { + middlewares.GetLogger(ctx, "tracing", routerTypeName). + Debug().Str(logs.RouterName, router).Str(logs.ServiceName, service).Msg("Added outgoing tracing middleware") + + return &routerTracing{ + router: router, + routerRule: routerRule, + service: service, + next: next, + } +} + +func (f *routerTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if tracer := tracing.TracerFromContext(req.Context()); tracer != nil { + tracingCtx, span := tracer.Start(req.Context(), "Router", trace.WithSpanKind(trace.SpanKindInternal)) + defer span.End() + + req = req.WithContext(tracingCtx) + + span.SetAttributes(attribute.String("traefik.service.name", f.service)) + span.SetAttributes(attribute.String("traefik.router.name", f.router)) + span.SetAttributes(semconv.HTTPRoute(f.routerRule)) + } + + f.next.ServeHTTP(rw, req) +} diff --git a/pkg/middlewares/tracing/router_test.go b/pkg/middlewares/tracing/router_test.go new file mode 100644 index 000000000..b220cdabe --- /dev/null +++ b/pkg/middlewares/tracing/router_test.go @@ -0,0 +1,86 @@ +package tracing + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +func TestNewRouter(t *testing.T) { + type expected struct { + attributes []attribute.KeyValue + name string + } + + testCases := []struct { + desc string + service string + router string + routerRule string + expected []expected + }{ + { + desc: "base", + service: "myService", + router: "myRouter", + routerRule: "Path(`/`)", + expected: []expected{ + { + name: "EntryPoint", + attributes: []attribute.KeyValue{ + attribute.String("span.kind", "server"), + }, + }, + { + name: "Router", + attributes: []attribute.KeyValue{ + attribute.String("span.kind", "internal"), + attribute.String("http.request.method", "GET"), + attribute.Int64("http.response.status_code", int64(404)), + attribute.String("network.protocol.version", "1.1"), + attribute.String("server.address", "www.test.com"), + attribute.Int64("server.port", int64(80)), + attribute.String("url.full", "http://www.test.com/traces?p=OpenTelemetry"), + attribute.String("url.scheme", "http"), + attribute.String("traefik.service.name", "myService"), + attribute.String("traefik.router.name", "myRouter"), + attribute.String("http.route", "Path(`/`)"), + attribute.String("user_agent.original", "router-test"), + }, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://www.test.com/traces?p=OpenTelemetry", nil) + req.RemoteAddr = "10.0.0.1:1234" + req.Header.Set("User-Agent", "router-test") + + tracer := &mockTracer{} + tracingCtx, entryPointSpan := tracer.Start(req.Context(), "EntryPoint", trace.WithSpanKind(trace.SpanKindServer)) + defer entryPointSpan.End() + + req = req.WithContext(tracingCtx) + + rw := httptest.NewRecorder() + next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(http.StatusNotFound) + }) + + handler := newRouter(context.Background(), test.router, test.routerRule, test.service, next) + handler.ServeHTTP(rw, req) + + for i, span := range tracer.spans { + assert.Equal(t, test.expected[i].name, span.name) + assert.Equal(t, test.expected[i].attributes, span.attributes) + } + }) + } +} diff --git a/pkg/middlewares/tracing/service.go b/pkg/middlewares/tracing/service.go new file mode 100644 index 000000000..9cc5bd1ca --- /dev/null +++ b/pkg/middlewares/tracing/service.go @@ -0,0 +1,45 @@ +package tracing + +import ( + "context" + "net/http" + + "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +const ( + serviceTypeName = "TracingService" +) + +type serviceTracing struct { + service string + next http.Handler +} + +// NewService creates a new tracing middleware that traces the outgoing requests. +func NewService(ctx context.Context, service string, next http.Handler) http.Handler { + middlewares.GetLogger(ctx, "tracing", serviceTypeName). + Debug().Str(logs.ServiceName, service).Msg("Added outgoing tracing middleware") + + return &serviceTracing{ + service: service, + next: next, + } +} + +func (t *serviceTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if tracer := tracing.TracerFromContext(req.Context()); tracer != nil { + tracingCtx, span := tracer.Start(req.Context(), "Service", trace.WithSpanKind(trace.SpanKindInternal)) + defer span.End() + + req = req.WithContext(tracingCtx) + + span.SetAttributes(attribute.String("traefik.service.name", t.service)) + } + + t.next.ServeHTTP(rw, req) +} diff --git a/pkg/middlewares/tracing/service_test.go b/pkg/middlewares/tracing/service_test.go new file mode 100644 index 000000000..c78510faf --- /dev/null +++ b/pkg/middlewares/tracing/service_test.go @@ -0,0 +1,80 @@ +package tracing + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +func TestNewService(t *testing.T) { + type expected struct { + attributes []attribute.KeyValue + name string + } + + testCases := []struct { + desc string + service string + expected []expected + }{ + { + desc: "base", + service: "myService", + expected: []expected{ + { + name: "EntryPoint", + attributes: []attribute.KeyValue{ + attribute.String("span.kind", "server"), + }, + }, + { + name: "Service", + attributes: []attribute.KeyValue{ + attribute.String("span.kind", "internal"), + attribute.String("http.request.method", "GET"), + attribute.Int64("http.response.status_code", int64(404)), + attribute.String("network.protocol.version", "1.1"), + attribute.String("server.address", "www.test.com"), + attribute.Int64("server.port", int64(80)), + attribute.String("url.full", "http://www.test.com/traces?p=OpenTelemetry"), + attribute.String("url.scheme", "http"), + attribute.String("traefik.service.name", "myService"), + attribute.String("user_agent.original", "service-test"), + }, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://www.test.com/traces?p=OpenTelemetry", nil) + req.RemoteAddr = "10.0.0.1:1234" + req.Header.Set("User-Agent", "service-test") + + tracer := &mockTracer{} + tracingCtx, entryPointSpan := tracer.Start(req.Context(), "EntryPoint", trace.WithSpanKind(trace.SpanKindServer)) + defer entryPointSpan.End() + + req = req.WithContext(tracingCtx) + + rw := httptest.NewRecorder() + next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(http.StatusNotFound) + }) + + handler := NewService(context.Background(), test.service, next) + handler.ServeHTTP(rw, req) + + for i, span := range tracer.spans { + assert.Equal(t, test.expected[i].name, span.name) + assert.Equal(t, test.expected[i].attributes, span.attributes) + } + }) + } +} diff --git a/pkg/middlewares/tracing/wrapper.go b/pkg/middlewares/tracing/wrapper.go deleted file mode 100644 index 1fa3cfcdd..000000000 --- a/pkg/middlewares/tracing/wrapper.go +++ /dev/null @@ -1,69 +0,0 @@ -package tracing - -import ( - "context" - "net/http" - - "github.com/containous/alice" - "github.com/opentracing/opentracing-go/ext" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/tracing" -) - -// Traceable embeds tracing information. -type Traceable interface { - GetTracingInformation() (name string, spanKind ext.SpanKindEnum) -} - -// Wrap adds traceability to an alice.Constructor. -func Wrap(ctx context.Context, constructor alice.Constructor) alice.Constructor { - return func(next http.Handler) (http.Handler, error) { - if constructor == nil { - return nil, nil - } - handler, err := constructor(next) - if err != nil { - return nil, err - } - - if traceableHandler, ok := handler.(Traceable); ok { - name, spanKind := traceableHandler.GetTracingInformation() - log.Ctx(ctx).Debug().Str(logs.MiddlewareName, name).Msg("Adding tracing to middleware") - return NewWrapper(handler, name, spanKind), nil - } - return handler, nil - } -} - -// NewWrapper returns a http.Handler struct. -func NewWrapper(next http.Handler, name string, spanKind ext.SpanKindEnum) http.Handler { - return &Wrapper{ - next: next, - name: name, - spanKind: spanKind, - } -} - -// Wrapper is used to wrap http handler middleware. -type Wrapper struct { - next http.Handler - name string - spanKind ext.SpanKindEnum -} - -func (w *Wrapper) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - _, err := tracing.FromContext(req.Context()) - if err != nil { - w.next.ServeHTTP(rw, req) - return - } - - var finish func() - _, req, finish = tracing.StartSpan(req, w.name, w.spanKind) - defer finish() - - if w.next != nil { - w.next.ServeHTTP(rw, req) - } -} diff --git a/pkg/muxer/tcp/mux_test.go b/pkg/muxer/tcp/mux_test.go index 08258b99e..5c52089b4 100644 --- a/pkg/muxer/tcp/mux_test.go +++ b/pkg/muxer/tcp/mux_test.go @@ -309,7 +309,7 @@ func Test_addTCPRoute(t *testing.T) { matchingHandler.ServeTCP(conn) n, ok := conn.call[msg] - assert.Equal(t, n, 1) + assert.Equal(t, 1, n) assert.True(t, ok) }) } diff --git a/pkg/plugins/builder.go b/pkg/plugins/builder.go index c2012427b..42f1a5f05 100644 --- a/pkg/plugins/builder.go +++ b/pkg/plugins/builder.go @@ -4,28 +4,34 @@ import ( "context" "fmt" "net/http" - "os" + "path/filepath" - "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/yaegi/interp" - "github.com/traefik/yaegi/stdlib" ) // Constructor creates a plugin handler. type Constructor func(context.Context, http.Handler) (http.Handler, error) +type pluginMiddleware interface { + NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) +} + +type middlewareBuilder interface { + newMiddleware(config map[string]interface{}, middlewareName string) (pluginMiddleware, error) +} + // Builder is a plugin builder. type Builder struct { - middlewareBuilders map[string]*middlewareBuilder providerBuilders map[string]providerBuilder + middlewareBuilders map[string]middlewareBuilder } // NewBuilder creates a new Builder. func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[string]LocalDescriptor) (*Builder, error) { + ctx := context.Background() + pb := &Builder{ - middlewareBuilders: map[string]*middlewareBuilder{}, + middlewareBuilders: map[string]middlewareBuilder{}, providerBuilders: map[string]providerBuilder{}, } @@ -36,44 +42,30 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[ return nil, fmt.Errorf("%s: failed to read manifest: %w", desc.ModuleName, err) } - logger := log.With().Str("plugin", "plugin-"+pName).Str("module", desc.ModuleName).Logger() - - i := interp.New(interp.Options{ - GoPath: client.GoPath(), - Env: os.Environ(), - Stdout: logs.NoLevel(logger, zerolog.DebugLevel), - Stderr: logs.NoLevel(logger, zerolog.ErrorLevel), - }) - - err = i.Use(stdlib.Symbols) - if err != nil { - return nil, fmt.Errorf("%s: failed to load symbols: %w", desc.ModuleName, err) - } - - err = i.Use(ppSymbols()) - if err != nil { - return nil, fmt.Errorf("%s: failed to load provider symbols: %w", desc.ModuleName, err) - } - - _, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import)) - if err != nil { - return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", desc.ModuleName, manifest.Import, err) - } + logger := log.With(). + Str("plugin", "plugin-"+pName). + Str("module", desc.ModuleName). + Str("runtime", manifest.Runtime). + Logger() + logCtx := logger.WithContext(ctx) switch manifest.Type { - case "middleware": - middleware, err := newMiddlewareBuilder(i, manifest.BasePkg, manifest.Import) + case typeMiddleware: + middleware, err := newMiddlewareBuilder(logCtx, client.GoPath(), manifest, desc.ModuleName) if err != nil { return nil, err } pb.middlewareBuilders[pName] = middleware - case "provider": - pb.providerBuilders[pName] = providerBuilder{ - interpreter: i, - Import: manifest.Import, - BasePkg: manifest.BasePkg, + + case typeProvider: + pBuilder, err := newProviderBuilder(logCtx, manifest, client.GoPath()) + if err != nil { + return nil, fmt.Errorf("%s: %w", desc.ModuleName, err) } + + pb.providerBuilders[pName] = pBuilder + default: return nil, fmt.Errorf("unknow plugin type: %s", manifest.Type) } @@ -85,48 +77,107 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[ return nil, fmt.Errorf("%s: failed to read manifest: %w", desc.ModuleName, err) } - logger := log.With().Str("plugin", "plugin-"+pName).Str("module", desc.ModuleName).Logger() - - i := interp.New(interp.Options{ - GoPath: localGoPath, - Env: os.Environ(), - Stdout: logs.NoLevel(logger, zerolog.DebugLevel), - Stderr: logs.NoLevel(logger, zerolog.ErrorLevel), - }) - - err = i.Use(stdlib.Symbols) - if err != nil { - return nil, fmt.Errorf("%s: failed to load symbols: %w", desc.ModuleName, err) - } - - err = i.Use(ppSymbols()) - if err != nil { - return nil, fmt.Errorf("%s: failed to load provider symbols: %w", desc.ModuleName, err) - } - - _, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import)) - if err != nil { - return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", desc.ModuleName, manifest.Import, err) - } + logger := log.With(). + Str("plugin", "plugin-"+pName). + Str("module", desc.ModuleName). + Str("runtime", manifest.Runtime). + Logger() + logCtx := logger.WithContext(ctx) switch manifest.Type { - case "middleware": - middleware, err := newMiddlewareBuilder(i, manifest.BasePkg, manifest.Import) + case typeMiddleware: + middleware, err := newMiddlewareBuilder(logCtx, localGoPath, manifest, desc.ModuleName) if err != nil { return nil, err } pb.middlewareBuilders[pName] = middleware - case "provider": - pb.providerBuilders[pName] = providerBuilder{ - interpreter: i, - Import: manifest.Import, - BasePkg: manifest.BasePkg, + + case typeProvider: + builder, err := newProviderBuilder(logCtx, manifest, localGoPath) + if err != nil { + return nil, fmt.Errorf("%s: %w", desc.ModuleName, err) } + + pb.providerBuilders[pName] = builder + default: return nil, fmt.Errorf("unknow plugin type: %s", manifest.Type) } } - return pb, nil } + +// Build builds a middleware plugin. +func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (Constructor, error) { + if b.middlewareBuilders == nil { + return nil, fmt.Errorf("no plugin definitions in the static configuration: %s", pName) + } + + // plugin (pName) can be located in yaegi or wasm middleware builders. + if descriptor, ok := b.middlewareBuilders[pName]; ok { + m, err := descriptor.newMiddleware(config, middlewareName) + if err != nil { + return nil, err + } + + return m.NewHandler, nil + } + + return nil, fmt.Errorf("unknown plugin type: %s", pName) +} + +func newMiddlewareBuilder(ctx context.Context, goPath string, manifest *Manifest, moduleName string) (middlewareBuilder, error) { + switch manifest.Runtime { + case runtimeWasm: + wasmPath, err := getWasmPath(manifest) + if err != nil { + return nil, fmt.Errorf("wasm path: %w", err) + } + + return newWasmMiddlewareBuilder(goPath, moduleName, wasmPath), nil + + case runtimeYaegi, "": + i, err := newInterpreter(ctx, goPath, manifest.Import) + if err != nil { + return nil, fmt.Errorf("failed to craete Yaegi intepreter: %w", err) + } + + return newYaegiMiddlewareBuilder(i, manifest.BasePkg, manifest.Import) + + default: + return nil, fmt.Errorf("unknown plugin runtime: %s", manifest.Runtime) + } +} + +func newProviderBuilder(ctx context.Context, manifest *Manifest, goPath string) (providerBuilder, error) { + switch manifest.Runtime { + case runtimeYaegi, "": + i, err := newInterpreter(ctx, goPath, manifest.Import) + if err != nil { + return providerBuilder{}, err + } + + return providerBuilder{ + interpreter: i, + Import: manifest.Import, + BasePkg: manifest.BasePkg, + }, nil + + default: + return providerBuilder{}, fmt.Errorf("unknown plugin runtime: %s", manifest.Runtime) + } +} + +func getWasmPath(manifest *Manifest) (string, error) { + wasmPath := manifest.WasmPath + if wasmPath == "" { + wasmPath = "plugin.wasm" + } + + if !filepath.IsLocal(wasmPath) { + return "", fmt.Errorf("wasmPath must be a local path") + } + + return wasmPath, nil +} diff --git a/pkg/plugins/client.go b/pkg/plugins/client.go index 8d15550f3..52369585f 100644 --- a/pkg/plugins/client.go +++ b/pkg/plugins/client.go @@ -4,6 +4,7 @@ import ( zipa "archive/zip" "context" "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -278,7 +279,15 @@ func unzipFile(f *zipa.File, dest string) error { defer func() { _ = rc.Close() }() pathParts := strings.SplitN(f.Name, "/", 2) - p := filepath.Join(dest, pathParts[1]) + + var pp string + if len(pathParts) < 2 { + pp = pathParts[0] + } else { + pp = pathParts[1] + } + + p := filepath.Join(dest, pp) if f.FileInfo().IsDir() { err = os.MkdirAll(p, f.Mode()) @@ -421,5 +430,5 @@ func computeHash(filepath string) (string, error) { sum := hash.Sum(nil) - return fmt.Sprintf("%x", sum), nil + return hex.EncodeToString(sum), nil } diff --git a/pkg/plugins/middlewarewasm.go b/pkg/plugins/middlewarewasm.go new file mode 100644 index 000000000..b307850d9 --- /dev/null +++ b/pkg/plugins/middlewarewasm.go @@ -0,0 +1,81 @@ +package plugins + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "reflect" + + "github.com/http-wasm/http-wasm-host-go/handler" + wasm "github.com/http-wasm/http-wasm-host-go/handler/nethttp" + "github.com/tetratelabs/wazero" + "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/middlewares" +) + +type wasmMiddlewareBuilder struct { + path string +} + +func newWasmMiddlewareBuilder(goPath string, moduleName, wasmPath string) *wasmMiddlewareBuilder { + return &wasmMiddlewareBuilder{path: filepath.Join(goPath, "src", moduleName, wasmPath)} +} + +func (b wasmMiddlewareBuilder) newMiddleware(config map[string]interface{}, middlewareName string) (pluginMiddleware, error) { + return &WasmMiddleware{ + middlewareName: middlewareName, + config: reflect.ValueOf(config), + builder: b, + }, nil +} + +func (b wasmMiddlewareBuilder) newHandler(ctx context.Context, next http.Handler, cfg reflect.Value, middlewareName string) (http.Handler, error) { + code, err := os.ReadFile(b.path) + if err != nil { + return nil, fmt.Errorf("loading Wasm binary: %w", err) + } + + logger := middlewares.GetLogger(ctx, middlewareName, "wasm") + + opts := []handler.Option{ + handler.ModuleConfig(wazero.NewModuleConfig().WithSysWalltime()), + handler.Logger(logs.NewWasmLogger(logger)), + } + + i := cfg.Interface() + if i != nil { + config, ok := i.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("could not type assert config: %T", i) + } + + data, err := json.Marshal(config) + if err != nil { + return nil, fmt.Errorf("marshaling config: %w", err) + } + + opts = append(opts, handler.GuestConfig(data)) + } + + mw, err := wasm.NewMiddleware(context.Background(), code, opts...) + if err != nil { + return nil, err + } + + return mw.NewHandler(ctx, next), nil +} + +// WasmMiddleware is an HTTP handler plugin wrapper. +type WasmMiddleware struct { + middlewareName string + config reflect.Value + builder wasmMiddlewareBuilder +} + +// NewHandler creates a new HTTP handler. +func (m WasmMiddleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) { + return m.builder.newHandler(ctx, next, m.config, m.middlewareName) +} diff --git a/pkg/plugins/middlewares.go b/pkg/plugins/middlewareyaegi.go similarity index 54% rename from pkg/plugins/middlewares.go rename to pkg/plugins/middlewareyaegi.go index e46dbae34..452f8713b 100644 --- a/pkg/plugins/middlewares.go +++ b/pkg/plugins/middlewareyaegi.go @@ -4,39 +4,25 @@ import ( "context" "fmt" "net/http" + "os" "path" "reflect" "strings" "github.com/mitchellh/mapstructure" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/yaegi/interp" + "github.com/traefik/yaegi/stdlib" ) -// Build builds a middleware plugin. -func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (Constructor, error) { - if b.middlewareBuilders == nil { - return nil, fmt.Errorf("no plugin definition in the static configuration: %s", pName) - } - - descriptor, ok := b.middlewareBuilders[pName] - if !ok { - return nil, fmt.Errorf("unknown plugin type: %s", pName) - } - - m, err := newMiddleware(descriptor, config, middlewareName) - if err != nil { - return nil, err - } - - return m.NewHandler, err -} - -type middlewareBuilder struct { +type yaegiMiddlewareBuilder struct { fnNew reflect.Value fnCreateConfig reflect.Value } -func newMiddlewareBuilder(i *interp.Interpreter, basePkg, imp string) (*middlewareBuilder, error) { +func newYaegiMiddlewareBuilder(i *interp.Interpreter, basePkg, imp string) (*yaegiMiddlewareBuilder, error) { if basePkg == "" { basePkg = strings.ReplaceAll(path.Base(imp), "-", "_") } @@ -51,21 +37,35 @@ func newMiddlewareBuilder(i *interp.Interpreter, basePkg, imp string) (*middlewa return nil, fmt.Errorf("failed to eval CreateConfig: %w", err) } - return &middlewareBuilder{ + return &yaegiMiddlewareBuilder{ fnNew: fnNew, fnCreateConfig: fnCreateConfig, }, nil } -func (p middlewareBuilder) newHandler(ctx context.Context, next http.Handler, cfg reflect.Value, middlewareName string) (http.Handler, error) { +func (b yaegiMiddlewareBuilder) newMiddleware(config map[string]interface{}, middlewareName string) (pluginMiddleware, error) { + vConfig, err := b.createConfig(config) + if err != nil { + return nil, err + } + + return &YaegiMiddleware{ + middlewareName: middlewareName, + config: vConfig, + builder: b, + }, nil +} + +func (b yaegiMiddlewareBuilder) newHandler(ctx context.Context, next http.Handler, cfg reflect.Value, middlewareName string) (http.Handler, error) { args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(next), cfg, reflect.ValueOf(middlewareName)} - results := p.fnNew.Call(args) + results := b.fnNew.Call(args) if len(results) > 1 && results[1].Interface() != nil { err, ok := results[1].Interface().(error) if !ok { return nil, fmt.Errorf("invalid error type: %T", results[0].Interface()) } + return nil, err } @@ -77,8 +77,8 @@ func (p middlewareBuilder) newHandler(ctx context.Context, next http.Handler, cf return handler, nil } -func (p middlewareBuilder) createConfig(config map[string]interface{}) (reflect.Value, error) { - results := p.fnCreateConfig.Call(nil) +func (b yaegiMiddlewareBuilder) createConfig(config map[string]interface{}) (reflect.Value, error) { + results := b.fnCreateConfig.Call(nil) if len(results) != 1 { return reflect.Value{}, fmt.Errorf("invalid number of return for the CreateConfig function: %d", len(results)) } @@ -107,27 +107,40 @@ func (p middlewareBuilder) createConfig(config map[string]interface{}) (reflect. return vConfig, nil } -// Middleware is an HTTP handler plugin wrapper. -type Middleware struct { +// YaegiMiddleware is an HTTP handler plugin wrapper. +type YaegiMiddleware struct { middlewareName string config reflect.Value - builder *middlewareBuilder -} - -func newMiddleware(builder *middlewareBuilder, config map[string]interface{}, middlewareName string) (*Middleware, error) { - vConfig, err := builder.createConfig(config) - if err != nil { - return nil, err - } - - return &Middleware{ - middlewareName: middlewareName, - config: vConfig, - builder: builder, - }, nil + builder yaegiMiddlewareBuilder } // NewHandler creates a new HTTP handler. -func (m *Middleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) { +func (m *YaegiMiddleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) { return m.builder.newHandler(ctx, next, m.config, m.middlewareName) } + +func newInterpreter(ctx context.Context, goPath string, manifestImport string) (*interp.Interpreter, error) { + i := interp.New(interp.Options{ + GoPath: goPath, + Env: os.Environ(), + Stdout: logs.NoLevel(*log.Ctx(ctx), zerolog.DebugLevel), + Stderr: logs.NoLevel(*log.Ctx(ctx), zerolog.ErrorLevel), + }) + + err := i.Use(stdlib.Symbols) + if err != nil { + return nil, fmt.Errorf("failed to load symbols: %w", err) + } + + err = i.Use(ppSymbols()) + if err != nil { + return nil, fmt.Errorf("failed to load provider symbols: %w", err) + } + + _, err = i.Eval(fmt.Sprintf(`import "%s"`, manifestImport)) + if err != nil { + return nil, fmt.Errorf("failed to import plugin code %q: %w", manifestImport, err) + } + + return i, nil +} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 43beee8f0..367b6c46c 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -138,18 +138,28 @@ func checkLocalPluginManifest(descriptor LocalDescriptor) error { var errs *multierror.Error switch m.Type { - case "middleware", "provider": - // noop + case typeMiddleware: + if m.Runtime != runtimeYaegi && m.Runtime != runtimeWasm && m.Runtime != "" { + errs = multierror.Append(errs, fmt.Errorf("%s: unsupported runtime '%q'", descriptor.ModuleName, m.Runtime)) + } + + case typeProvider: + if m.Runtime != runtimeYaegi && m.Runtime != "" { + errs = multierror.Append(errs, fmt.Errorf("%s: unsupported runtime '%q'", descriptor.ModuleName, m.Runtime)) + } + default: errs = multierror.Append(errs, fmt.Errorf("%s: unsupported type %q", descriptor.ModuleName, m.Type)) } - if m.Import == "" { - errs = multierror.Append(errs, fmt.Errorf("%s: missing import", descriptor.ModuleName)) - } + if m.IsYaegiPlugin() { + if m.Import == "" { + errs = multierror.Append(errs, fmt.Errorf("%s: missing import", descriptor.ModuleName)) + } - if !strings.HasPrefix(m.Import, descriptor.ModuleName) { - errs = multierror.Append(errs, fmt.Errorf("the import %q must be related to the module name %q", m.Import, descriptor.ModuleName)) + if !strings.HasPrefix(m.Import, descriptor.ModuleName) { + errs = multierror.Append(errs, fmt.Errorf("the import %q must be related to the module name %q", m.Import, descriptor.ModuleName)) + } } if m.DisplayName == "" { diff --git a/pkg/plugins/types.go b/pkg/plugins/types.go index 78ea13a34..35b357faf 100644 --- a/pkg/plugins/types.go +++ b/pkg/plugins/types.go @@ -1,5 +1,15 @@ package plugins +const ( + runtimeYaegi = "yaegi" + runtimeWasm = "wasm" +) + +const ( + typeMiddleware = "middleware" + typeProvider = "provider" +) + // Descriptor The static part of a plugin configuration. type Descriptor struct { // ModuleName (required) @@ -19,9 +29,17 @@ type LocalDescriptor struct { type Manifest struct { DisplayName string `yaml:"displayName"` Type string `yaml:"type"` + Runtime string `yaml:"runtime"` + WasmPath string `yaml:"wasmPath"` Import string `yaml:"import"` BasePkg string `yaml:"basePkg"` Compatibility string `yaml:"compatibility"` Summary string `yaml:"summary"` TestData map[string]interface{} `yaml:"testData"` } + +// IsYaegiPlugin returns true if the plugin is a Yaegi plugin. +func (m *Manifest) IsYaegiPlugin() bool { + // defaults always Yaegi to have backwards compatibility to plugins without runtime + return m.Runtime == runtimeYaegi || m.Runtime == "" +} diff --git a/pkg/provider/acme/challenge_http.go b/pkg/provider/acme/challenge_http.go index 155356505..37dfd0bc8 100644 --- a/pkg/provider/acme/challenge_http.go +++ b/pkg/provider/acme/challenge_http.go @@ -11,11 +11,9 @@ import ( "sync" "time" - "github.com/cenkalti/backoff/v4" "github.com/go-acme/lego/v4/challenge/http01" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/safe" ) // ChallengeHTTP HTTP challenge provider implements challenge.Provider. @@ -105,35 +103,18 @@ func (c *ChallengeHTTP) getTokenValue(ctx context.Context, token, domain string) logger := log.Ctx(ctx) logger.Debug().Msgf("Retrieving the ACME challenge for %s (token %q)...", domain, token) - var result []byte - - operation := func() error { - c.lock.RLock() - defer c.lock.RUnlock() - - if _, ok := c.httpChallenges[token]; !ok { - return fmt.Errorf("cannot find challenge for token %q (%s)", token, domain) - } - - var ok bool - result, ok = c.httpChallenges[token][domain] - if !ok { - return fmt.Errorf("cannot find challenge for %s (token %q)", domain, token) - } + c.lock.RLock() + defer c.lock.RUnlock() + if _, ok := c.httpChallenges[token]; !ok { + logger.Error().Msgf("Cannot retrieve the ACME challenge for %s (token %q)", domain, token) return nil } - notify := func(err error, time time.Duration) { - logger.Error().Msgf("Error getting challenge for token retrying in %s", time) - } - - ebo := backoff.NewExponentialBackOff() - ebo.MaxElapsedTime = 60 * time.Second - err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify) - if err != nil { - logger.Error().Err(err).Msgf("Cannot retrieve the ACME challenge for %s (token %q)", domain, token) - return []byte{} + result, ok := c.httpChallenges[token][domain] + if !ok { + logger.Error().Msgf("Cannot retrieve the ACME challenge for %s (token %q)", domain, token) + return nil } return result diff --git a/pkg/provider/acme/challenge_tls.go b/pkg/provider/acme/challenge_tls.go index b4ce624d5..4de4f0916 100644 --- a/pkg/provider/acme/challenge_tls.go +++ b/pkg/provider/acme/challenge_tls.go @@ -152,8 +152,8 @@ func createMessage(certs map[string]*Certificate) dynamic.Message { for _, cert := range certs { certConf := &traefiktls.CertAndStores{ Certificate: traefiktls.Certificate{ - CertFile: traefiktls.FileOrContent(cert.Certificate), - KeyFile: traefiktls.FileOrContent(cert.Key), + CertFile: types.FileOrContent(cert.Certificate), + KeyFile: types.FileOrContent(cert.Key), }, Stores: []string{tlsalpn01.ACMETLS1Protocol}, } diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 514c0ae68..12d5a0b6d 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -31,6 +31,8 @@ import ( "github.com/traefik/traefik/v3/pkg/version" ) +const resolverSuffix = ".acme" + // ocspMustStaple enables OCSP stapling as from https://github.com/go-acme/lego/issues/270. var ocspMustStaple = false @@ -86,7 +88,7 @@ type DNSChallenge struct { // HTTPChallenge contains HTTP challenge configuration. type HTTPChallenge struct { - EntryPoint string `description:"HTTP challenge EntryPoint" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"` + EntryPoint string `description:"HTTP challenge EntryPoint" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"` } // TLSChallenge contains TLS challenge configuration. @@ -132,7 +134,7 @@ func (p *Provider) ListenConfiguration(config dynamic.Configuration) { // Init for compatibility reason the BaseProvider implements an empty Init. func (p *Provider) Init() error { - logger := log.With().Str(logs.ProviderName, p.ResolverName+".acme").Logger() + logger := log.With().Str(logs.ProviderName, p.ResolverName+resolverSuffix).Logger() if len(p.Configuration.Storage) == 0 { return errors.New("unable to initialize ACME provider with no storage location for the certificates") @@ -194,7 +196,7 @@ func (p *Provider) ThrottleDuration() time.Duration { // Provide allows the file provider to provide configurations to traefik // using the given Configuration channel. func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { - logger := log.With().Str(logs.ProviderName, p.ResolverName+".acme").Str("acmeCA", p.Configuration.CAServer). + logger := log.With().Str(logs.ProviderName, p.ResolverName+resolverSuffix).Str("acmeCA", p.Configuration.CAServer). Logger() ctx := logger.WithContext(context.Background()) @@ -236,7 +238,7 @@ func (p *Provider) getClient() (*lego.Client, error) { p.clientMutex.Lock() defer p.clientMutex.Unlock() - logger := log.With().Str(logs.ProviderName, p.ResolverName+".acme").Logger() + logger := log.With().Str(logs.ProviderName, p.ResolverName+resolverSuffix).Logger() ctx := logger.WithContext(context.Background()) @@ -407,7 +409,7 @@ func (p *Provider) resolveDomains(ctx context.Context, domains []string, tlsStor } func (p *Provider) watchNewDomains(ctx context.Context) { - rootLogger := log.Ctx(ctx).With().Str(logs.ProviderName, p.ResolverName+".acme").Logger() + rootLogger := log.Ctx(ctx).With().Str(logs.ProviderName, p.ResolverName+resolverSuffix).Str("ACME CA", p.Configuration.CAServer).Logger() ctx = rootLogger.WithContext(ctx) p.pool.GoCtx(func(ctxPool context.Context) { @@ -781,8 +783,8 @@ func (p *Provider) buildMessage() dynamic.Message { for _, cert := range p.certificates { certConf := &traefiktls.CertAndStores{ Certificate: traefiktls.Certificate{ - CertFile: traefiktls.FileOrContent(cert.Certificate.Certificate), - KeyFile: traefiktls.FileOrContent(cert.Key), + CertFile: types.FileOrContent(cert.Certificate.Certificate), + KeyFile: types.FileOrContent(cert.Key), }, Stores: []string{cert.Store}, } diff --git a/pkg/provider/acme/provider_test.go b/pkg/provider/acme/provider_test.go index a06f35c44..762ca3e9e 100644 --- a/pkg/provider/acme/provider_test.go +++ b/pkg/provider/acme/provider_test.go @@ -580,7 +580,7 @@ func TestInitAccount(t *testing.T) { acmeProvider := Provider{account: test.account, Configuration: &Configuration{Email: test.email, KeyType: test.keyType}} actualAccount, err := acmeProvider.initAccount(context.Background()) - assert.Nil(t, err, "Init account in error") + assert.NoError(t, err, "Init account in error") assert.Equal(t, test.expectedAccount.Email, actualAccount.Email, "unexpected email account") assert.Equal(t, test.expectedAccount.KeyType, actualAccount.KeyType, "unexpected keyType account") }) diff --git a/pkg/provider/acme/store.go b/pkg/provider/acme/store.go index 6fe899574..986510525 100644 --- a/pkg/provider/acme/store.go +++ b/pkg/provider/acme/store.go @@ -8,8 +8,8 @@ type StoredData struct { // Store is a generic interface that represents a storage. type Store interface { - GetAccount(string) (*Account, error) - SaveAccount(string, *Account) error - GetCertificates(string) ([]*CertAndStore, error) - SaveCertificates(string, []*CertAndStore) error + GetAccount(resolverName string) (*Account, error) + SaveAccount(resolverName string, account *Account) error + GetCertificates(resolverName string) ([]*CertAndStore, error) + SaveCertificates(resolverName string, certificates []*CertAndStore) error } diff --git a/pkg/provider/configuration.go b/pkg/provider/configuration.go index ebe57b92e..325afabc1 100644 --- a/pkg/provider/configuration.go +++ b/pkg/provider/configuration.go @@ -388,7 +388,7 @@ func BuildTCPRouterConfiguration(ctx context.Context, configuration *dynamic.TCP continue } - if len(router.Service) == 0 { + if router.Service == "" { if len(configuration.Services) > 1 { delete(configuration.Routers, routerName) loggerRouter.Error(). @@ -408,7 +408,7 @@ func BuildUDPRouterConfiguration(ctx context.Context, configuration *dynamic.UDP for routerName, router := range configuration.Routers { loggerRouter := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger() - if len(router.Service) > 0 { + if router.Service != "" { continue } @@ -454,9 +454,12 @@ func BuildRouterConfiguration(ctx context.Context, configuration *dynamic.HTTPCo delete(configuration.Routers, routerName) continue } + + // Flag default rule routers to add the denyRouterRecursion middleware. + router.DefaultRule = true } - if len(router.Service) == 0 { + if router.Service == "" { if len(configuration.Services) > 1 { delete(configuration.Routers, routerName) loggerRouter.Error(). diff --git a/pkg/provider/consulcatalog/config.go b/pkg/provider/consulcatalog/config.go index 14c4fcc87..a45696fd4 100644 --- a/pkg/provider/consulcatalog/config.go +++ b/pkg/provider/consulcatalog/config.go @@ -229,6 +229,7 @@ func (p *Provider) addServerTCP(item itemData, loadBalancer *dynamic.TCPServersL if item.ExtraConf.ConsulCatalog.Connect { loadBalancer.ServersTransport = itemServersTransportKey(item) + loadBalancer.Servers[0].TLS = true } loadBalancer.Servers[0].Address = net.JoinHostPort(item.Address, port) diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go index d1a606554..5370a75ca 100644 --- a/pkg/provider/consulcatalog/config_test.go +++ b/pkg/provider/consulcatalog/config_test.go @@ -12,6 +12,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" ) func Int(v int) *int { return &v } @@ -52,8 +53,9 @@ func TestDefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`foo.bar`)", + Service: "Test", + Rule: "Host(`foo.bar`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -106,8 +108,9 @@ func TestDefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: `Host("Test.foo.bar")`, + Service: "Test", + Rule: `Host("Test.foo.bar")`, + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -252,8 +255,9 @@ func TestDefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test`)", + Service: "Test", + Rule: "Host(`Test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -341,8 +345,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "dev-Test": { - Service: "dev-Test", - Rule: "Host(`dev-Test.traefik.wtf`)", + Service: "dev-Test", + Rule: "Host(`dev-Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -398,8 +403,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "dev-Test": { - Service: "dev-Test", - Rule: "Host(`dev-Test.traefik.wtf`)", + Service: "dev-Test", + Rule: "Host(`dev-Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -423,7 +429,7 @@ func Test_buildConfiguration(t *testing.T) { "tls-ns-dc1-dev-Test": { ServerName: "ns-dc1-dev/Test", InsecureSkipVerify: true, - RootCAs: []tls.FileOrContent{ + RootCAs: []types.FileOrContent{ "root", }, Certificates: []tls.Certificate{ @@ -485,8 +491,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "dev-Test": { - Service: "dev-Test", - Rule: "Host(`dev-Test.traefik.wtf`)", + Service: "dev-Test", + Rule: "Host(`dev-Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -513,7 +520,7 @@ func Test_buildConfiguration(t *testing.T) { "tls-ns-dc1-dev-Test": { ServerName: "ns-dc1-dev/Test", InsecureSkipVerify: true, - RootCAs: []tls.FileOrContent{ + RootCAs: []types.FileOrContent{ "root", }, Certificates: []tls.Certificate{ @@ -564,12 +571,14 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, "Test2": { - Service: "Test2", - Rule: "Host(`Test2.traefik.wtf`)", + Service: "Test2", + Rule: "Host(`Test2.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -641,8 +650,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -704,8 +714,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -764,8 +775,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -819,8 +831,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1101,8 +1114,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1159,8 +1173,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1207,8 +1222,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1262,8 +1278,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Services: map[string]*dynamic.Service{ @@ -1329,8 +1346,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{ @@ -1399,8 +1417,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1476,8 +1495,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1737,8 +1757,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1790,8 +1811,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2065,8 +2087,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2120,6 +2143,7 @@ func Test_buildConfiguration(t *testing.T) { "Test": { Service: "Test", Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, Middlewares: []string{"Middleware1"}, }, }, @@ -2245,6 +2269,7 @@ func Test_buildConfiguration(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "127.0.0.1:80", + TLS: true, }, }, ServersTransport: "tls-ns-dc1-Test", @@ -2256,7 +2281,7 @@ func Test_buildConfiguration(t *testing.T) { TLS: &dynamic.TLSClientConfig{ ServerName: "ns-dc1-Test", InsecureSkipVerify: true, - RootCAs: []tls.FileOrContent{ + RootCAs: []types.FileOrContent{ "root", }, Certificates: []tls.Certificate{ @@ -2538,8 +2563,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2625,8 +2651,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2828,12 +2855,14 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, "Test-97077516270503695": { - Service: "Test-97077516270503695", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test-97077516270503695", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2871,7 +2900,7 @@ func Test_buildConfiguration(t *testing.T) { "tls-ns-dc1-Test": { ServerName: "ns-dc1-Test", InsecureSkipVerify: true, - RootCAs: []tls.FileOrContent{ + RootCAs: []types.FileOrContent{ "root", }, Certificates: []tls.Certificate{ diff --git a/pkg/provider/consulcatalog/connect_tls.go b/pkg/provider/consulcatalog/connect_tls.go index 904131283..b73acb1a1 100644 --- a/pkg/provider/consulcatalog/connect_tls.go +++ b/pkg/provider/consulcatalog/connect_tls.go @@ -3,9 +3,9 @@ package consulcatalog import ( "fmt" - "github.com/hashicorp/consul/agent/connect" "github.com/traefik/traefik/v3/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" ) // connectCert holds our certificates as a client of the Consul Connect protocol. @@ -14,18 +14,18 @@ type connectCert struct { leaf keyPair } -func (c *connectCert) getRoot() []traefiktls.FileOrContent { - var result []traefiktls.FileOrContent +func (c *connectCert) getRoot() []types.FileOrContent { + var result []types.FileOrContent for _, r := range c.root { - result = append(result, traefiktls.FileOrContent(r)) + result = append(result, types.FileOrContent(r)) } return result } func (c *connectCert) getLeaf() traefiktls.Certificate { return traefiktls.Certificate{ - CertFile: traefiktls.FileOrContent(c.leaf.cert), - KeyFile: traefiktls.FileOrContent(c.leaf.key), + CertFile: types.FileOrContent(c.leaf.cert), + KeyFile: types.FileOrContent(c.leaf.key), } } @@ -52,11 +52,11 @@ func (c *connectCert) equals(other *connectCert) bool { } func (c *connectCert) serversTransport(item itemData) *dynamic.ServersTransport { - spiffeIDService := connect.SpiffeIDService{ - Namespace: item.Namespace, - Datacenter: item.Datacenter, - Service: item.Name, - } + spiffeID := fmt.Sprintf("spiffe:///ns/%s/dc/%s/svc/%s", + item.Namespace, + item.Datacenter, + item.Name, + ) return &dynamic.ServersTransport{ // This ensures that the config changes whenever the verifier function changes @@ -67,16 +67,16 @@ func (c *connectCert) serversTransport(item itemData) *dynamic.ServersTransport Certificates: traefiktls.Certificates{ c.getLeaf(), }, - PeerCertURI: spiffeIDService.URI().String(), + PeerCertURI: spiffeID, } } func (c *connectCert) tcpServersTransport(item itemData) *dynamic.TCPServersTransport { - spiffeIDService := connect.SpiffeIDService{ - Namespace: item.Namespace, - Datacenter: item.Datacenter, - Service: item.Name, - } + spiffeID := fmt.Sprintf("spiffe:///ns/%s/dc/%s/svc/%s", + item.Namespace, + item.Datacenter, + item.Name, + ) return &dynamic.TCPServersTransport{ TLS: &dynamic.TLSClientConfig{ @@ -88,7 +88,7 @@ func (c *connectCert) tcpServersTransport(item itemData) *dynamic.TCPServersTran Certificates: traefiktls.Certificates{ c.getLeaf(), }, - PeerCertURI: spiffeIDService.URI().String(), + PeerCertURI: spiffeID, }, } } diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index 829c5905d..d2c40851a 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -57,8 +57,9 @@ func TestDynConfBuilder_DefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`foo.bar`)", + Service: "Test", + Rule: "Host(`foo.bar`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -116,8 +117,9 @@ func TestDynConfBuilder_DefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.foo.bar`)", + Service: "Test", + Rule: "Host(`Test.foo.bar`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -177,8 +179,9 @@ func TestDynConfBuilder_DefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: `Host("Test.foo.bar")`, + Service: "Test", + Rule: `Host("Test.foo.bar")`, + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -344,8 +347,9 @@ func TestDynConfBuilder_DefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test`)", + Service: "Test", + Rule: "Host(`Test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -566,8 +570,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -640,12 +645,14 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, "Test2": { - Service: "Test2", - Rule: "Host(`Test2.traefik.wtf`)", + Service: "Test2", + Rule: "Host(`Test2.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -733,8 +740,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -796,8 +804,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1190,8 +1199,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1275,8 +1285,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1341,8 +1352,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1404,8 +1416,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Services: map[string]*dynamic.Service{ @@ -1490,8 +1503,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{ @@ -1579,8 +1593,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1681,8 +1696,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2094,8 +2110,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2155,8 +2172,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2417,8 +2435,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2658,8 +2677,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2722,6 +2742,7 @@ func TestDynConfBuilder_build(t *testing.T) { Service: "Test", Rule: "Host(`Test.traefik.wtf`)", Middlewares: []string{"Middleware1"}, + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{ @@ -3178,8 +3199,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -3352,8 +3374,9 @@ func TestDynConfBuilder_build(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, diff --git a/pkg/provider/ecs/config_test.go b/pkg/provider/ecs/config_test.go index aac4d9cfd..c840c5dad 100644 --- a/pkg/provider/ecs/config_test.go +++ b/pkg/provider/ecs/config_test.go @@ -53,8 +53,9 @@ func TestDefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`foo.bar`)", + Service: "Test", + Rule: "Host(`foo.bar`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -107,8 +108,9 @@ func TestDefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.foo.bar`)", + Service: "Test", + Rule: "Host(`Test.foo.bar`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -163,8 +165,9 @@ func TestDefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: `Host("Test.foo.bar")`, + Service: "Test", + Rule: `Host("Test.foo.bar")`, + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -315,8 +318,9 @@ func TestDefaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test`)", + Service: "Test", + Rule: "Host(`Test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -512,8 +516,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -576,12 +581,14 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, "Test2": { - Service: "Test2", - Rule: "Host(`Test2.traefik.wtf`)", + Service: "Test2", + Rule: "Host(`Test2.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -659,8 +666,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -717,8 +725,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1076,8 +1085,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1146,8 +1156,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1202,8 +1213,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1260,8 +1272,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Services: map[string]*dynamic.Service{ @@ -1336,8 +1349,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{ @@ -1415,8 +1429,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1502,8 +1517,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1866,8 +1882,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1922,8 +1939,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1978,8 +1996,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2388,8 +2407,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2447,6 +2467,7 @@ func Test_buildConfiguration(t *testing.T) { Service: "Test", Rule: "Host(`Test.traefik.wtf`)", Middlewares: []string{"Middleware1"}, + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{ @@ -2863,8 +2884,9 @@ func Test_buildConfiguration(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.wtf`)", + Service: "Service1", + Rule: "Host(`Test.traefik.wtf`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index 938252664..ad19f573e 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -11,6 +11,7 @@ import ( "text/template" "github.com/Masterminds/sprig/v3" + "github.com/fsnotify/fsnotify" "github.com/rs/zerolog/log" "github.com/traefik/paerser/file" "github.com/traefik/traefik/v3/pkg/config/dynamic" @@ -18,7 +19,7 @@ import ( "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/tls" - "gopkg.in/fsnotify.v1" + "github.com/traefik/traefik/v3/pkg/types" ) const providerName = "file" @@ -67,7 +68,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. configuration, err := p.BuildConfiguration() if err != nil { if p.Watch { - log.Debug(). + log.Error(). Str(logs.ProviderName, providerName). Err(err). Msg("Error while building configuration (for the first time)") @@ -180,7 +181,7 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem // TLS Options if configuration.TLS.Options != nil { for name, options := range configuration.TLS.Options { - var caCerts []tls.FileOrContent + var caCerts []types.FileOrContent for _, caFile := range options.ClientAuth.CAFiles { content, err := caFile.Read() @@ -189,7 +190,7 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem continue } - caCerts = append(caCerts, tls.FileOrContent(content)) + caCerts = append(caCerts, types.FileOrContent(content)) } options.ClientAuth.CAFiles = caCerts @@ -209,14 +210,14 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem log.Ctx(ctx).Error().Err(err).Send() continue } - store.DefaultCertificate.CertFile = tls.FileOrContent(content) + store.DefaultCertificate.CertFile = types.FileOrContent(content) content, err = store.DefaultCertificate.KeyFile.Read() if err != nil { log.Ctx(ctx).Error().Err(err).Send() continue } - store.DefaultCertificate.KeyFile = tls.FileOrContent(content) + store.DefaultCertificate.KeyFile = types.FileOrContent(content) configuration.TLS.Stores[name] = store } @@ -233,21 +234,21 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem log.Ctx(ctx).Error().Err(err).Send() continue } - cert.CertFile = tls.FileOrContent(content) + cert.CertFile = types.FileOrContent(content) content, err = cert.KeyFile.Read() if err != nil { log.Ctx(ctx).Error().Err(err).Send() continue } - cert.KeyFile = tls.FileOrContent(content) + cert.KeyFile = types.FileOrContent(content) certificates = append(certificates, cert) } configuration.HTTP.ServersTransports[name].Certificates = certificates - var rootCAs []tls.FileOrContent + var rootCAs []types.FileOrContent for _, rootCA := range st.RootCAs { content, err := rootCA.Read() if err != nil { @@ -255,7 +256,7 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem continue } - rootCAs = append(rootCAs, tls.FileOrContent(content)) + rootCAs = append(rootCAs, types.FileOrContent(content)) } st.RootCAs = rootCAs @@ -275,21 +276,21 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem log.Ctx(ctx).Error().Err(err).Send() continue } - cert.CertFile = tls.FileOrContent(content) + cert.CertFile = types.FileOrContent(content) content, err = cert.KeyFile.Read() if err != nil { log.Ctx(ctx).Error().Err(err).Send() continue } - cert.KeyFile = tls.FileOrContent(content) + cert.KeyFile = types.FileOrContent(content) certificates = append(certificates, cert) } configuration.TCP.ServersTransports[name].TLS.Certificates = certificates - var rootCAs []tls.FileOrContent + var rootCAs []types.FileOrContent for _, rootCA := range st.TLS.RootCAs { content, err := rootCA.Read() if err != nil { @@ -297,7 +298,7 @@ func (p *Provider) loadFileConfig(ctx context.Context, filename string, parseTem continue } - rootCAs = append(rootCAs, tls.FileOrContent(content)) + rootCAs = append(rootCAs, types.FileOrContent(content)) } st.TLS.RootCAs = rootCAs @@ -315,14 +316,14 @@ func flattenCertificates(ctx context.Context, tlsConfig *dynamic.TLSConfiguratio log.Ctx(ctx).Error().Err(err).Send() continue } - cert.Certificate.CertFile = tls.FileOrContent(string(content)) + cert.Certificate.CertFile = types.FileOrContent(string(content)) content, err = cert.Certificate.KeyFile.Read() if err != nil { log.Ctx(ctx).Error().Err(err).Send() continue } - cert.Certificate.KeyFile = tls.FileOrContent(string(content)) + cert.Certificate.KeyFile = types.FileOrContent(string(content)) certs = append(certs, cert) } diff --git a/pkg/provider/file/file_test.go b/pkg/provider/file/file_test.go index 961024af9..3cb4a0490 100644 --- a/pkg/provider/file/file_test.go +++ b/pkg/provider/file/file_test.go @@ -156,10 +156,10 @@ func TestProvideWithWatch(t *testing.T) { require.NotNil(t, conf.Configuration.HTTP) numServices := len(conf.Configuration.HTTP.Services) + len(conf.Configuration.TCP.Services) + len(conf.Configuration.UDP.Services) numRouters := len(conf.Configuration.HTTP.Routers) + len(conf.Configuration.TCP.Routers) + len(conf.Configuration.UDP.Routers) - assert.Equal(t, numServices, 0) - assert.Equal(t, numRouters, 0) + assert.Equal(t, 0, numServices) + assert.Equal(t, 0, numRouters) require.NotNil(t, conf.Configuration.TLS) - assert.Len(t, conf.Configuration.TLS.Certificates, 0) + assert.Empty(t, conf.Configuration.TLS.Certificates) case <-timeout: t.Errorf("timeout while waiting for config") } diff --git a/pkg/provider/http/http_test.go b/pkg/provider/http/http_test.go index d805dd3c4..f99706ba9 100644 --- a/pkg/provider/http/http_test.go +++ b/pkg/provider/http/http_test.go @@ -268,5 +268,5 @@ func TestProvider_ProvideConfigurationOnlyOnceIfUnchanged(t *testing.T) { time.Sleep(time.Second) - assert.Equal(t, 1, len(configurationChan)) + assert.Len(t, configurationChan, 1) } diff --git a/pkg/provider/kubernetes/crd/client.go b/pkg/provider/kubernetes/crd/client.go index d52588536..83c4834ba 100644 --- a/pkg/provider/kubernetes/crd/client.go +++ b/pkg/provider/kubernetes/crd/client.go @@ -13,6 +13,7 @@ import ( traefikinformers "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/informers/externalversions" traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" + "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" corev1 "k8s.io/api/core/v1" kerror "k8s.io/apimachinery/pkg/api/errors" @@ -120,14 +121,19 @@ func newExternalClusterClientFromFile(file string) (*clientWrapper, error) { // newExternalClusterClient returns a new Provider client that may run outside // of the cluster. // The endpoint parameter must not be empty. -func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrapper, error) { +func newExternalClusterClient(endpoint, caFilePath string, token types.FileOrContent) (*clientWrapper, error) { if endpoint == "" { return nil, errors.New("endpoint missing for external cluster client") } + tokenData, err := token.Read() + if err != nil { + return nil, fmt.Errorf("read token: %w", err) + } + config := &rest.Config{ Host: endpoint, - BearerToken: token, + BearerToken: string(tokenData), } if caFilePath != "" { diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/clientset.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/clientset.go index ec71200a7..10fa5352f 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/clientset.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/clientset.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -28,6 +28,7 @@ package versioned import ( "fmt" + "net/http" traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1" discovery "k8s.io/client-go/discovery" @@ -40,8 +41,7 @@ type Interface interface { TraefikV1alpha1() traefikv1alpha1.TraefikV1alpha1Interface } -// Clientset contains the clients for groups. Each group has exactly one -// version included in a Clientset. +// Clientset contains the clients for groups. type Clientset struct { *discovery.DiscoveryClient traefikV1alpha1 *traefikv1alpha1.TraefikV1alpha1Client @@ -63,22 +63,45 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface { // NewForConfig creates a new Clientset for the given config. // If config's RateLimiter is not set and QPS and Burst are acceptable, // NewForConfig will generate a rate-limiter in configShallowCopy. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*Clientset, error) { configShallowCopy := *c + + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + + // share the transport between all clients + httpClient, err := rest.HTTPClientFor(&configShallowCopy) + if err != nil { + return nil, err + } + + return NewForConfigAndClient(&configShallowCopy, httpClient) +} + +// NewForConfigAndClient creates a new Clientset for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. +func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { + configShallowCopy := *c if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { if configShallowCopy.Burst <= 0 { return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") } configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) } + var cs Clientset var err error - cs.traefikV1alpha1, err = traefikv1alpha1.NewForConfig(&configShallowCopy) + cs.traefikV1alpha1, err = traefikv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err } - cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err } @@ -88,11 +111,11 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { // NewForConfigOrDie creates a new Clientset for the given config and // panics if there is an error in the config. func NewForConfigOrDie(c *rest.Config) *Clientset { - var cs Clientset - cs.traefikV1alpha1 = traefikv1alpha1.NewForConfigOrDie(c) - - cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) - return &cs + cs, err := NewForConfig(c) + if err != nil { + panic(err) + } + return cs } // New creates a new Clientset for the given RESTClient. diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/doc.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/doc.go deleted file mode 100644 index b6f98d475..000000000 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/doc.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -The MIT License (MIT) - -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated clientset. -package versioned diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/clientset_generated.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/clientset_generated.go index 55b5edcb2..deca4ddde 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/clientset_generated.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -82,7 +82,10 @@ func (c *Clientset) Tracker() testing.ObjectTracker { return c.tracker } -var _ clientset.Interface = &Clientset{} +var ( + _ clientset.Interface = &Clientset{} + _ testing.FakeClient = &Clientset{} +) // TraefikV1alpha1 retrieves the TraefikV1alpha1Client func (c *Clientset) TraefikV1alpha1() traefikv1alpha1.TraefikV1alpha1Interface { diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/doc.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/doc.go index 081d07edb..ae4bdb20e 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/doc.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/doc.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/register.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/register.go index c25b16318..afa72ae48 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/register.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/register.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/doc.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/doc.go index 87af68273..ba3753967 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/doc.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/doc.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/register.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/register.go index cb048b154..4d0c0dff7 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/register.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/register.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/doc.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/doc.go index e39212eab..91c1c1d8a 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/doc.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/doc.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/doc.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/doc.go index 24ecc2be0..1964e2084 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/doc.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/doc.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressroute.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressroute.go index b01905713..09247d99d 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressroute.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressroute.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeIngressRoutes struct { ns string } -var ingressroutesResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "ingressroutes"} +var ingressroutesResource = v1alpha1.SchemeGroupVersion.WithResource("ingressroutes") -var ingressroutesKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "IngressRoute"} +var ingressroutesKind = v1alpha1.SchemeGroupVersion.WithKind("IngressRoute") // Get takes name of the ingressRoute, and returns the corresponding ingressRoute object, and an error if there is any. func (c *FakeIngressRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IngressRoute, err error) { @@ -113,7 +112,7 @@ func (c *FakeIngressRoutes) Update(ctx context.Context, ingressRoute *v1alpha1.I // Delete takes name of the ingressRoute and deletes it. Returns an error if one occurs. func (c *FakeIngressRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(ingressroutesResource, c.ns, name), &v1alpha1.IngressRoute{}) + Invokes(testing.NewDeleteActionWithOptions(ingressroutesResource, c.ns, name, opts), &v1alpha1.IngressRoute{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressroutetcp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressroutetcp.go index 394b1ff55..d8146c20a 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressroutetcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeIngressRouteTCPs struct { ns string } -var ingressroutetcpsResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "ingressroutetcps"} +var ingressroutetcpsResource = v1alpha1.SchemeGroupVersion.WithResource("ingressroutetcps") -var ingressroutetcpsKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "IngressRouteTCP"} +var ingressroutetcpsKind = v1alpha1.SchemeGroupVersion.WithKind("IngressRouteTCP") // Get takes name of the ingressRouteTCP, and returns the corresponding ingressRouteTCP object, and an error if there is any. func (c *FakeIngressRouteTCPs) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IngressRouteTCP, err error) { @@ -113,7 +112,7 @@ func (c *FakeIngressRouteTCPs) Update(ctx context.Context, ingressRouteTCP *v1al // Delete takes name of the ingressRouteTCP and deletes it. Returns an error if one occurs. func (c *FakeIngressRouteTCPs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(ingressroutetcpsResource, c.ns, name), &v1alpha1.IngressRouteTCP{}) + Invokes(testing.NewDeleteActionWithOptions(ingressroutetcpsResource, c.ns, name, opts), &v1alpha1.IngressRouteTCP{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressrouteudp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressrouteudp.go index f67d889de..a91576d54 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_ingressrouteudp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeIngressRouteUDPs struct { ns string } -var ingressrouteudpsResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "ingressrouteudps"} +var ingressrouteudpsResource = v1alpha1.SchemeGroupVersion.WithResource("ingressrouteudps") -var ingressrouteudpsKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "IngressRouteUDP"} +var ingressrouteudpsKind = v1alpha1.SchemeGroupVersion.WithKind("IngressRouteUDP") // Get takes name of the ingressRouteUDP, and returns the corresponding ingressRouteUDP object, and an error if there is any. func (c *FakeIngressRouteUDPs) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IngressRouteUDP, err error) { @@ -113,7 +112,7 @@ func (c *FakeIngressRouteUDPs) Update(ctx context.Context, ingressRouteUDP *v1al // Delete takes name of the ingressRouteUDP and deletes it. Returns an error if one occurs. func (c *FakeIngressRouteUDPs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(ingressrouteudpsResource, c.ns, name), &v1alpha1.IngressRouteUDP{}) + Invokes(testing.NewDeleteActionWithOptions(ingressrouteudpsResource, c.ns, name, opts), &v1alpha1.IngressRouteUDP{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_middleware.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_middleware.go index efef604fd..4150d8fe2 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_middleware.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_middleware.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeMiddlewares struct { ns string } -var middlewaresResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "middlewares"} +var middlewaresResource = v1alpha1.SchemeGroupVersion.WithResource("middlewares") -var middlewaresKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "Middleware"} +var middlewaresKind = v1alpha1.SchemeGroupVersion.WithKind("Middleware") // Get takes name of the middleware, and returns the corresponding middleware object, and an error if there is any. func (c *FakeMiddlewares) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Middleware, err error) { @@ -113,7 +112,7 @@ func (c *FakeMiddlewares) Update(ctx context.Context, middleware *v1alpha1.Middl // Delete takes name of the middleware and deletes it. Returns an error if one occurs. func (c *FakeMiddlewares) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(middlewaresResource, c.ns, name), &v1alpha1.Middleware{}) + Invokes(testing.NewDeleteActionWithOptions(middlewaresResource, c.ns, name, opts), &v1alpha1.Middleware{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_middlewaretcp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_middlewaretcp.go index 767405b0e..83197b7d0 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_middlewaretcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeMiddlewareTCPs struct { ns string } -var middlewaretcpsResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "middlewaretcps"} +var middlewaretcpsResource = v1alpha1.SchemeGroupVersion.WithResource("middlewaretcps") -var middlewaretcpsKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "MiddlewareTCP"} +var middlewaretcpsKind = v1alpha1.SchemeGroupVersion.WithKind("MiddlewareTCP") // Get takes name of the middlewareTCP, and returns the corresponding middlewareTCP object, and an error if there is any. func (c *FakeMiddlewareTCPs) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.MiddlewareTCP, err error) { @@ -113,7 +112,7 @@ func (c *FakeMiddlewareTCPs) Update(ctx context.Context, middlewareTCP *v1alpha1 // Delete takes name of the middlewareTCP and deletes it. Returns an error if one occurs. func (c *FakeMiddlewareTCPs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(middlewaretcpsResource, c.ns, name), &v1alpha1.MiddlewareTCP{}) + Invokes(testing.NewDeleteActionWithOptions(middlewaretcpsResource, c.ns, name, opts), &v1alpha1.MiddlewareTCP{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_serverstransport.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_serverstransport.go index 3666bb4a5..3f3c187bd 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_serverstransport.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_serverstransport.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeServersTransports struct { ns string } -var serverstransportsResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "serverstransports"} +var serverstransportsResource = v1alpha1.SchemeGroupVersion.WithResource("serverstransports") -var serverstransportsKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "ServersTransport"} +var serverstransportsKind = v1alpha1.SchemeGroupVersion.WithKind("ServersTransport") // Get takes name of the serversTransport, and returns the corresponding serversTransport object, and an error if there is any. func (c *FakeServersTransports) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ServersTransport, err error) { @@ -113,7 +112,7 @@ func (c *FakeServersTransports) Update(ctx context.Context, serversTransport *v1 // Delete takes name of the serversTransport and deletes it. Returns an error if one occurs. func (c *FakeServersTransports) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(serverstransportsResource, c.ns, name), &v1alpha1.ServersTransport{}) + Invokes(testing.NewDeleteActionWithOptions(serverstransportsResource, c.ns, name, opts), &v1alpha1.ServersTransport{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_serverstransporttcp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_serverstransporttcp.go index e18ae4db1..644e04403 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_serverstransporttcp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_serverstransporttcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeServersTransportTCPs struct { ns string } -var serverstransporttcpsResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "serverstransporttcps"} +var serverstransporttcpsResource = v1alpha1.SchemeGroupVersion.WithResource("serverstransporttcps") -var serverstransporttcpsKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "ServersTransportTCP"} +var serverstransporttcpsKind = v1alpha1.SchemeGroupVersion.WithKind("ServersTransportTCP") // Get takes name of the serversTransportTCP, and returns the corresponding serversTransportTCP object, and an error if there is any. func (c *FakeServersTransportTCPs) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ServersTransportTCP, err error) { @@ -113,7 +112,7 @@ func (c *FakeServersTransportTCPs) Update(ctx context.Context, serversTransportT // Delete takes name of the serversTransportTCP and deletes it. Returns an error if one occurs. func (c *FakeServersTransportTCPs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(serverstransporttcpsResource, c.ns, name), &v1alpha1.ServersTransportTCP{}) + Invokes(testing.NewDeleteActionWithOptions(serverstransporttcpsResource, c.ns, name, opts), &v1alpha1.ServersTransportTCP{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_tlsoption.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_tlsoption.go index 182812ff9..d1fb2d073 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_tlsoption.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_tlsoption.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeTLSOptions struct { ns string } -var tlsoptionsResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "tlsoptions"} +var tlsoptionsResource = v1alpha1.SchemeGroupVersion.WithResource("tlsoptions") -var tlsoptionsKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "TLSOption"} +var tlsoptionsKind = v1alpha1.SchemeGroupVersion.WithKind("TLSOption") // Get takes name of the tLSOption, and returns the corresponding tLSOption object, and an error if there is any. func (c *FakeTLSOptions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TLSOption, err error) { @@ -113,7 +112,7 @@ func (c *FakeTLSOptions) Update(ctx context.Context, tLSOption *v1alpha1.TLSOpti // Delete takes name of the tLSOption and deletes it. Returns an error if one occurs. func (c *FakeTLSOptions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(tlsoptionsResource, c.ns, name), &v1alpha1.TLSOption{}) + Invokes(testing.NewDeleteActionWithOptions(tlsoptionsResource, c.ns, name, opts), &v1alpha1.TLSOption{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_tlsstore.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_tlsstore.go index 09bd1f827..15f78c74e 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_tlsstore.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_tlsstore.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeTLSStores struct { ns string } -var tlsstoresResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "tlsstores"} +var tlsstoresResource = v1alpha1.SchemeGroupVersion.WithResource("tlsstores") -var tlsstoresKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "TLSStore"} +var tlsstoresKind = v1alpha1.SchemeGroupVersion.WithKind("TLSStore") // Get takes name of the tLSStore, and returns the corresponding tLSStore object, and an error if there is any. func (c *FakeTLSStores) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TLSStore, err error) { @@ -113,7 +112,7 @@ func (c *FakeTLSStores) Update(ctx context.Context, tLSStore *v1alpha1.TLSStore, // Delete takes name of the tLSStore and deletes it. Returns an error if one occurs. func (c *FakeTLSStores) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(tlsstoresResource, c.ns, name), &v1alpha1.TLSStore{}) + Invokes(testing.NewDeleteActionWithOptions(tlsstoresResource, c.ns, name, opts), &v1alpha1.TLSStore{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_traefikio_client.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_traefikio_client.go index 6e6ca7d86..0b7af0e83 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_traefikio_client.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_traefikio_client.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_traefikservice.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_traefikservice.go index ef3cf30f3..2996ad872 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_traefikservice.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/fake/fake_traefikservice.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,6 @@ import ( v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" @@ -44,9 +43,9 @@ type FakeTraefikServices struct { ns string } -var traefikservicesResource = schema.GroupVersionResource{Group: "traefik.io", Version: "v1alpha1", Resource: "traefikservices"} +var traefikservicesResource = v1alpha1.SchemeGroupVersion.WithResource("traefikservices") -var traefikservicesKind = schema.GroupVersionKind{Group: "traefik.io", Version: "v1alpha1", Kind: "TraefikService"} +var traefikservicesKind = v1alpha1.SchemeGroupVersion.WithKind("TraefikService") // Get takes name of the traefikService, and returns the corresponding traefikService object, and an error if there is any. func (c *FakeTraefikServices) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TraefikService, err error) { @@ -113,7 +112,7 @@ func (c *FakeTraefikServices) Update(ctx context.Context, traefikService *v1alph // Delete takes name of the traefikService and deletes it. Returns an error if one occurs. func (c *FakeTraefikServices) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. - Invokes(testing.NewDeleteAction(traefikservicesResource, c.ns, name), &v1alpha1.TraefikService{}) + Invokes(testing.NewDeleteActionWithOptions(traefikservicesResource, c.ns, name, opts), &v1alpha1.TraefikService{}) return err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/generated_expansion.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/generated_expansion.go index 0fbf0a82f..37a6da2d4 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/generated_expansion.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/generated_expansion.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressroute.go index 6ec921760..0f91dac03 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressroute.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressroutetcp.go index 93caa16aa..88d0af505 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressroutetcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressrouteudp.go index 74dab24ac..8ce614347 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/ingressrouteudp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/middleware.go index 615f8d200..a4b1333a7 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/middleware.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/middlewaretcp.go index ca4c596f9..39bf46e4a 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/middlewaretcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/serverstransport.go index cc1b467fa..e38d19c00 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/serverstransport.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/serverstransporttcp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/serverstransporttcp.go index ba1c1bd07..ba36b32bb 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/serverstransporttcp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/serverstransporttcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/tlsoption.go index 24ba147f1..c5b5f7c15 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/tlsoption.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/tlsstore.go index 1f66fb482..f67d3b487 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/tlsstore.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/tlsstore.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/traefikio_client.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/traefikio_client.go index 6de07c33c..94f7b189a 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/traefikio_client.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/traefikio_client.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,8 @@ THE SOFTWARE. package v1alpha1 import ( + "net/http" + "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme" v1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" rest "k8s.io/client-go/rest" @@ -92,12 +94,28 @@ func (c *TraefikV1alpha1Client) TraefikServices(namespace string) TraefikService } // NewForConfig creates a new TraefikV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*TraefikV1alpha1Client, error) { config := *c if err := setConfigDefaults(&config); err != nil { return nil, err } - client, err := rest.RESTClientFor(&config) + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new TraefikV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*TraefikV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) if err != nil { return nil, err } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/traefikservice.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/traefikservice.go index e83889472..a10b7b9db 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/traefikservice.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefikio/v1alpha1/traefikservice.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/factory.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/factory.go index 75b79d658..56302b197 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/factory.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/factory.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -55,6 +55,11 @@ type sharedInformerFactory struct { // startedInformers is used for tracking which informers have been started. // This allows Start() to be called multiple times safely. startedInformers map[reflect.Type]bool + // wg tracks how many goroutines were started. + wg sync.WaitGroup + // shuttingDown is true when Shutdown has been called. It may still be running + // because it needs to wait for goroutines. + shuttingDown bool } // WithCustomResyncConfig sets a custom resync period for the specified informer types. @@ -115,20 +120,39 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy return factory } -// Start initializes all requested informers. func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { f.lock.Lock() defer f.lock.Unlock() + if f.shuttingDown { + return + } + for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - go informer.Run(stopCh) + f.wg.Add(1) + // We need a new variable in each loop iteration, + // otherwise the goroutine would use the loop variable + // and that keeps changing. + informer := informer + go func() { + defer f.wg.Done() + informer.Run(stopCh) + }() f.startedInformers[informerType] = true } } } -// WaitForCacheSync waits for all started informers' cache were synced. +func (f *sharedInformerFactory) Shutdown() { + f.lock.Lock() + f.shuttingDown = true + f.lock.Unlock() + + // Will return immediately if there is nothing to wait for. + f.wg.Wait() +} + func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() @@ -150,7 +174,7 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref return res } -// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// InformerFor returns the SharedIndexInformer for obj using an internal // client. func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { f.lock.Lock() @@ -175,11 +199,58 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // SharedInformerFactory provides shared informers for resources in all known // API group versions. +// +// It is typically used like this: +// +// ctx, cancel := context.Background() +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.WaitForStop() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// factory.Start(ctx.Done()) // Start processing these informers. +// synced := factory.WaitForCacheSync(ctx.Done()) +// for v, ok := range synced { +// if !ok { +// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) +// return +// } +// } +// +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.Start(ctx.Done()) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory - ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // Start initializes all requested informers. They are handled in goroutines + // which run until the stop channel gets closed. + Start(stopCh <-chan struct{}) + + // Shutdown marks a factory as shutting down. At that point no new + // informers can be started anymore and Start will return without + // doing anything. + // + // In addition, Shutdown blocks until all goroutines have terminated. For that + // to happen, the close channel(s) that they were started with must be closed, + // either before Shutdown gets called or while it is waiting. + // + // Shutdown may be called multiple times, even concurrently. All such calls will + // block until all goroutines have terminated. + Shutdown() + + // WaitForCacheSync blocks until all started informers' caches were synced + // or the stop channel gets closed. WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + // ForResource gives generic access to a shared informer of the matching type. + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // InformerFor returns the SharedIndexInformer for obj using an internal + // client. + InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + Traefik() traefikio.Interface } diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go index 93ba25251..1739b5ab4 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces/factory_interfaces.go index 6af7a12d8..60400b890 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/interface.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/interface.go index 22009c56e..b38008a94 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/interface.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/interface.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressroute.go index c2293518f..273659939 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressroute.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressroutetcp.go index 0ed00501a..f891af0cb 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressroutetcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressrouteudp.go index 68787c969..c5d5258fc 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/ingressrouteudp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/interface.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/interface.go index a611e3c78..ae821c598 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/interface.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/interface.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/middleware.go index 9a00c146b..c889244b6 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/middleware.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/middlewaretcp.go index 8b547fc45..4fa7f41aa 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/middlewaretcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/serverstransport.go index cd76b4457..9ed0ab1b2 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/serverstransport.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/serverstransporttcp.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/serverstransporttcp.go index 467e2fad1..0c3461ae5 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/serverstransporttcp.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/serverstransporttcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/tlsoption.go index 34336133b..c93a8f924 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/tlsoption.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/tlsstore.go index 56314fa20..8817b5b0c 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/tlsstore.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/tlsstore.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/traefikservice.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/traefikservice.go index 59dd21c71..d80ff4b05 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/traefikservice.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefikio/v1alpha1/traefikservice.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/expansion_generated.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/expansion_generated.go index a8df13373..fe402a3c5 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/expansion_generated.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/expansion_generated.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressroute.go index dbbcd28fe..9ef8d2234 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressroute.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressroutetcp.go index 63ffc06b1..399830bd4 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressroutetcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressrouteudp.go index 7156f8bf9..6e4c5ce6c 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/ingressrouteudp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/middleware.go index c69ef6bea..ead5dfca0 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/middleware.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/middlewaretcp.go index 388537233..d7e6fbc27 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/middlewaretcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/serverstransport.go index 2532091fa..35b5a5010 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/serverstransport.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/serverstransporttcp.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/serverstransporttcp.go index 2b2354e1a..70ba73041 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/serverstransporttcp.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/serverstransporttcp.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/tlsoption.go index 520c95da1..60dd53d8d 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/tlsoption.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/tlsstore.go index 9687ba492..b3cfa733c 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/tlsstore.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/tlsstore.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/traefikservice.go b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/traefikservice.go index cb4331c10..7c45e05d5 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/traefikservice.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefikio/v1alpha1/traefikservice.go @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 0e22a9390..45b6634af 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -48,16 +48,16 @@ const ( // Provider holds configurations of the provider. type Provider struct { - Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` - CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` - Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` - AllowCrossNamespace bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"` - AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"` - LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` - IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` - ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` - AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Token types.FileOrContent `description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` + CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` + Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` + AllowCrossNamespace bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"` + AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"` + LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` + IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` + ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` + AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` lastConfiguration safe.Safe @@ -101,7 +101,7 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) default: log.Ctx(ctx).Info().Msgf("Creating cluster-external Provider client%s", withEndpoint) - client, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) + client, err = newExternalClusterClient(p.Endpoint, p.CertAuthFilePath, p.Token) } if err != nil { @@ -288,6 +288,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) ReplacePath: middleware.Spec.ReplacePath, ReplacePathRegex: middleware.Spec.ReplacePathRegex, Chain: createChainMiddleware(ctxMid, middleware.Namespace, middleware.Spec.Chain), + IPWhiteList: middleware.Spec.IPWhiteList, IPAllowList: middleware.Spec.IPAllowList, Headers: middleware.Spec.Headers, Errors: errorPage, @@ -314,6 +315,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) conf.TCP.Middlewares[id] = &dynamic.TCPMiddleware{ InFlightConn: middlewareTCP.Spec.InFlightConn, + IPWhiteList: middlewareTCP.Spec.IPWhiteList, IPAllowList: middlewareTCP.Spec.IPAllowList, } } @@ -337,7 +339,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) for _, serversTransport := range client.GetServersTransports() { logger := log.Ctx(ctx).With().Str(logs.ServersTransportName, serversTransport.Name).Logger() - var rootCAs []tls.FileOrContent + var rootCAs []types.FileOrContent for _, secret := range serversTransport.Spec.RootCAsSecrets { caSecret, err := loadCASecret(serversTransport.Namespace, secret, client) if err != nil { @@ -345,7 +347,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) continue } - rootCAs = append(rootCAs, tls.FileOrContent(caSecret)) + rootCAs = append(rootCAs, types.FileOrContent(caSecret)) } var certs tls.Certificates @@ -357,8 +359,8 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) } certs = append(certs, tls.Certificate{ - CertFile: tls.FileOrContent(tlsSecret), - KeyFile: tls.FileOrContent(tlsKey), + CertFile: types.FileOrContent(tlsSecret), + KeyFile: types.FileOrContent(tlsKey), }) } @@ -444,7 +446,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) } if serversTransportTCP.Spec.TLS != nil { - var rootCAs []tls.FileOrContent + var rootCAs []types.FileOrContent for _, secret := range serversTransportTCP.Spec.TLS.RootCAsSecrets { caSecret, err := loadCASecret(serversTransportTCP.Namespace, secret, client) if err != nil { @@ -455,7 +457,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) continue } - rootCAs = append(rootCAs, tls.FileOrContent(caSecret)) + rootCAs = append(rootCAs, types.FileOrContent(caSecret)) } var certs tls.Certificates @@ -470,8 +472,8 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) } certs = append(certs, tls.Certificate{ - CertFile: tls.FileOrContent(tlsCert), - KeyFile: tls.FileOrContent(tlsKey), + CertFile: types.FileOrContent(tlsCert), + KeyFile: types.FileOrContent(tlsKey), }) } @@ -726,6 +728,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef AuthResponseHeaders: auth.AuthResponseHeaders, AuthResponseHeadersRegex: auth.AuthResponseHeadersRegex, AuthRequestHeaders: auth.AuthRequestHeaders, + AddAuthCookiesToResponse: auth.AddAuthCookiesToResponse, } if auth.TLS == nil { @@ -961,7 +964,7 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options for _, tlsOption := range tlsOptionsCRD { logger := log.Ctx(ctx).With().Str("tlsOption", tlsOption.Name).Str("namespace", tlsOption.Namespace).Logger() - var clientCAs []tls.FileOrContent + var clientCAs []types.FileOrContent for _, secretName := range tlsOption.Spec.ClientAuth.SecretNames { secret, exists, err := client.GetSecret(tlsOption.Namespace, secretName) @@ -981,7 +984,7 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options continue } - clientCAs = append(clientCAs, tls.FileOrContent(cert)) + clientCAs = append(clientCAs, types.FileOrContent(cert)) } id := makeID(tlsOption.Namespace, tlsOption.Name) @@ -1061,8 +1064,8 @@ func buildTLSStores(ctx context.Context, client Client) (map[string]tls.Store, m } tlsStore.DefaultCertificate = &tls.Certificate{ - CertFile: tls.FileOrContent(cert), - KeyFile: tls.FileOrContent(key), + CertFile: types.FileOrContent(cert), + KeyFile: types.FileOrContent(key), } } @@ -1147,8 +1150,8 @@ func getTLS(k8sClient Client, secretName, namespace string) (*tls.CertAndStores, return &tls.CertAndStores{ Certificate: tls.Certificate{ - CertFile: tls.FileOrContent(cert), - KeyFile: tls.FileOrContent(key), + CertFile: types.FileOrContent(cert), + KeyFile: types.FileOrContent(key), }, }, nil } diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 23a038b2d..a6d2c64ca 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -571,8 +571,8 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -673,9 +673,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { "TLS_RSA_WITH_AES_256_GCM_SHA384", }, ClientAuth: tls.ClientAuth{ - CAFiles: []tls.FileOrContent{ - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + CAFiles: []types.FileOrContent{ + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), }, ClientAuthType: "VerifyClientCertIfGiven", }, @@ -741,9 +741,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { "TLS_RSA_WITH_AES_256_GCM_SHA384", }, ClientAuth: tls.ClientAuth{ - CAFiles: []tls.FileOrContent{ - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + CAFiles: []types.FileOrContent{ + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), }, ClientAuthType: "VerifyClientCertIfGiven", }, @@ -809,8 +809,8 @@ func TestLoadIngressRouteTCPs(t *testing.T) { "TLS_RSA_WITH_AES_256_GCM_SHA384", }, ClientAuth: tls.ClientAuth{ - CAFiles: []tls.FileOrContent{ - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + CAFiles: []types.FileOrContent{ + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), }, ClientAuthType: "VerifyClientCertIfGiven", }, @@ -1064,8 +1064,8 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Stores: map[string]tls.Store{ "default": { DefaultCertificate: &tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -1405,7 +1405,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { TLS: &dynamic.TLSClientConfig{ ServerName: "test", InsecureSkipVerify: true, - RootCAs: []tls.FileOrContent{"TESTROOTCAS0", "TESTROOTCAS1", "TESTROOTCAS2", "TESTROOTCAS3", "TESTROOTCAS5", "TESTALLCERTS"}, + RootCAs: []types.FileOrContent{"TESTROOTCAS0", "TESTROOTCAS1", "TESTROOTCAS2", "TESTROOTCAS3", "TESTROOTCAS5", "TESTALLCERTS"}, Certificates: tls.Certificates{ {CertFile: "TESTCERT1", KeyFile: "TESTKEY1"}, {CertFile: "TESTCERT2", KeyFile: "TESTKEY2"}, @@ -2917,8 +2917,8 @@ func TestLoadIngressRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -2979,9 +2979,9 @@ func TestLoadIngressRoutes(t *testing.T) { "TLS_RSA_WITH_AES_256_GCM_SHA384", }, ClientAuth: tls.ClientAuth{ - CAFiles: []tls.FileOrContent{ - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + CAFiles: []types.FileOrContent{ + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), }, ClientAuthType: "VerifyClientCertIfGiven", }, @@ -3100,9 +3100,9 @@ func TestLoadIngressRoutes(t *testing.T) { "TLS_RSA_WITH_AES_256_GCM_SHA384", }, ClientAuth: tls.ClientAuth{ - CAFiles: []tls.FileOrContent{ - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + CAFiles: []types.FileOrContent{ + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), }, ClientAuthType: "VerifyClientCertIfGiven", }, @@ -3178,9 +3178,9 @@ func TestLoadIngressRoutes(t *testing.T) { "TLS_RSA_WITH_AES_256_GCM_SHA384", }, ClientAuth: tls.ClientAuth{ - CAFiles: []tls.FileOrContent{ - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + CAFiles: []types.FileOrContent{ + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), }, ClientAuthType: "VerifyClientCertIfGiven", }, @@ -3251,8 +3251,8 @@ func TestLoadIngressRoutes(t *testing.T) { "TLS_RSA_WITH_AES_256_GCM_SHA384", }, ClientAuth: tls.ClientAuth{ - CAFiles: []tls.FileOrContent{ - tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + CAFiles: []types.FileOrContent{ + types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), }, ClientAuthType: "VerifyClientCertIfGiven", }, @@ -3881,8 +3881,8 @@ func TestLoadIngressRoutes(t *testing.T) { Stores: map[string]tls.Store{ "default": { DefaultCertificate: &tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -3938,8 +3938,8 @@ func TestLoadIngressRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, Stores: []string{"default"}, }, @@ -4215,7 +4215,7 @@ func TestLoadIngressRoutes(t *testing.T) { "foo-test": { ServerName: "test", InsecureSkipVerify: true, - RootCAs: []tls.FileOrContent{"TESTROOTCAS0", "TESTROOTCAS1", "TESTROOTCAS2", "TESTROOTCAS3", "TESTROOTCAS5", "TESTALLCERTS"}, + RootCAs: []types.FileOrContent{"TESTROOTCAS0", "TESTROOTCAS1", "TESTROOTCAS2", "TESTROOTCAS3", "TESTROOTCAS5", "TESTALLCERTS"}, Certificates: tls.Certificates{ {CertFile: "TESTCERT1", KeyFile: "TESTKEY1"}, {CertFile: "TESTCERT2", KeyFile: "TESTKEY2"}, @@ -7067,7 +7067,7 @@ func TestCreateBasicAuthCredentials(t *testing.T) { username = components[0] hashedPassword = components[1] - assert.Equal(t, username, "test2") - assert.Equal(t, hashedPassword, "$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0") + assert.Equal(t, "test2", username) + assert.Equal(t, "$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", hashedPassword) assert.True(t, auth.CheckSecret("test2", hashedPassword)) } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go index 51078f856..1378dc85d 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go @@ -26,12 +26,14 @@ type Middleware struct { // MiddlewareSpec defines the desired state of a Middleware. type MiddlewareSpec struct { - AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"` - StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"` - StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"` - ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"` - ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"` - Chain *Chain `json:"chain,omitempty"` + AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"` + StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"` + StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"` + ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"` + ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"` + Chain *Chain `json:"chain,omitempty"` + // Deprecated: please use IPAllowList instead. + IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"` IPAllowList *dynamic.IPAllowList `json:"ipAllowList,omitempty"` Headers *dynamic.Headers `json:"headers,omitempty"` Errors *ErrorPage `json:"errors,omitempty"` @@ -155,6 +157,8 @@ type ForwardAuth struct { AuthRequestHeaders []string `json:"authRequestHeaders,omitempty"` // TLS defines the configuration used to secure the connection to the authentication server. TLS *ClientTLS `json:"tls,omitempty"` + // AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response. + AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty"` } // ClientTLS holds the client TLS configuration. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go index 10799f295..d15402a26 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go @@ -25,6 +25,9 @@ type MiddlewareTCP struct { type MiddlewareTCPSpec struct { // InFlightConn defines the InFlightConn middleware configuration. InFlightConn *dynamic.TCPInFlightConn `json:"inFlightConn,omitempty"` + // IPWhiteList defines the IPWhiteList middleware configuration. + // Deprecated: please use IPAllowList instead. + IPWhiteList *dynamic.TCPIPWhiteList `json:"ipWhiteList,omitempty"` // IPAllowList defines the IPAllowList middleware configuration. IPAllowList *dynamic.TCPIPAllowList `json:"ipAllowList,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/types.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/types.go new file mode 100644 index 000000000..efc1183be --- /dev/null +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/types.go @@ -0,0 +1,7 @@ +package v1alpha1 + +/* +This file is needed for kubernetes/code-generator/kube_codegen.sh script used in script/code-gen.sh. +*/ + +// +genclient diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index e8f75cda6..40c9b979d 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -4,7 +4,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -215,6 +215,11 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) { *out = new(ClientTLS) **out = **in } + if in.AddAuthCookiesToResponse != nil { + in, out := &in.AddAuthCookiesToResponse, &out.AddAuthCookiesToResponse + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -689,6 +694,11 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { *out = new(Chain) (*in).DeepCopyInto(*out) } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(dynamic.IPWhiteList) + (*in).DeepCopyInto(*out) + } if in.IPAllowList != nil { in, out := &in.IPAllowList, &out.IPAllowList *out = new(dynamic.IPAllowList) @@ -862,6 +872,11 @@ func (in *MiddlewareTCPSpec) DeepCopyInto(out *MiddlewareTCPSpec) { *out = new(dynamic.TCPInFlightConn) **out = **in } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(dynamic.TCPIPWhiteList) + (*in).DeepCopyInto(*out) + } if in.IPAllowList != nil { in, out := &in.IPAllowList, &out.IPAllowList *out = new(dynamic.TCPIPAllowList) diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index 22a93dcda..709be079d 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -8,6 +8,7 @@ import ( "time" "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/types" corev1 "k8s.io/api/core/v1" kerror "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -16,9 +17,10 @@ import ( kclientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned" - gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions" + gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" + gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions" ) const resyncPeriod = 10 * time.Minute @@ -27,13 +29,13 @@ type resourceEventHandler struct { ev chan<- interface{} } -func (reh *resourceEventHandler) OnAdd(obj interface{}) { +func (reh *resourceEventHandler) OnAdd(obj interface{}, isInInitialList bool) { eventHandlerFunc(reh.ev, obj) } func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) { switch oldObj.(type) { - case *gatev1alpha2.GatewayClass: + case *gatev1.GatewayClass: // Skip update for gateway classes. We only manage addition or deletion for this cluster-wide resource. return default: @@ -50,11 +52,11 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) { // The stores can then be accessed via the Get* functions. type Client interface { WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) - GetGatewayClasses() ([]*gatev1alpha2.GatewayClass, error) - UpdateGatewayStatus(gateway *gatev1alpha2.Gateway, gatewayStatus gatev1alpha2.GatewayStatus) error - UpdateGatewayClassStatus(gatewayClass *gatev1alpha2.GatewayClass, condition metav1.Condition) error - GetGateways() []*gatev1alpha2.Gateway - GetHTTPRoutes(namespaces []string) ([]*gatev1alpha2.HTTPRoute, error) + GetGatewayClasses() ([]*gatev1.GatewayClass, error) + UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStatus gatev1.GatewayStatus) error + UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayClass, condition metav1.Condition) error + GetGateways() []*gatev1.Gateway + GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error) GetTCPRoutes(namespaces []string) ([]*gatev1alpha2.TCPRoute, error) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute, error) GetService(namespace, name string) (*corev1.Service, bool, error) @@ -128,14 +130,19 @@ func newExternalClusterClientFromFile(file string) (*clientWrapper, error) { // newExternalClusterClient returns a new Provider client that may run outside of the cluster. // The endpoint parameter must not be empty. -func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrapper, error) { +func newExternalClusterClient(endpoint, caFilePath string, token types.FileOrContent) (*clientWrapper, error) { if endpoint == "" { return nil, errors.New("endpoint missing for external cluster client") } + tokenData, err := token.Read() + if err != nil { + return nil, fmt.Errorf("read token: %w", err) + } + config := &rest.Config{ Host: endpoint, - BearerToken: token, + BearerToken: string(tokenData), } if caFilePath != "" { @@ -177,7 +184,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } c.factoryGatewayClass = gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithTweakListOptions(labelSelectorOptions)) - _, err = c.factoryGatewayClass.Gateway().V1alpha2().GatewayClasses().Informer().AddEventHandler(eventHandler) + _, err = c.factoryGatewayClass.Gateway().V1().GatewayClasses().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } @@ -187,11 +194,11 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< for _, ns := range namespaces { factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns)) - _, err = factoryGateway.Gateway().V1alpha2().Gateways().Informer().AddEventHandler(eventHandler) + _, err = factoryGateway.Gateway().V1().Gateways().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } - _, err = factoryGateway.Gateway().V1alpha2().HTTPRoutes().Informer().AddEventHandler(eventHandler) + _, err = factoryGateway.Gateway().V1().HTTPRoutes().Informer().AddEventHandler(eventHandler) if err != nil { return nil, err } @@ -286,15 +293,15 @@ func (c *clientWrapper) GetNamespaces(selector labels.Selector) ([]string, error return namespaces, nil } -func (c *clientWrapper) GetHTTPRoutes(namespaces []string) ([]*gatev1alpha2.HTTPRoute, error) { - var httpRoutes []*gatev1alpha2.HTTPRoute +func (c *clientWrapper) GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error) { + var httpRoutes []*gatev1.HTTPRoute for _, namespace := range namespaces { if !c.isWatchedNamespace(namespace) { log.Warn().Msgf("Failed to get HTTPRoutes: %q is not within watched namespaces", namespace) continue } - routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1alpha2().HTTPRoutes().Lister().HTTPRoutes(namespace).List(labels.Everything()) + routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1().HTTPRoutes().Lister().HTTPRoutes(namespace).List(labels.Everything()) if err != nil { return nil, err } @@ -356,11 +363,11 @@ func (c *clientWrapper) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRo return tlsRoutes, nil } -func (c *clientWrapper) GetGateways() []*gatev1alpha2.Gateway { - var result []*gatev1alpha2.Gateway +func (c *clientWrapper) GetGateways() []*gatev1.Gateway { + var result []*gatev1.Gateway for ns, factory := range c.factoriesGateway { - gateways, err := factory.Gateway().V1alpha2().Gateways().Lister().List(labels.Everything()) + gateways, err := factory.Gateway().V1().Gateways().Lister().List(labels.Everything()) if err != nil { log.Error().Err(err).Msgf("Failed to list Gateways in namespace %s", ns) continue @@ -371,11 +378,11 @@ func (c *clientWrapper) GetGateways() []*gatev1alpha2.Gateway { return result } -func (c *clientWrapper) GetGatewayClasses() ([]*gatev1alpha2.GatewayClass, error) { - return c.factoryGatewayClass.Gateway().V1alpha2().GatewayClasses().Lister().List(labels.Everything()) +func (c *clientWrapper) GetGatewayClasses() ([]*gatev1.GatewayClass, error) { + return c.factoryGatewayClass.Gateway().V1().GatewayClasses().Lister().List(labels.Everything()) } -func (c *clientWrapper) UpdateGatewayClassStatus(gatewayClass *gatev1alpha2.GatewayClass, condition metav1.Condition) error { +func (c *clientWrapper) UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayClass, condition metav1.Condition) error { gc := gatewayClass.DeepCopy() var newConditions []metav1.Condition @@ -398,7 +405,7 @@ func (c *clientWrapper) UpdateGatewayClassStatus(gatewayClass *gatev1alpha2.Gate ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - _, err := c.csGateway.GatewayV1alpha2().GatewayClasses().UpdateStatus(ctx, gc, metav1.UpdateOptions{}) + _, err := c.csGateway.GatewayV1().GatewayClasses().UpdateStatus(ctx, gc, metav1.UpdateOptions{}) if err != nil { return fmt.Errorf("failed to update GatewayClass %q status: %w", gatewayClass.Name, err) } @@ -406,7 +413,7 @@ func (c *clientWrapper) UpdateGatewayClassStatus(gatewayClass *gatev1alpha2.Gate return nil } -func (c *clientWrapper) UpdateGatewayStatus(gateway *gatev1alpha2.Gateway, gatewayStatus gatev1alpha2.GatewayStatus) error { +func (c *clientWrapper) UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStatus gatev1.GatewayStatus) error { if !c.isWatchedNamespace(gateway.Namespace) { return fmt.Errorf("cannot update Gateway status %s/%s: namespace is not within watched namespaces", gateway.Namespace, gateway.Name) } @@ -421,7 +428,7 @@ func (c *clientWrapper) UpdateGatewayStatus(gateway *gatev1alpha2.Gateway, gatew ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - _, err := c.csGateway.GatewayV1alpha2().Gateways(gateway.Namespace).UpdateStatus(ctx, g, metav1.UpdateOptions{}) + _, err := c.csGateway.GatewayV1().Gateways(gateway.Namespace).UpdateStatus(ctx, g, metav1.UpdateOptions{}) if err != nil { return fmt.Errorf("failed to update Gateway %q status: %w", gateway.Name, err) } @@ -429,7 +436,7 @@ func (c *clientWrapper) UpdateGatewayStatus(gateway *gatev1alpha2.Gateway, gatew return nil } -func statusEquals(oldStatus, newStatus gatev1alpha2.GatewayStatus) bool { +func statusEquals(oldStatus, newStatus gatev1.GatewayStatus) bool { if len(oldStatus.Listeners) != len(newStatus.Listeners) { return false } diff --git a/pkg/provider/kubernetes/gateway/client_mock_test.go b/pkg/provider/kubernetes/gateway/client_mock_test.go index 10c78801c..01611de40 100644 --- a/pkg/provider/kubernetes/gateway/client_mock_test.go +++ b/pkg/provider/kubernetes/gateway/client_mock_test.go @@ -10,6 +10,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" kscheme "k8s.io/client-go/kubernetes/scheme" + gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" ) @@ -21,6 +22,11 @@ func init() { if err != nil { panic(err) } + + err = gatev1.AddToScheme(kscheme.Scheme) + if err != nil { + panic(err) + } } type clientMock struct { @@ -33,9 +39,9 @@ type clientMock struct { apiSecretError error apiEndpointsError error - gatewayClasses []*gatev1alpha2.GatewayClass - gateways []*gatev1alpha2.Gateway - httpRoutes []*gatev1alpha2.HTTPRoute + gatewayClasses []*gatev1.GatewayClass + gateways []*gatev1.Gateway + httpRoutes []*gatev1.HTTPRoute tcpRoutes []*gatev1alpha2.TCPRoute tlsRoutes []*gatev1alpha2.TLSRoute @@ -62,11 +68,11 @@ func newClientMock(paths ...string) clientMock { c.namespaces = append(c.namespaces, o) case *corev1.Endpoints: c.endpoints = append(c.endpoints, o) - case *gatev1alpha2.GatewayClass: + case *gatev1.GatewayClass: c.gatewayClasses = append(c.gatewayClasses, o) - case *gatev1alpha2.Gateway: + case *gatev1.Gateway: c.gateways = append(c.gateways, o) - case *gatev1alpha2.HTTPRoute: + case *gatev1.HTTPRoute: c.httpRoutes = append(c.httpRoutes, o) case *gatev1alpha2.TCPRoute: c.tcpRoutes = append(c.tcpRoutes, o) @@ -81,7 +87,7 @@ func newClientMock(paths ...string) clientMock { return c } -func (c clientMock) UpdateGatewayStatus(gateway *gatev1alpha2.Gateway, gatewayStatus gatev1alpha2.GatewayStatus) error { +func (c clientMock) UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStatus gatev1.GatewayStatus) error { for _, g := range c.gateways { if g.Name == gateway.Name { if !statusEquals(g.Status, gatewayStatus) { @@ -94,7 +100,7 @@ func (c clientMock) UpdateGatewayStatus(gateway *gatev1alpha2.Gateway, gatewaySt return nil } -func (c clientMock) UpdateGatewayClassStatus(gatewayClass *gatev1alpha2.GatewayClass, condition metav1.Condition) error { +func (c clientMock) UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayClass, condition metav1.Condition) error { for _, gc := range c.gatewayClasses { if gc.Name == gatewayClass.Name { for _, c := range gc.Status.Conditions { @@ -110,7 +116,7 @@ func (c clientMock) UpdateGatewayClassStatus(gatewayClass *gatev1alpha2.GatewayC return nil } -func (c clientMock) UpdateGatewayStatusConditions(gateway *gatev1alpha2.Gateway, condition metav1.Condition) error { +func (c clientMock) UpdateGatewayStatusConditions(gateway *gatev1.Gateway, condition metav1.Condition) error { for _, g := range c.gatewayClasses { if g.Name == gateway.Name { for _, c := range g.Status.Conditions { @@ -126,11 +132,11 @@ func (c clientMock) UpdateGatewayStatusConditions(gateway *gatev1alpha2.Gateway, return nil } -func (c clientMock) GetGatewayClasses() ([]*gatev1alpha2.GatewayClass, error) { +func (c clientMock) GetGatewayClasses() ([]*gatev1.GatewayClass, error) { return c.gatewayClasses, nil } -func (c clientMock) GetGateways() []*gatev1alpha2.Gateway { +func (c clientMock) GetGateways() []*gatev1.Gateway { return c.gateways } @@ -148,8 +154,8 @@ func (c clientMock) GetNamespaces(selector labels.Selector) ([]string, error) { return ns, nil } -func (c clientMock) GetHTTPRoutes(namespaces []string) ([]*gatev1alpha2.HTTPRoute, error) { - var httpRoutes []*gatev1alpha2.HTTPRoute +func (c clientMock) GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error) { + var httpRoutes []*gatev1.HTTPRoute for _, namespace := range namespaces { for _, httpRoute := range c.httpRoutes { if inNamespace(httpRoute.ObjectMeta, namespace) { diff --git a/pkg/provider/kubernetes/gateway/client_test.go b/pkg/provider/kubernetes/gateway/client_test.go index 794f7d6a8..e6e751959 100644 --- a/pkg/provider/kubernetes/gateway/client_test.go +++ b/pkg/provider/kubernetes/gateway/client_test.go @@ -5,32 +5,32 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatev1 "sigs.k8s.io/gateway-api/apis/v1" ) func TestStatusEquals(t *testing.T) { testCases := []struct { desc string - statusA gatev1alpha2.GatewayStatus - statusB gatev1alpha2.GatewayStatus + statusA gatev1.GatewayStatus + statusB gatev1.GatewayStatus expected bool }{ { desc: "Empty", - statusA: gatev1alpha2.GatewayStatus{}, - statusB: gatev1alpha2.GatewayStatus{}, + statusA: gatev1.GatewayStatus{}, + statusB: gatev1.GatewayStatus{}, expected: true, }, { desc: "Same status", - statusA: gatev1alpha2.GatewayStatus{ + statusA: gatev1.GatewayStatus{ Conditions: []metav1.Condition{ { Type: "foobar", Reason: "foobar", }, }, - Listeners: []gatev1alpha2.ListenerStatus{ + Listeners: []gatev1.ListenerStatus{ { Name: "foo", Conditions: []metav1.Condition{ @@ -42,14 +42,14 @@ func TestStatusEquals(t *testing.T) { }, }, }, - statusB: gatev1alpha2.GatewayStatus{ + statusB: gatev1.GatewayStatus{ Conditions: []metav1.Condition{ { Type: "foobar", Reason: "foobar", }, }, - Listeners: []gatev1alpha2.ListenerStatus{ + Listeners: []gatev1.ListenerStatus{ { Name: "foo", Conditions: []metav1.Condition{ @@ -65,11 +65,11 @@ func TestStatusEquals(t *testing.T) { }, { desc: "Listeners length not equal", - statusA: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{}, + statusA: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{}, }, - statusB: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{ + statusB: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{ {}, }, }, @@ -77,10 +77,10 @@ func TestStatusEquals(t *testing.T) { }, { desc: "Gateway conditions length not equal", - statusA: gatev1alpha2.GatewayStatus{ + statusA: gatev1.GatewayStatus{ Conditions: []metav1.Condition{}, }, - statusB: gatev1alpha2.GatewayStatus{ + statusB: gatev1.GatewayStatus{ Conditions: []metav1.Condition{ {}, }, @@ -89,14 +89,14 @@ func TestStatusEquals(t *testing.T) { }, { desc: "Gateway conditions different types", - statusA: gatev1alpha2.GatewayStatus{ + statusA: gatev1.GatewayStatus{ Conditions: []metav1.Condition{ { Type: "foobar", }, }, }, - statusB: gatev1alpha2.GatewayStatus{ + statusB: gatev1.GatewayStatus{ Conditions: []metav1.Condition{ { Type: "foobir", @@ -107,14 +107,14 @@ func TestStatusEquals(t *testing.T) { }, { desc: "Gateway conditions same types but different reason", - statusA: gatev1alpha2.GatewayStatus{ + statusA: gatev1.GatewayStatus{ Conditions: []metav1.Condition{ { Type: "foobar", }, }, }, - statusB: gatev1alpha2.GatewayStatus{ + statusB: gatev1.GatewayStatus{ Conditions: []metav1.Condition{ { Type: "foobar", @@ -126,16 +126,16 @@ func TestStatusEquals(t *testing.T) { }, { desc: "Gateway listeners conditions length", - statusA: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{ + statusA: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{ { Name: "foo", Conditions: []metav1.Condition{}, }, }, }, - statusB: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{ + statusB: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{ { Name: "foo", Conditions: []metav1.Condition{ @@ -148,8 +148,8 @@ func TestStatusEquals(t *testing.T) { }, { desc: "Gateway listeners conditions same types but different status", - statusA: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{ + statusA: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{ { Conditions: []metav1.Condition{ { @@ -159,8 +159,8 @@ func TestStatusEquals(t *testing.T) { }, }, }, - statusB: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{ + statusB: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{ { Conditions: []metav1.Condition{ { @@ -175,8 +175,8 @@ func TestStatusEquals(t *testing.T) { }, { desc: "Gateway listeners conditions same types but different message", - statusA: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{ + statusA: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{ { Conditions: []metav1.Condition{ { @@ -186,8 +186,8 @@ func TestStatusEquals(t *testing.T) { }, }, }, - statusB: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{ + statusB: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{ { Conditions: []metav1.Condition{ { @@ -202,8 +202,8 @@ func TestStatusEquals(t *testing.T) { }, { desc: "Gateway listeners conditions same types/reason but different names", - statusA: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{ + statusA: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{ { Name: "foo", Conditions: []metav1.Condition{ @@ -215,8 +215,8 @@ func TestStatusEquals(t *testing.T) { }, }, }, - statusB: gatev1alpha2.GatewayStatus{ - Listeners: []gatev1alpha2.ListenerStatus{ + statusB: gatev1.GatewayStatus{ + Listeners: []gatev1.ListenerStatus{ { Name: "bar", Conditions: []metav1.Condition{ diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_http_to_https.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_http_to_https.yml index 5b8d1607d..de4cdf9ec 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_http_to_https.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_http_to_https.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -27,7 +27,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_http_to_https_with_hostname_and_port.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_http_to_https_with_hostname_and_port.yml index 79e875be1..f1aba4c62 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_http_to_https_with_hostname_and_port.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_http_to_https_with_hostname_and_port.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -27,7 +27,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/gatewayclass_with_unknown_controller.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/gatewayclass_with_unknown_controller.yml index f4962f2b3..c618fa851 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/gatewayclass_with_unknown_controller.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/gatewayclass_with_unknown_controller.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/one_rule_two_targets.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/one_rule_two_targets.yml index bd181b220..3c466202f 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/one_rule_two_targets.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/one_rule_two_targets.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple.yml index f48efddf2..d4a79574a 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -27,7 +27,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_cross_provider.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_cross_provider.yml index ff90c82d0..d44b97236 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_cross_provider.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_cross_provider.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_to_api_internal.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_to_api_internal.yml index 6426736f6..b78cc0a56 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_to_api_internal.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_to_api_internal.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_bad_rule.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_bad_rule.yml index 0ce0690e3..0ff70ee1a 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_bad_rule.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_bad_rule.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_tls_entrypoint.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_tls_entrypoint.yml index ca1f133c3..115dfb982 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_tls_entrypoint.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_with_tls_entrypoint.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/two_rules.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/two_rules.yml index cc4ee179f..e1a08baba 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/two_rules.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/two_rules.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_multiple_host.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_multiple_host.yml index 34e676f55..1f35bc7bc 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_multiple_host.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_multiple_host.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_all.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_all.yml index d40ab73ca..5db1069ca 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_all.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_all.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-default namespace: default @@ -49,7 +49,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-bar namespace: bar diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_same.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_same.yml index aa793a556..486c16764 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_same.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_same.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-default namespace: default @@ -49,7 +49,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-bar namespace: bar diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_selector.yml index a5bffb47a..cb7da2f50 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_selector.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_namespace_selector.yml @@ -8,7 +8,7 @@ metadata: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -16,7 +16,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -35,7 +35,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-default namespace: default @@ -60,7 +60,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-bar namespace: bar diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml index 9512aae07..cd4abdec1 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -40,7 +40,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml index e104514e0..9804bc80d 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_with_tls_passthrough.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -40,7 +40,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_without_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_without_tls.yml index 09c74b40d..c43d4684c 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_without_tls.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_https_without_tls.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tcp.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tcp.yml index 02ca3936d..a4cbed65e 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tcp.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tcp.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml index 9733a895c..bace669a0 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_protocol_tls.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -40,7 +40,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_several_rules.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_several_rules.yml index aeea3c26b..3dc9b9ecf 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_several_rules.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_several_rules.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_tls_configuration.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_tls_configuration.yml index 81362d6d6..0218677df 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_tls_configuration.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_tls_configuration.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml index ceb9f2061..934b67f82 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_gateways_one_httproute.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-https namespace: default @@ -40,7 +40,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-http namespace: default @@ -56,7 +56,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml index 7bd9fd05e..ca1c1a90e 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_two_listeners_one_httproute.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -47,7 +47,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_wrong_service_port.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_wrong_service_port.yml index e5959a809..48813cf21 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_wrong_service_port.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_wrong_service_port.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/without_gatewayclass.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/without_gatewayclass.yml index be795970a..bbf20fcd8 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/without_gatewayclass.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/without_gatewayclass.yml @@ -1,6 +1,6 @@ --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -16,7 +16,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/without_httproute.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/without_httproute.yml index 228525d55..ec10f8fda 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/without_httproute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/without_httproute.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml index 60f77deb4..ac15a9934 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/simple.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -92,7 +92,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_protocol.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_protocol.yml index f6519d8ea..3c11ca130 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_protocol.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_protocol.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -27,7 +27,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_route_kind.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_route_kind.yml index 3df51b8a4..1e54ea49c 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_route_kind.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_bad_listener_route_kind.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -27,7 +27,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_core_group.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_core_group.yml index 88047e8e1..d9a0c0a00 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_core_group.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_core_group.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -79,7 +79,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-default namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_incompatible_protocol_and_route_kind.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_incompatible_protocol_and_route_kind.yml index c4a146881..6f983f1ec 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_incompatible_protocol_and_route_kind.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_incompatible_protocol_and_route_kind.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -27,7 +27,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_listeners_using_same_hostname_port_protocol.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_listeners_using_same_hostname_port_protocol.yml index dd3d32238..b57edc456 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_listeners_using_same_hostname_port_protocol.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_multiple_listeners_using_same_hostname_port_protocol.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-http namespace: default @@ -49,7 +49,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-tcp namespace: default @@ -77,7 +77,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-tls namespace: default @@ -113,7 +113,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default @@ -128,7 +128,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-2 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml index f12dad908..a05cf0c73 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_all.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -92,7 +92,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-default namespace: default @@ -160,7 +160,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-bar namespace: bar diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml index 4dbf7118c..78ce26173 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_same.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -92,7 +92,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-default namespace: default @@ -160,7 +160,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-bar namespace: bar diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml index e2c982eaa..eb16d4727 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_namespace_selector.yml @@ -30,7 +30,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -38,7 +38,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -126,7 +126,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-default namespace: default @@ -194,7 +194,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-bar namespace: bar diff --git a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_wrong_routes_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_wrong_routes_selector.yml index 4562ecfe4..9d915f3e6 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/mixed/with_wrong_routes_selector.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/mixed/with_wrong_routes_selector.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -9,7 +9,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-mixed-gateway namespace: default @@ -74,7 +74,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/gatewayclass_with_unknown_controller.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/gatewayclass_with_unknown_controller.yml index 5dafcdfd3..5c6769c1c 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/gatewayclass_with_unknown_controller.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/gatewayclass_with_unknown_controller.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple.yml index 49e6fb1d4..707cc79c0 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -9,7 +9,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tcp-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_cross_provider.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_cross_provider.yml index be491cf79..01b314182 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_cross_provider.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_cross_provider.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_routes.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_routes.yml index d4da7c2fc..f6e64920b 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_routes.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_routes.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -9,7 +9,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tcp-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_rules.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_rules.yml index 57496b0d6..ab6118021 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_rules.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_multiple_rules.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -9,7 +9,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tcp-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_all.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_all.yml index 27c9abce6..1386c859d 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_all.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_all.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -9,7 +9,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tcp-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_same.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_same.yml index 43e251edd..20b2d0b87 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_same.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_same.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -9,7 +9,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tcp-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_selector.yml index 94c7770c9..23d0c9540 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_selector.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_namespace_selector.yml @@ -8,7 +8,7 @@ metadata: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -17,7 +17,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tcp-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_http.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_http.yml index 724103c4a..2a9068ffa 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_http.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_http.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml index b207e758c..ceb2f0bb0 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_https.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml index 6d4228279..05e3fc5ae 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_protocol_tls.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_tls_configuration.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_tls_configuration.yml index d31ddeb04..4c859717d 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_tls_configuration.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_tls_configuration.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_wrong_service_port.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_wrong_service_port.yml index 7c442a179..6de20a14a 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_wrong_service_port.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/with_wrong_service_port.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_gatewayclass.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_gatewayclass.yml index 231e6e55d..6b21d91c0 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_gatewayclass.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_gatewayclass.yml @@ -1,6 +1,6 @@ --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute.yml index 64eb3c73b..1dc7bd058 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml index d6c6fa309..67e8f584d 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/without_tcproute_tls_protocol.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml index 6b19420b0..0e9c8f448 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/gatewayclass_with_unknown_controller.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml index 25c935451..4a2dc107f 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -20,7 +20,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tls-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute_passthrough.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute_passthrough.yml index 525d6dbf2..664ad837a 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute_passthrough.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TCPRoute_passthrough.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -9,7 +9,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tls-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml index de79b72a0..e41a60aa7 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_TLS_to_TLSRoute.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -20,7 +20,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tls-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml index 4d687b5de..46c588590 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/simple_cross_provider.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_SNI_matching.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_SNI_matching.yml index f4f09af5f..fa61b383f 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_SNI_matching.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_SNI_matching.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_invalid_SNI_matching.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_invalid_SNI_matching.yml index 512e39770..f1485032d 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_invalid_SNI_matching.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_invalid_SNI_matching.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_SNI_matching.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_SNI_matching.yml index ae03dc627..a99ac3ac5 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_SNI_matching.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_SNI_matching.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml index 2f62394e4..c88672329 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_routes_kind.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -20,7 +20,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-tls-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_rules.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_rules.yml index f6313193c..44cd37953 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_rules.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_multiple_rules.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class namespace: default @@ -9,7 +9,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_all.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_all.yml index e9a942afa..763ce9006 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_all.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_all.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_same.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_same.yml index ef81e534c..473e7618b 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_same.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_same.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_selector.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_selector.yml index c51ed45a2..25cd31c65 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_selector.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_namespace_selector.yml @@ -8,7 +8,7 @@ metadata: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -16,7 +16,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough.yml index 2c3369aca..149b4af98 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml index da5d22d84..e04d4e362 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_passthrough_tls.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_http.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_http.yml index 25411db69..072f74477 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_http.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_http.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml index 782f344aa..c5d904baa 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_https.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_tcp.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_tcp.yml index b27e1fe7e..916506a4a 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_tcp.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_protocol_tcp.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml index 1aafeb234..c0c472a55 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/with_wrong_service_port.yml @@ -11,7 +11,7 @@ data: --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -19,7 +19,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_gatewayclass.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_gatewayclass.yml index c053f92e1..c4970535c 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_gatewayclass.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_gatewayclass.yml @@ -1,6 +1,6 @@ --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_tlsroute.yml b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_tlsroute.yml index 9eb8cc5e0..b134a16d7 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_tlsroute.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/tlsroute/without_tlsroute.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_two_hosts_one_wildcard.yml b/pkg/provider/kubernetes/gateway/fixtures/with_two_hosts_one_wildcard.yml index 5e20fd9bf..33b2cad7e 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/with_two_hosts_one_wildcard.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/with_two_hosts_one_wildcard.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/fixtures/with_two_hosts_wildcard.yml b/pkg/provider/kubernetes/gateway/fixtures/with_two_hosts_wildcard.yml index c368d7fbc..f069e9b3b 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/with_two_hosts_wildcard.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/with_two_hosts_wildcard.yml @@ -1,6 +1,6 @@ --- kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway-class spec: @@ -8,7 +8,7 @@ spec: --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: my-gateway namespace: default @@ -24,7 +24,7 @@ spec: --- kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1 metadata: name: http-app-1 namespace: default diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index bbdf63fc7..c86467b0f 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -27,12 +27,13 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "k8s.io/utils/strings/slices" - gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatev1 "sigs.k8s.io/gateway-api/apis/v1" ) const ( @@ -48,7 +49,7 @@ const ( // Provider holds configurations of the provider. type Provider struct { Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` + Token types.FileOrContent `description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` LabelSelector string `description:"Kubernetes label selector to select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` @@ -64,7 +65,7 @@ func (p *Provider) SetRouterTransform(routerTransform k8s.RouterTransform) { p.routerTransform = routerTransform } -func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, route *gatev1alpha2.HTTPRoute) { +func (p *Provider) applyRouterTransform(ctx context.Context, rt *dynamic.Router, route *gatev1.HTTPRoute) { if p.routerTransform == nil { return } @@ -101,7 +102,7 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) default: logger.Info().Str("endpoint", p.Endpoint).Msg("Creating cluster-external Provider client") - client, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) + client, err = newExternalClusterClient(p.Endpoint, p.CertAuthFilePath, p.Token) } if err != nil { @@ -227,14 +228,14 @@ func (p *Provider) loadConfigurationFromGateway(ctx context.Context, client Clie gatewayClassNames[gatewayClass.Name] = struct{}{} err := client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{ - Type: string(gatev1alpha2.GatewayClassConditionStatusAccepted), + Type: string(gatev1.GatewayClassConditionStatusAccepted), Status: metav1.ConditionTrue, Reason: "Handled", Message: "Handled by Traefik controller", LastTransitionTime: metav1.Now(), }) if err != nil { - logger.Error().Err(err).Msgf("Failed to update %s condition", gatev1alpha2.GatewayClassConditionStatusAccepted) + logger.Error().Err(err).Msgf("Failed to update %s condition", gatev1.GatewayClassConditionStatusAccepted) } } } @@ -290,7 +291,7 @@ func (p *Provider) loadConfigurationFromGateway(ctx context.Context, client Clie return conf } -func (p *Provider) createGatewayConf(ctx context.Context, client Client, gateway *gatev1alpha2.Gateway) (*dynamic.Configuration, error) { +func (p *Provider) createGatewayConf(ctx context.Context, client Client, gateway *gatev1.Gateway) (*dynamic.Configuration, error) { conf := &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{}, @@ -318,7 +319,7 @@ func (p *Provider) createGatewayConf(ctx context.Context, client Client, gateway // and cannot be configured on the Gateway. listenerStatuses := p.fillGatewayConf(ctx, client, gateway, conf, tlsConfigs) - gatewayStatus, errG := p.makeGatewayStatus(listenerStatuses) + gatewayStatus, errG := p.makeGatewayStatus(gateway, listenerStatuses) err := client.UpdateGatewayStatus(gateway, gatewayStatus) if err != nil { @@ -336,15 +337,15 @@ func (p *Provider) createGatewayConf(ctx context.Context, client Client, gateway return conf, nil } -func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *gatev1alpha2.Gateway, conf *dynamic.Configuration, tlsConfigs map[string]*tls.CertAndStores) []gatev1alpha2.ListenerStatus { +func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *gatev1.Gateway, conf *dynamic.Configuration, tlsConfigs map[string]*tls.CertAndStores) []gatev1.ListenerStatus { logger := log.Ctx(ctx) - listenerStatuses := make([]gatev1alpha2.ListenerStatus, len(gateway.Spec.Listeners)) + listenerStatuses := make([]gatev1.ListenerStatus, len(gateway.Spec.Listeners)) allocatedListeners := make(map[string]struct{}) for i, listener := range gateway.Spec.Listeners { - listenerStatuses[i] = gatev1alpha2.ListenerStatus{ + listenerStatuses[i] = gatev1.ListenerStatus{ Name: listener.Name, - SupportedKinds: []gatev1alpha2.RouteGroupKind{}, + SupportedKinds: []gatev1.RouteGroupKind{}, Conditions: []metav1.Condition{}, } @@ -356,7 +357,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * listenerStatuses[i].SupportedKinds = supportedKinds - routeKinds, conditions := getAllowedRouteKinds(listener, supportedKinds) + routeKinds, conditions := getAllowedRouteKinds(gateway, listener, supportedKinds) if len(conditions) > 0 { listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, conditions...) continue @@ -366,8 +367,9 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * if _, ok := allocatedListeners[listenerKey]; ok { listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionConflicted), + Type: string(gatev1.ListenerConditionConflicted), Status: metav1.ConditionTrue, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "DuplicateListener", Message: "A listener with same protocol, port and hostname already exists", @@ -382,20 +384,22 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * if err != nil { // update "Detached" status with "PortUnavailable" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionDetached), - Status: metav1.ConditionTrue, + Type: string(gatev1.ListenerConditionAccepted), + Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonPortUnavailable), + Reason: string(gatev1.ListenerReasonPortUnavailable), Message: fmt.Sprintf("Cannot find entryPoint for Gateway: %v", err), }) continue } - if (listener.Protocol == gatev1alpha2.HTTPProtocolType || listener.Protocol == gatev1alpha2.TCPProtocolType) && listener.TLS != nil { + if (listener.Protocol == gatev1.HTTPProtocolType || listener.Protocol == gatev1.TCPProtocolType) && listener.TLS != nil { listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionDetached), - Status: metav1.ConditionTrue, + Type: string(gatev1.ListenerConditionAccepted), + Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidTLSConfiguration", // TODO check the spec if a proper reason is introduced at some point Message: "TLS configuration must no be defined when using HTTP or TCP protocol", @@ -405,12 +409,13 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * } // TLS - if listener.Protocol == gatev1alpha2.HTTPSProtocolType || listener.Protocol == gatev1alpha2.TLSProtocolType { - if listener.TLS == nil || (len(listener.TLS.CertificateRefs) == 0 && listener.TLS.Mode != nil && *listener.TLS.Mode != gatev1alpha2.TLSModePassthrough) { + if listener.Protocol == gatev1.HTTPSProtocolType || listener.Protocol == gatev1.TLSProtocolType { + if listener.TLS == nil || (len(listener.TLS.CertificateRefs) == 0 && listener.TLS.Mode != nil && *listener.TLS.Mode != gatev1.TLSModePassthrough) { // update "Detached" status with "UnsupportedProtocol" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionDetached), - Status: metav1.ConditionTrue, + Type: string(gatev1.ListenerConditionAccepted), + Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidTLSConfiguration", // TODO check the spec if a proper reason is introduced at some point Message: fmt.Sprintf("No TLS configuration for Gateway Listener %s:%d and protocol %q", @@ -420,12 +425,12 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * continue } - var tlsModeType gatev1alpha2.TLSModeType + var tlsModeType gatev1.TLSModeType if listener.TLS.Mode != nil { tlsModeType = *listener.TLS.Mode } - isTLSPassthrough := tlsModeType == gatev1alpha2.TLSModePassthrough + isTLSPassthrough := tlsModeType == gatev1.TLSModePassthrough if isTLSPassthrough && len(listener.TLS.CertificateRefs) > 0 { // https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayTLSConfig @@ -436,12 +441,13 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * // Protocol TLS -> Passthrough -> TLSRoute/TCPRoute // Protocol TLS -> Terminate -> TLSRoute/TCPRoute // Protocol HTTPS -> Terminate -> HTTPRoute - if listener.Protocol == gatev1alpha2.HTTPSProtocolType && isTLSPassthrough { + if listener.Protocol == gatev1.HTTPSProtocolType && isTLSPassthrough { listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionDetached), - Status: metav1.ConditionTrue, + Type: string(gatev1.ListenerConditionAccepted), + Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonUnsupportedProtocol), + Reason: string(gatev1.ListenerReasonUnsupportedProtocol), Message: "HTTPS protocol is not supported with TLS mode Passthrough", }) @@ -452,10 +458,11 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * if len(listener.TLS.CertificateRefs) == 0 { // update "ResolvedRefs" status true with "InvalidCertificateRef" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonInvalidCertificateRef), + Reason: string(gatev1.ListenerReasonInvalidCertificateRef), Message: "One TLS CertificateRef is required in Terminate mode", }) @@ -469,10 +476,11 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * certificateRef.Group == nil || (*certificateRef.Group != "" && *certificateRef.Group != "core") { // update "ResolvedRefs" status true with "InvalidCertificateRef" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonInvalidCertificateRef), + Reason: string(gatev1.ListenerReasonInvalidCertificateRef), Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind: %v/%v", certificateRef.Group, certificateRef.Kind), }) @@ -482,10 +490,11 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * // TODO Support ReferencePolicy to support cross namespace references. if certificateRef.Namespace != nil && string(*certificateRef.Namespace) != gateway.Namespace { listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonInvalidCertificateRef), + Reason: string(gatev1.ListenerReasonInvalidCertificateRef), Message: "Cross namespace secrets are not supported", }) @@ -498,10 +507,11 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * if err != nil { // update "ResolvedRefs" status true with "InvalidCertificateRef" reason listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonInvalidCertificateRef), + Reason: string(gatev1.ListenerReasonInvalidCertificateRef), Message: fmt.Sprintf("Error while retrieving certificate: %v", err), }) @@ -528,10 +538,10 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway * return listenerStatuses } -func (p *Provider) makeGatewayStatus(listenerStatuses []gatev1alpha2.ListenerStatus) (gatev1alpha2.GatewayStatus, error) { +func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses []gatev1.ListenerStatus) (gatev1.GatewayStatus, error) { // As Status.Addresses are not implemented yet, we initialize an empty array to follow the API expectations. - gatewayStatus := gatev1alpha2.GatewayStatus{ - Addresses: []gatev1alpha2.GatewayAddress{}, + gatewayStatus := gatev1.GatewayStatus{ + Addresses: []gatev1.GatewayStatusAddress{}, } var result error @@ -539,8 +549,9 @@ func (p *Provider) makeGatewayStatus(listenerStatuses []gatev1alpha2.ListenerSta if len(listener.Conditions) == 0 { // GatewayConditionReady "Ready", GatewayConditionReason "ListenerReady" listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionReady), + Type: string(gatev1.ListenerReasonAccepted), Status: metav1.ConditionTrue, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "ListenerReady", Message: "No error found", @@ -557,10 +568,11 @@ func (p *Provider) makeGatewayStatus(listenerStatuses []gatev1alpha2.ListenerSta if result != nil { // GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid" gatewayStatus.Conditions = append(gatewayStatus.Conditions, metav1.Condition{ - Type: string(gatev1alpha2.GatewayConditionReady), + Type: string(gatev1.GatewayConditionAccepted), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.GatewayReasonListenersNotValid), + Reason: string(gatev1.GatewayReasonListenersNotValid), Message: "All Listeners must be valid", }) @@ -570,20 +582,13 @@ func (p *Provider) makeGatewayStatus(listenerStatuses []gatev1alpha2.ListenerSta gatewayStatus.Listeners = listenerStatuses gatewayStatus.Conditions = append(gatewayStatus.Conditions, - // update "Scheduled" status with "ResourcesAvailable" reason + // update "Accepted" status with "Accepted" reason metav1.Condition{ - Type: string(gatev1alpha2.GatewayConditionScheduled), + Type: string(gatev1.GatewayConditionAccepted), Status: metav1.ConditionTrue, - Reason: "ResourcesAvailable", - Message: "Resources available", - LastTransitionTime: metav1.Now(), - }, - // update "Ready" status with "ListenersValid" reason - metav1.Condition{ - Type: string(gatev1alpha2.GatewayConditionReady), - Status: metav1.ConditionTrue, - Reason: "ListenersValid", - Message: "Listeners valid", + ObservedGeneration: gateway.Generation, + Reason: string(gatev1.GatewayConditionAccepted), + Message: "Gateway successfully scheduled", LastTransitionTime: metav1.Now(), }, ) @@ -591,14 +596,14 @@ func (p *Provider) makeGatewayStatus(listenerStatuses []gatev1alpha2.ListenerSta return gatewayStatus, nil } -func (p *Provider) entryPointName(port gatev1alpha2.PortNumber, protocol gatev1alpha2.ProtocolType) (string, error) { +func (p *Provider) entryPointName(port gatev1.PortNumber, protocol gatev1.ProtocolType) (string, error) { portStr := strconv.FormatInt(int64(port), 10) for name, entryPoint := range p.EntryPoints { if strings.HasSuffix(entryPoint.Address, ":"+portStr) { // If the protocol is HTTP the entryPoint must have no TLS conf - // Not relevant for gatev1alpha2.TLSProtocolType && gatev1alpha2.TCPProtocolType - if protocol == gatev1alpha2.HTTPProtocolType && entryPoint.HasHTTPTLSConf { + // Not relevant for gatev1.TLSProtocolType && gatev1.TCPProtocolType + if protocol == gatev1.HTTPProtocolType && entryPoint.HasHTTPTLSConf { continue } @@ -609,43 +614,43 @@ func (p *Provider) entryPointName(port gatev1alpha2.PortNumber, protocol gatev1a return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol) } -func supportedRouteKinds(protocol gatev1alpha2.ProtocolType) ([]gatev1alpha2.RouteGroupKind, []metav1.Condition) { - group := gatev1alpha2.Group(gatev1alpha2.GroupName) +func supportedRouteKinds(protocol gatev1.ProtocolType) ([]gatev1.RouteGroupKind, []metav1.Condition) { + group := gatev1.Group(gatev1.GroupName) switch protocol { - case gatev1alpha2.TCPProtocolType: - return []gatev1alpha2.RouteGroupKind{{Kind: kindTCPRoute, Group: &group}}, nil + case gatev1.TCPProtocolType: + return []gatev1.RouteGroupKind{{Kind: kindTCPRoute, Group: &group}}, nil - case gatev1alpha2.HTTPProtocolType, gatev1alpha2.HTTPSProtocolType: - return []gatev1alpha2.RouteGroupKind{{Kind: kindHTTPRoute, Group: &group}}, nil + case gatev1.HTTPProtocolType, gatev1.HTTPSProtocolType: + return []gatev1.RouteGroupKind{{Kind: kindHTTPRoute, Group: &group}}, nil - case gatev1alpha2.TLSProtocolType: - return []gatev1alpha2.RouteGroupKind{ + case gatev1.TLSProtocolType: + return []gatev1.RouteGroupKind{ {Kind: kindTCPRoute, Group: &group}, {Kind: kindTLSRoute, Group: &group}, }, nil } return nil, []metav1.Condition{{ - Type: string(gatev1alpha2.ListenerConditionDetached), - Status: metav1.ConditionTrue, + Type: string(gatev1.ListenerConditionAccepted), + Status: metav1.ConditionFalse, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonUnsupportedProtocol), + Reason: string(gatev1.ListenerReasonUnsupportedProtocol), Message: fmt.Sprintf("Unsupported listener protocol %q", protocol), }} } -func getAllowedRouteKinds(listener gatev1alpha2.Listener, supportedKinds []gatev1alpha2.RouteGroupKind) ([]gatev1alpha2.RouteGroupKind, []metav1.Condition) { +func getAllowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, supportedKinds []gatev1.RouteGroupKind) ([]gatev1.RouteGroupKind, []metav1.Condition) { if listener.AllowedRoutes == nil || len(listener.AllowedRoutes.Kinds) == 0 { return supportedKinds, nil } var ( - routeKinds []gatev1alpha2.RouteGroupKind + routeKinds []gatev1.RouteGroupKind conditions []metav1.Condition ) - uniqRouteKinds := map[gatev1alpha2.Kind]struct{}{} + uniqRouteKinds := map[gatev1.Kind]struct{}{} for _, routeKind := range listener.AllowedRoutes.Kinds { var isSupported bool for _, kind := range supportedKinds { @@ -657,10 +662,11 @@ func getAllowedRouteKinds(listener gatev1alpha2.Listener, supportedKinds []gatev if !isSupported { conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionDetached), + Type: string(gatev1.ListenerConditionAccepted), Status: metav1.ConditionTrue, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonInvalidRouteKinds), + Reason: string(gatev1.ListenerReasonInvalidRouteKinds), Message: fmt.Sprintf("Listener protocol %q does not support RouteGroupKind %v/%s", listener.Protocol, routeKind.Group, routeKind.Kind), }) continue @@ -675,7 +681,7 @@ func getAllowedRouteKinds(listener gatev1alpha2.Listener, supportedKinds []gatev return routeKinds, conditions } -func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener gatev1alpha2.Listener, gateway *gatev1alpha2.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { +func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, listener gatev1.Listener, gateway *gatev1.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { if listener.AllowedRoutes == nil { // Should not happen due to validation. return nil @@ -685,8 +691,9 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li if err != nil { // update "ResolvedRefs" status true with "InvalidRoutesRef" reason return []metav1.Condition{{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidRouteNamespacesSelector", // Should never happen as the selector is validated by kubernetes Message: fmt.Sprintf("Invalid route namespaces selector: %v", err), @@ -697,10 +704,11 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li if err != nil { // update "ResolvedRefs" status true with "InvalidRoutesRef" reason return []metav1.Condition{{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonRefNotPermitted), + Reason: string(gatev1.ListenerReasonRefNotPermitted), Message: fmt.Sprintf("Cannot fetch HTTPRoutes: %v", err), }} } @@ -726,8 +734,9 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li hostRule, err := hostRule(hostnames) if err != nil { conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidRouteHostname", // TODO check the spec if a proper reason is introduced at some point Message: fmt.Sprintf("Skipping HTTPRoute %s: invalid hostname: %v", route.Name, err), @@ -740,8 +749,9 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "UnsupportedPathOrHeaderType", // TODO check the spec if a proper reason is introduced at some point Message: fmt.Sprintf("Skipping HTTPRoute %s: cannot generate rule: %v", route.Name, err), @@ -753,7 +763,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li EntryPoints: []string{ep}, } - if listener.Protocol == gatev1alpha2.HTTPSProtocolType && listener.TLS != nil { + if listener.Protocol == gatev1.HTTPSProtocolType && listener.TLS != nil { // TODO support let's encrypt router.TLS = &dynamic.RouterTLSConfig{} } @@ -764,8 +774,9 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidRouterKey", // Should never happen Message: fmt.Sprintf("Skipping HTTPRoute %s: cannot make router's key with rule %s: %v", route.Name, router.Rule, err), @@ -779,8 +790,9 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li if err != nil { // update "ResolvedRefs" status true with "InvalidFilters" reason conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidFilters", // TODO check the spec if a proper reason is introduced at some point Message: fmt.Sprintf("Cannot load HTTPRoute filter %s/%s: %v", route.Namespace, route.Name, err), @@ -807,8 +819,9 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidBackendRefs", // TODO check the spec if a proper reason is introduced at some point Message: fmt.Sprintf("Cannot load HTTPRoute service %s/%s: %v", route.Namespace, route.Name, err), @@ -839,7 +852,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li return conditions } -func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1alpha2.Listener, gateway *gatev1alpha2.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { +func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Listener, gateway *gatev1.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { if listener.AllowedRoutes == nil { // Should not happen due to validation. return nil @@ -849,8 +862,9 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if err != nil { // update "ResolvedRefs" status true with "InvalidRoutesRef" reason return []metav1.Condition{{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidRouteNamespacesSelector", // TODO should never happen as the selector is validated by Kubernetes Message: fmt.Sprintf("Invalid route namespaces selector: %v", err), @@ -861,10 +875,11 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if err != nil { // update "ResolvedRefs" status true with "InvalidRoutesRef" reason return []metav1.Condition{{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonRefNotPermitted), + Reason: string(gatev1.ListenerReasonRefNotPermitted), Message: fmt.Sprintf("Cannot fetch TCPRoutes: %v", err), }} } @@ -885,10 +900,10 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp EntryPoints: []string{ep}, } - if listener.Protocol == gatev1alpha2.TLSProtocolType && listener.TLS != nil { + if listener.Protocol == gatev1.TLSProtocolType && listener.TLS != nil { // TODO support let's encrypt router.TLS = &dynamic.RouterTCPTLSConfig{ - Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1alpha2.TLSModePassthrough, + Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1.TLSModePassthrough, } } @@ -898,8 +913,9 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidRouterKey", // Should never happen Message: fmt.Sprintf("Skipping TCPRoute %s: cannot make router's key with rule %s: %v", route.Name, router.Rule, err), @@ -923,8 +939,9 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidBackendRefs", // TODO check the spec if a proper reason is introduced at some point Message: fmt.Sprintf("Cannot load TCPRoute service %s/%s: %v", route.Namespace, route.Name, err), @@ -969,7 +986,7 @@ func gatewayTCPRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp return conditions } -func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alpha2.Listener, gateway *gatev1alpha2.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { +func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Listener, gateway *gatev1.Gateway, client Client, conf *dynamic.Configuration) []metav1.Condition { if listener.AllowedRoutes == nil { // Should not happen due to validation. return nil @@ -979,8 +996,9 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if err != nil { // update "ResolvedRefs" status true with "InvalidRoutesRef" reason return []metav1.Condition{{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidRouteNamespacesSelector", // TODO should never happen as the selector is validated by Kubernetes Message: fmt.Sprintf("Invalid route namespaces selector: %v", err), @@ -991,10 +1009,11 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if err != nil { // update "ResolvedRefs" status true with "InvalidRoutesRef" reason return []metav1.Condition{{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), - Reason: string(gatev1alpha2.ListenerReasonRefNotPermitted), + Reason: string(gatev1.ListenerReasonRefNotPermitted), Message: fmt.Sprintf("Cannot fetch TLSRoutes: %v", err), }} } @@ -1014,9 +1033,10 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if len(hostnames) == 0 && listener.Hostname != nil && *listener.Hostname != "" && len(route.Spec.Hostnames) > 0 { for _, parent := range route.Status.Parents { parent.Conditions = append(parent.Conditions, metav1.Condition{ - Type: string(gatev1alpha2.GatewayClassConditionStatusAccepted), + Type: string(gatev1.GatewayClassConditionStatusAccepted), Status: metav1.ConditionFalse, - Reason: string(gatev1alpha2.ListenerReasonRouteConflict), + ObservedGeneration: gateway.Generation, + Reason: string(gatev1.ListenerConditionConflicted), Message: fmt.Sprintf("No hostname match between listener: %v and route: %v", listener.Hostname, route.Spec.Hostnames), LastTransitionTime: metav1.Now(), }) @@ -1029,8 +1049,9 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidHostnames", // TODO check the spec if a proper reason is introduced at some point Message: fmt.Sprintf("Skipping TLSRoute %s: cannot make route's SNI match: %v", route.Name, err), @@ -1043,7 +1064,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp Rule: rule, EntryPoints: []string{ep}, TLS: &dynamic.RouterTCPTLSConfig{ - Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1alpha2.TLSModePassthrough, + Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1.TLSModePassthrough, }, } @@ -1053,8 +1074,9 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidRouterKey", // Should never happen Message: fmt.Sprintf("Skipping TLSRoute %s: cannot make router's key with rule %s: %v", route.Name, router.Rule, err), @@ -1078,8 +1100,9 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp if err != nil { // update "ResolvedRefs" status true with "DroppedRoutes" reason conditions = append(conditions, metav1.Condition{ - Type: string(gatev1alpha2.ListenerConditionResolvedRefs), + Type: string(gatev1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, + ObservedGeneration: gateway.Generation, LastTransitionTime: metav1.Now(), Reason: "InvalidBackendRefs", // TODO check the spec if a proper reason is introduced at some point Message: fmt.Sprintf("Cannot load TLSRoute service %s/%s: %v", route.Namespace, route.Name, err), @@ -1126,18 +1149,18 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1alp // Because of Kubernetes validation we admit that the given Hostnames are valid. // https://github.com/kubernetes-sigs/gateway-api/blob/ff9883da4cad8554cd300394f725ab3a27502785/apis/v1alpha2/shared_types.go#L252 -func matchingHostnames(listener gatev1alpha2.Listener, hostnames []gatev1alpha2.Hostname) []gatev1alpha2.Hostname { +func matchingHostnames(listener gatev1.Listener, hostnames []gatev1.Hostname) []gatev1.Hostname { if listener.Hostname == nil || *listener.Hostname == "" { return hostnames } if len(hostnames) == 0 { - return []gatev1alpha2.Hostname{*listener.Hostname} + return []gatev1.Hostname{*listener.Hostname} } listenerLabels := strings.Split(string(*listener.Hostname), ".") - var matches []gatev1alpha2.Hostname + var matches []gatev1.Hostname for _, hostname := range hostnames { if hostname == *listener.Hostname { @@ -1168,9 +1191,9 @@ func matchingHostnames(listener gatev1alpha2.Listener, hostnames []gatev1alpha2. return matches } -func shouldAttach(gateway *gatev1alpha2.Gateway, listener gatev1alpha2.Listener, routeNamespace string, routeSpec gatev1alpha2.CommonRouteSpec) bool { +func shouldAttach(gateway *gatev1.Gateway, listener gatev1.Listener, routeNamespace string, routeSpec gatev1.CommonRouteSpec) bool { for _, parentRef := range routeSpec.ParentRefs { - if parentRef.Group == nil || *parentRef.Group != gatev1alpha2.GroupName { + if parentRef.Group == nil || *parentRef.Group != gatev1.GroupName { continue } @@ -1195,19 +1218,19 @@ func shouldAttach(gateway *gatev1alpha2.Gateway, listener gatev1alpha2.Listener, return false } -func getRouteBindingSelectorNamespace(client Client, gatewayNamespace string, routeNamespaces *gatev1alpha2.RouteNamespaces) ([]string, error) { +func getRouteBindingSelectorNamespace(client Client, gatewayNamespace string, routeNamespaces *gatev1.RouteNamespaces) ([]string, error) { if routeNamespaces == nil || routeNamespaces.From == nil { return []string{gatewayNamespace}, nil } switch *routeNamespaces.From { - case gatev1alpha2.NamespacesFromAll: + case gatev1.NamespacesFromAll: return []string{metav1.NamespaceAll}, nil - case gatev1alpha2.NamespacesFromSame: + case gatev1.NamespacesFromSame: return []string{gatewayNamespace}, nil - case gatev1alpha2.NamespacesFromSelector: + case gatev1.NamespacesFromSelector: selector, err := metav1.LabelSelectorAsSelector(routeNamespaces.Selector) if err != nil { return nil, fmt.Errorf("malformed selector: %w", err) @@ -1219,7 +1242,7 @@ func getRouteBindingSelectorNamespace(client Client, gatewayNamespace string, ro return nil, fmt.Errorf("unsupported RouteSelectType: %q", *routeNamespaces.From) } -func hostRule(hostnames []gatev1alpha2.Hostname) (string, error) { +func hostRule(hostnames []gatev1.Hostname) (string, error) { var rules []string for _, hostname := range hostnames { @@ -1256,9 +1279,9 @@ func hostRule(hostnames []gatev1alpha2.Hostname) (string, error) { } } -func hostSNIRule(hostnames []gatev1alpha2.Hostname) (string, error) { +func hostSNIRule(hostnames []gatev1.Hostname) (string, error) { rules := make([]string, 0, len(hostnames)) - uniqHostnames := map[gatev1alpha2.Hostname]struct{}{} + uniqHostnames := map[gatev1.Hostname]struct{}{} for _, hostname := range hostnames { if len(hostname) == 0 { @@ -1293,7 +1316,7 @@ func hostSNIRule(hostnames []gatev1alpha2.Hostname) (string, error) { return strings.Join(rules, " || "), nil } -func extractRule(routeRule gatev1alpha2.HTTPRouteRule, hostRule string) (string, error) { +func extractRule(routeRule gatev1.HTTPRouteRule, hostRule string) (string, error) { var rule string var matchesRules []string @@ -1307,9 +1330,9 @@ func extractRule(routeRule gatev1alpha2.HTTPRouteRule, hostRule string) (string, if match.Path != nil && match.Path.Type != nil && match.Path.Value != nil { // TODO handle other path types switch *match.Path.Type { - case gatev1alpha2.PathMatchExact: + case gatev1.PathMatchExact: matchRules = append(matchRules, fmt.Sprintf("Path(`%s`)", *match.Path.Value)) - case gatev1alpha2.PathMatchPathPrefix: + case gatev1.PathMatchPathPrefix: matchRules = append(matchRules, fmt.Sprintf("PathPrefix(`%s`)", *match.Path.Value)) default: return "", fmt.Errorf("unsupported path match %s", *match.Path.Type) @@ -1350,7 +1373,7 @@ func extractRule(routeRule gatev1alpha2.HTTPRouteRule, hostRule string) (string, return rule + "(" + strings.Join(matchesRules, " || ") + ")", nil } -func extractHeaderRules(headers []gatev1alpha2.HTTPHeaderMatch) ([]string, error) { +func extractHeaderRules(headers []gatev1.HTTPHeaderMatch) ([]string, error) { var headerRules []string // TODO handle other headers types @@ -1361,7 +1384,7 @@ func extractHeaderRules(headers []gatev1alpha2.HTTPHeaderMatch) ([]string, error } switch *header.Type { - case gatev1alpha2.HeaderMatchExact: + case gatev1.HeaderMatchExact: headerRules = append(headerRules, fmt.Sprintf("Headers(`%s`,`%s`)", header.Name, header.Value)) default: return nil, fmt.Errorf("unsupported header match type %s", *header.Type) @@ -1390,7 +1413,7 @@ func makeID(namespace, name string) string { return namespace + "-" + name } -func getTLS(k8sClient Client, secretName gatev1alpha2.ObjectName, namespace string) (*tls.CertAndStores, error) { +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) @@ -1406,8 +1429,8 @@ func getTLS(k8sClient Client, secretName gatev1alpha2.ObjectName, namespace stri return &tls.CertAndStores{ Certificate: tls.Certificate{ - CertFile: tls.FileOrContent(cert), - KeyFile: tls.FileOrContent(key), + CertFile: types.FileOrContent(cert), + KeyFile: types.FileOrContent(key), }, }, nil } @@ -1464,7 +1487,7 @@ func getCertificateBlocks(secret *corev1.Secret, namespace, secretName string) ( } // loadServices is generating a WRR service, even when there is only one target. -func loadServices(client Client, namespace string, backendRefs []gatev1alpha2.HTTPBackendRef) (*dynamic.Service, map[string]*dynamic.Service, error) { +func loadServices(client Client, namespace string, backendRefs []gatev1.HTTPBackendRef) (*dynamic.Service, map[string]*dynamic.Service, error) { services := map[string]*dynamic.Service{} wrrSvc := &dynamic.Service{ @@ -1483,7 +1506,7 @@ func loadServices(client Client, namespace string, backendRefs []gatev1alpha2.HT return nil, nil, fmt.Errorf("traefik internal service %s is not allowed in a WRR loadbalancer", backendRef.BackendRef.Name) } - weight := int(pointer.Int32Deref(backendRef.Weight, 1)) + weight := int(ptr.Deref(backendRef.Weight, 1)) if isTraefikService(backendRef.BackendRef) { wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.WRRService{Name: string(backendRef.Name), Weight: &weight}) @@ -1587,7 +1610,7 @@ func loadServices(client Client, namespace string, backendRefs []gatev1alpha2.HT } // loadTCPServices is generating a WRR service, even when there is only one target. -func loadTCPServices(client Client, namespace string, backendRefs []gatev1alpha2.BackendRef) (*dynamic.TCPService, map[string]*dynamic.TCPService, error) { +func loadTCPServices(client Client, namespace string, backendRefs []gatev1.BackendRef) (*dynamic.TCPService, map[string]*dynamic.TCPService, error) { services := map[string]*dynamic.TCPService{} wrrSvc := &dynamic.TCPService{ @@ -1606,7 +1629,7 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1alpha2 return nil, nil, fmt.Errorf("traefik internal service %s is not allowed in a WRR loadbalancer", backendRef.Name) } - weight := int(pointer.Int32Deref(backendRef.Weight, 1)) + weight := int(ptr.Deref(backendRef.Weight, 1)) if isTraefikService(backendRef) { wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: string(backendRef.Name), Weight: &weight}) @@ -1705,16 +1728,16 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1alpha2 return wrrSvc, services, nil } -func loadMiddlewares(listener gatev1alpha2.Listener, prefix string, filters []gatev1alpha2.HTTPRouteFilter) (map[string]*dynamic.Middleware, error) { +func loadMiddlewares(listener gatev1.Listener, prefix string, filters []gatev1.HTTPRouteFilter) (map[string]*dynamic.Middleware, error) { middlewares := make(map[string]*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. var listenerScheme string switch listener.Protocol { - case gatev1alpha2.HTTPProtocolType: + case gatev1.HTTPProtocolType: listenerScheme = "http" - case gatev1alpha2.HTTPSProtocolType: + case gatev1.HTTPSProtocolType: listenerScheme = "https" default: return nil, fmt.Errorf("invalid listener protocol %s", listener.Protocol) @@ -1723,7 +1746,7 @@ func loadMiddlewares(listener gatev1alpha2.Listener, prefix string, filters []ga for i, filter := range filters { var middleware *dynamic.Middleware switch filter.Type { - case gatev1alpha2.HTTPRouteFilterRequestRedirect: + case gatev1.HTTPRouteFilterRequestRedirect: var err error middleware, err = createRedirectRegexMiddleware(listenerScheme, filter.RequestRedirect) if err != nil { @@ -1745,7 +1768,7 @@ func loadMiddlewares(listener gatev1alpha2.Listener, prefix string, filters []ga return middlewares, nil } -func createRedirectRegexMiddleware(scheme string, filter *gatev1alpha2.HTTPRequestRedirectFilter) (*dynamic.Middleware, error) { +func createRedirectRegexMiddleware(scheme string, filter *gatev1.HTTPRequestRedirectFilter) (*dynamic.Middleware, error) { // Use the HTTPRequestRedirectFilter scheme if defined. filterScheme := scheme if filter.Scheme != nil { @@ -1823,7 +1846,7 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s return eventsChanBuffered } -func isTraefikService(ref gatev1alpha2.BackendRef) bool { +func isTraefikService(ref gatev1.BackendRef) bool { if ref.Kind == nil || ref.Group == nil { return false } @@ -1831,13 +1854,13 @@ func isTraefikService(ref gatev1alpha2.BackendRef) bool { return *ref.Group == traefikv1alpha1.GroupName && *ref.Kind == kindTraefikService } -func isInternalService(ref gatev1alpha2.BackendRef) bool { +func isInternalService(ref gatev1.BackendRef) bool { return isTraefikService(ref) && strings.HasSuffix(string(ref.Name), "@internal") } // makeListenerKey joins protocol, hostname, and port of a listener into a string key. -func makeListenerKey(l gatev1alpha2.Listener) string { - var hostname gatev1alpha2.Hostname +func makeListenerKey(l gatev1.Listener) string { + var hostname gatev1.Hostname if l.Hostname != nil { hostname = *l.Hostname } diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index e3783796e..7c747a270 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -11,9 +11,10 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" - gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "k8s.io/utils/ptr" + gatev1 "sigs.k8s.io/gateway-api/apis/v1" ) var _ provider.Provider = (*Provider)(nil) @@ -492,8 +493,8 @@ func TestLoadHTTPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -573,7 +574,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -668,7 +669,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -728,7 +729,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -741,8 +742,8 @@ func TestLoadHTTPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -796,7 +797,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -855,7 +856,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -914,7 +915,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -988,7 +989,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1004,7 +1005,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.4:8080", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1067,7 +1068,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1083,7 +1084,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.4:8080", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1163,7 +1164,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1176,8 +1177,8 @@ func TestLoadHTTPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -1252,7 +1253,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1265,8 +1266,8 @@ func TestLoadHTTPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -1335,7 +1336,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1394,7 +1395,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1468,7 +1469,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1484,7 +1485,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.12:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1543,7 +1544,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.12:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1611,7 +1612,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -1678,7 +1679,7 @@ func TestLoadHTTPRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -2240,8 +2241,8 @@ func TestLoadTCPRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -2735,8 +2736,8 @@ func TestLoadTLSRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -2947,8 +2948,8 @@ func TestLoadTLSRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -3016,8 +3017,8 @@ func TestLoadTLSRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -3810,7 +3811,7 @@ func TestLoadMixedRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -3823,8 +3824,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -3989,7 +3990,7 @@ func TestLoadMixedRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -4002,8 +4003,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -4194,7 +4195,7 @@ func TestLoadMixedRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -4210,7 +4211,7 @@ func TestLoadMixedRoutes(t *testing.T) { URL: "http://10.10.0.12:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -4243,8 +4244,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -4361,7 +4362,7 @@ func TestLoadMixedRoutes(t *testing.T) { URL: "http://10.10.0.12:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -4394,8 +4395,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -4513,7 +4514,7 @@ func TestLoadMixedRoutes(t *testing.T) { URL: "http://10.10.0.2:80", }, }, - PassHostHeader: pointer.Bool(true), + PassHostHeader: ptr.To(true), ResponseForwarding: &dynamic.ResponseForwarding{ FlushInterval: ptypes.Duration(100 * time.Millisecond), }, @@ -4526,8 +4527,8 @@ func TestLoadMixedRoutes(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -4555,7 +4556,7 @@ func TestLoadMixedRoutes(t *testing.T) { func Test_hostRule(t *testing.T) { testCases := []struct { desc string - hostnames []gatev1alpha2.Hostname + hostnames []gatev1.Hostname expectedRule string expectErr bool }{ @@ -4565,14 +4566,14 @@ func Test_hostRule(t *testing.T) { }, { desc: "One Host", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "Foo", }, expectedRule: "Host(`Foo`)", }, { desc: "Multiple Hosts", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "Foo", "Bar", "Bir", @@ -4581,7 +4582,7 @@ func Test_hostRule(t *testing.T) { }, { desc: "Multiple Hosts with empty one", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "Foo", "", "Bir", @@ -4590,7 +4591,7 @@ func Test_hostRule(t *testing.T) { }, { desc: "Multiple empty hosts", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "", "", "", @@ -4599,7 +4600,7 @@ func Test_hostRule(t *testing.T) { }, { desc: "Several Host and wildcard", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "*.bar.foo", "bar.foo", "foo.foo", @@ -4608,21 +4609,21 @@ func Test_hostRule(t *testing.T) { }, { desc: "Host with wildcard", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "*.bar.foo", }, expectedRule: "HostRegexp(`^[a-zA-Z0-9-]+\\.bar\\.foo$`)", }, { desc: "Alone wildcard", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "*", "*.foo.foo", }, }, { desc: "Multiple alone Wildcard", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "foo.foo", "*.*", }, @@ -4630,7 +4631,7 @@ func Test_hostRule(t *testing.T) { }, { desc: "Multiple Wildcard", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "foo.foo", "*.toto.*.bar.foo", }, @@ -4638,7 +4639,7 @@ func Test_hostRule(t *testing.T) { }, { desc: "Multiple subdomain with misplaced wildcard", - hostnames: []gatev1alpha2.Hostname{ + hostnames: []gatev1.Hostname{ "foo.foo", "toto.*.bar.foo", }, @@ -4663,7 +4664,7 @@ func Test_hostRule(t *testing.T) { func Test_extractRule(t *testing.T) { testCases := []struct { desc string - routeRule gatev1alpha2.HTTPRouteRule + routeRule gatev1.HTTPRouteRule hostRule string expectedRule string expectedError bool @@ -4679,8 +4680,8 @@ func Test_extractRule(t *testing.T) { }, { desc: "One HTTPRouteMatch with nil HTTPHeaderMatch", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ {Headers: nil}, }, }, @@ -4688,10 +4689,10 @@ func Test_extractRule(t *testing.T) { }, { desc: "One HTTPRouteMatch with nil HTTPHeaderMatch Type", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Headers: []gatev1alpha2.HTTPHeaderMatch{ + Headers: []gatev1.HTTPHeaderMatch{ {Type: nil, Name: "foo", Value: "bar"}, }, }, @@ -4701,8 +4702,8 @@ func Test_extractRule(t *testing.T) { }, { desc: "One HTTPRouteMatch with nil HTTPPathMatch", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ {Path: nil}, }, }, @@ -4710,12 +4711,12 @@ func Test_extractRule(t *testing.T) { }, { desc: "One HTTPRouteMatch with nil HTTPPathMatch Type", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Path: &gatev1alpha2.HTTPPathMatch{ + Path: &gatev1.HTTPPathMatch{ Type: nil, - Value: pointer.String("/foo/"), + Value: ptr.To("/foo/"), }, }, }, @@ -4724,11 +4725,11 @@ func Test_extractRule(t *testing.T) { }, { desc: "One HTTPRouteMatch with nil HTTPPathMatch Values", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Path: &gatev1alpha2.HTTPPathMatch{ - Type: pathMatchTypePtr(gatev1alpha2.PathMatchExact), + Path: &gatev1.HTTPPathMatch{ + Type: pathMatchTypePtr(gatev1.PathMatchExact), Value: nil, }, }, @@ -4738,12 +4739,12 @@ func Test_extractRule(t *testing.T) { }, { desc: "One Path in matches", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Path: &gatev1alpha2.HTTPPathMatch{ - Type: pathMatchTypePtr(gatev1alpha2.PathMatchExact), - Value: pointer.String("/foo/"), + Path: &gatev1.HTTPPathMatch{ + Type: pathMatchTypePtr(gatev1.PathMatchExact), + Value: ptr.To("/foo/"), }, }, }, @@ -4752,18 +4753,18 @@ func Test_extractRule(t *testing.T) { }, { desc: "One Path in matches and another unknown", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Path: &gatev1alpha2.HTTPPathMatch{ - Type: pathMatchTypePtr(gatev1alpha2.PathMatchExact), - Value: pointer.String("/foo/"), + Path: &gatev1.HTTPPathMatch{ + Type: pathMatchTypePtr(gatev1.PathMatchExact), + Value: ptr.To("/foo/"), }, }, { - Path: &gatev1alpha2.HTTPPathMatch{ + Path: &gatev1.HTTPPathMatch{ Type: pathMatchTypePtr("unknown"), - Value: pointer.String("/foo/"), + Value: ptr.To("/foo/"), }, }, }, @@ -4772,12 +4773,12 @@ func Test_extractRule(t *testing.T) { }, { desc: "One Path in matches and another empty", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Path: &gatev1alpha2.HTTPPathMatch{ - Type: pathMatchTypePtr(gatev1alpha2.PathMatchExact), - Value: pointer.String("/foo/"), + Path: &gatev1.HTTPPathMatch{ + Type: pathMatchTypePtr(gatev1.PathMatchExact), + Value: ptr.To("/foo/"), }, }, {}, @@ -4787,18 +4788,18 @@ func Test_extractRule(t *testing.T) { }, { desc: "Path OR Header rules", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Path: &gatev1alpha2.HTTPPathMatch{ - Type: pathMatchTypePtr(gatev1alpha2.PathMatchExact), - Value: pointer.String("/foo/"), + Path: &gatev1.HTTPPathMatch{ + Type: pathMatchTypePtr(gatev1.PathMatchExact), + Value: ptr.To("/foo/"), }, }, { - Headers: []gatev1alpha2.HTTPHeaderMatch{ + Headers: []gatev1.HTTPHeaderMatch{ { - Type: headerMatchTypePtr(gatev1alpha2.HeaderMatchExact), + Type: headerMatchTypePtr(gatev1.HeaderMatchExact), Name: "my-header", Value: "foo", }, @@ -4810,16 +4811,16 @@ func Test_extractRule(t *testing.T) { }, { desc: "Path && Header rules", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Path: &gatev1alpha2.HTTPPathMatch{ - Type: pathMatchTypePtr(gatev1alpha2.PathMatchExact), - Value: pointer.String("/foo/"), + Path: &gatev1.HTTPPathMatch{ + Type: pathMatchTypePtr(gatev1.PathMatchExact), + Value: ptr.To("/foo/"), }, - Headers: []gatev1alpha2.HTTPHeaderMatch{ + Headers: []gatev1.HTTPHeaderMatch{ { - Type: headerMatchTypePtr(gatev1alpha2.HeaderMatchExact), + Type: headerMatchTypePtr(gatev1.HeaderMatchExact), Name: "my-header", Value: "foo", }, @@ -4832,16 +4833,16 @@ func Test_extractRule(t *testing.T) { { desc: "Host && Path && Header rules", hostRule: "Host(`foo.com`)", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Path: &gatev1alpha2.HTTPPathMatch{ - Type: pathMatchTypePtr(gatev1alpha2.PathMatchExact), - Value: pointer.String("/foo/"), + Path: &gatev1.HTTPPathMatch{ + Type: pathMatchTypePtr(gatev1.PathMatchExact), + Value: ptr.To("/foo/"), }, - Headers: []gatev1alpha2.HTTPHeaderMatch{ + Headers: []gatev1.HTTPHeaderMatch{ { - Type: headerMatchTypePtr(gatev1alpha2.HeaderMatchExact), + Type: headerMatchTypePtr(gatev1.HeaderMatchExact), Name: "my-header", Value: "foo", }, @@ -4854,18 +4855,18 @@ func Test_extractRule(t *testing.T) { { desc: "Host && (Path || Header) rules", hostRule: "Host(`foo.com`)", - routeRule: gatev1alpha2.HTTPRouteRule{ - Matches: []gatev1alpha2.HTTPRouteMatch{ + routeRule: gatev1.HTTPRouteRule{ + Matches: []gatev1.HTTPRouteMatch{ { - Path: &gatev1alpha2.HTTPPathMatch{ - Type: pathMatchTypePtr(gatev1alpha2.PathMatchExact), - Value: pointer.String("/foo/"), + Path: &gatev1.HTTPPathMatch{ + Type: pathMatchTypePtr(gatev1.PathMatchExact), + Value: ptr.To("/foo/"), }, }, { - Headers: []gatev1alpha2.HTTPHeaderMatch{ + Headers: []gatev1.HTTPHeaderMatch{ { - Type: headerMatchTypePtr(gatev1alpha2.HeaderMatchExact), + Type: headerMatchTypePtr(gatev1.HeaderMatchExact), Name: "my-header", Value: "foo", }, @@ -4897,7 +4898,7 @@ func Test_extractRule(t *testing.T) { func Test_hostSNIRule(t *testing.T) { testCases := []struct { desc string - hostnames []gatev1alpha2.Hostname + hostnames []gatev1.Hostname expectedRule string expectError bool }{ @@ -4907,47 +4908,47 @@ func Test_hostSNIRule(t *testing.T) { }, { desc: "Empty hostname", - hostnames: []gatev1alpha2.Hostname{""}, + hostnames: []gatev1.Hostname{""}, expectedRule: "HostSNI(`*`)", }, { desc: "Unsupported wildcard", - hostnames: []gatev1alpha2.Hostname{"*"}, + hostnames: []gatev1.Hostname{"*"}, expectError: true, }, { desc: "Supported wildcard", - hostnames: []gatev1alpha2.Hostname{"*.foo"}, + hostnames: []gatev1.Hostname{"*.foo"}, expectedRule: "HostSNIRegexp(`^[a-zA-Z0-9-]+\\.foo$`)", }, { desc: "Multiple malformed wildcard", - hostnames: []gatev1alpha2.Hostname{"*.foo.*"}, + hostnames: []gatev1.Hostname{"*.foo.*"}, expectError: true, }, { desc: "Some empty hostnames", - hostnames: []gatev1alpha2.Hostname{"foo", "", "bar"}, + hostnames: []gatev1.Hostname{"foo", "", "bar"}, expectedRule: "HostSNI(`foo`) || HostSNI(`bar`)", }, { desc: "Valid hostname", - hostnames: []gatev1alpha2.Hostname{"foo"}, + hostnames: []gatev1.Hostname{"foo"}, expectedRule: "HostSNI(`foo`)", }, { desc: "Multiple valid hostnames", - hostnames: []gatev1alpha2.Hostname{"foo", "bar"}, + hostnames: []gatev1.Hostname{"foo", "bar"}, expectedRule: "HostSNI(`foo`) || HostSNI(`bar`)", }, { desc: "Multiple valid hostnames with wildcard", - hostnames: []gatev1alpha2.Hostname{"bar.foo", "foo.foo", "*.foo"}, + hostnames: []gatev1.Hostname{"bar.foo", "foo.foo", "*.foo"}, expectedRule: "HostSNI(`bar.foo`) || HostSNI(`foo.foo`) || HostSNIRegexp(`^[a-zA-Z0-9-]+\\.foo$`)", }, { desc: "Multiple overlapping hostnames", - hostnames: []gatev1alpha2.Hostname{"foo", "bar", "foo", "baz"}, + hostnames: []gatev1.Hostname{"foo", "bar", "foo", "baz"}, expectedRule: "HostSNI(`foo`) || HostSNI(`bar`) || HostSNI(`baz`)", }, } @@ -4972,49 +4973,49 @@ func Test_hostSNIRule(t *testing.T) { func Test_shouldAttach(t *testing.T) { testCases := []struct { desc string - gateway *gatev1alpha2.Gateway - listener gatev1alpha2.Listener + gateway *gatev1.Gateway + listener gatev1.Listener routeNamespace string - routeSpec gatev1alpha2.CommonRouteSpec + routeSpec gatev1.CommonRouteSpec expectedAttach bool }{ { desc: "No ParentRefs", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "default", - routeSpec: gatev1alpha2.CommonRouteSpec{ + routeSpec: gatev1.CommonRouteSpec{ ParentRefs: nil, }, expectedAttach: false, }, { desc: "Unsupported Kind", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "default", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("bar"), Name: "gateway", Namespace: namespacePtr("default"), Kind: kindPtr("Foo"), - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), }, }, }, @@ -5022,18 +5023,18 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "Unsupported Group", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "default", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("bar"), Name: "gateway", @@ -5047,23 +5048,23 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "Kind is nil", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "default", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("bar"), Name: "gateway", Namespace: namespacePtr("default"), - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), }, }, }, @@ -5071,18 +5072,18 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "Group is nil", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "default", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("bar"), Name: "gateway", @@ -5095,23 +5096,23 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "SectionName does not match a listener desc", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "default", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("bar"), Name: "gateway", Namespace: namespacePtr("default"), - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), Kind: kindPtr("Gateway"), }, }, @@ -5120,23 +5121,23 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "Namespace does not match the Gateway namespace", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "default", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("bar"), Name: "gateway", Namespace: namespacePtr("bar"), - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), Kind: kindPtr("Gateway"), }, }, @@ -5145,22 +5146,22 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "Route namespace does not match the Gateway namespace", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "bar", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("bar"), Name: "gateway", - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), Kind: kindPtr("Gateway"), }, }, @@ -5169,24 +5170,24 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "Unsupported Kind", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "default", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("bar"), Name: "gateway", Namespace: namespacePtr("default"), Kind: kindPtr("Gateway"), - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), }, }, }, @@ -5194,23 +5195,23 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "Route namespace matches the Gateway namespace", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "default", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("foo"), Name: "gateway", Kind: kindPtr("Gateway"), - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), }, }, }, @@ -5218,24 +5219,24 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "Namespace matches the Gateway namespace", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "bar", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { SectionName: sectionNamePtr("foo"), Name: "gateway", Namespace: namespacePtr("default"), Kind: kindPtr("Gateway"), - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), }, }, }, @@ -5243,29 +5244,29 @@ func Test_shouldAttach(t *testing.T) { }, { desc: "Only one ParentRef matches the Gateway", - gateway: &gatev1alpha2.Gateway{ + gateway: &gatev1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway", Namespace: "default", }, }, - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Name: "foo", }, routeNamespace: "bar", - routeSpec: gatev1alpha2.CommonRouteSpec{ - ParentRefs: []gatev1alpha2.ParentRef{ + routeSpec: gatev1.CommonRouteSpec{ + ParentRefs: []gatev1.ParentReference{ { Name: "gateway2", Namespace: namespacePtr("default"), Kind: kindPtr("Gateway"), - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), }, { Name: "gateway", Namespace: namespacePtr("default"), Kind: kindPtr("Gateway"), - Group: groupPtr(gatev1alpha2.GroupName), + Group: groupPtr(gatev1.GroupName), }, }, }, @@ -5287,93 +5288,93 @@ func Test_shouldAttach(t *testing.T) { func Test_matchingHostnames(t *testing.T) { testCases := []struct { desc string - listener gatev1alpha2.Listener - hostnames []gatev1alpha2.Hostname - want []gatev1alpha2.Hostname + listener gatev1.Listener + hostnames []gatev1.Hostname + want []gatev1.Hostname }{ { desc: "Empty", }, { desc: "Only listener hostname", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("foo.com"), }, - want: []gatev1alpha2.Hostname{"foo.com"}, + want: []gatev1.Hostname{"foo.com"}, }, { desc: "Only Route hostname", - hostnames: []gatev1alpha2.Hostname{"foo.com"}, - want: []gatev1alpha2.Hostname{"foo.com"}, + hostnames: []gatev1.Hostname{"foo.com"}, + want: []gatev1.Hostname{"foo.com"}, }, { desc: "Matching hostname", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("foo.com"), }, - hostnames: []gatev1alpha2.Hostname{"foo.com"}, - want: []gatev1alpha2.Hostname{"foo.com"}, + hostnames: []gatev1.Hostname{"foo.com"}, + want: []gatev1.Hostname{"foo.com"}, }, { desc: "Matching hostname with wildcard", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("*.foo.com"), }, - hostnames: []gatev1alpha2.Hostname{"*.foo.com"}, - want: []gatev1alpha2.Hostname{"*.foo.com"}, + hostnames: []gatev1.Hostname{"*.foo.com"}, + want: []gatev1.Hostname{"*.foo.com"}, }, { desc: "Matching subdomain with listener wildcard", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("*.foo.com"), }, - hostnames: []gatev1alpha2.Hostname{"bar.foo.com"}, - want: []gatev1alpha2.Hostname{"bar.foo.com"}, + hostnames: []gatev1.Hostname{"bar.foo.com"}, + want: []gatev1.Hostname{"bar.foo.com"}, }, { desc: "Matching subdomain with route hostname wildcard", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("bar.foo.com"), }, - hostnames: []gatev1alpha2.Hostname{"*.foo.com"}, - want: []gatev1alpha2.Hostname{"bar.foo.com"}, + hostnames: []gatev1.Hostname{"*.foo.com"}, + want: []gatev1.Hostname{"bar.foo.com"}, }, { desc: "Non matching root domain with listener wildcard", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("*.foo.com"), }, - hostnames: []gatev1alpha2.Hostname{"foo.com"}, + hostnames: []gatev1.Hostname{"foo.com"}, }, { desc: "Non matching root domain with route hostname wildcard", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("foo.com"), }, - hostnames: []gatev1alpha2.Hostname{"*.foo.com"}, + hostnames: []gatev1.Hostname{"*.foo.com"}, }, { desc: "Multiple route hostnames with one matching route hostname", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("*.foo.com"), }, - hostnames: []gatev1alpha2.Hostname{"bar.com", "test.foo.com", "test.buz.com"}, - want: []gatev1alpha2.Hostname{"test.foo.com"}, + hostnames: []gatev1.Hostname{"bar.com", "test.foo.com", "test.buz.com"}, + want: []gatev1.Hostname{"test.foo.com"}, }, { desc: "Multiple route hostnames with non matching route hostname", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("*.fuz.com"), }, - hostnames: []gatev1alpha2.Hostname{"bar.com", "test.foo.com", "test.buz.com"}, + hostnames: []gatev1.Hostname{"bar.com", "test.foo.com", "test.buz.com"}, }, { desc: "Multiple route hostnames with multiple matching route hostnames", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Hostname: hostnamePtr("*.foo.com"), }, - hostnames: []gatev1alpha2.Hostname{"toto.foo.com", "test.foo.com", "test.buz.com"}, - want: []gatev1alpha2.Hostname{"toto.foo.com", "test.foo.com"}, + hostnames: []gatev1.Hostname{"toto.foo.com", "test.foo.com", "test.buz.com"}, + want: []gatev1.Hostname{"toto.foo.com", "test.foo.com"}, }, } @@ -5391,9 +5392,9 @@ func Test_matchingHostnames(t *testing.T) { func Test_getAllowedRoutes(t *testing.T) { testCases := []struct { desc string - listener gatev1alpha2.Listener - supportedRouteKinds []gatev1alpha2.RouteGroupKind - wantKinds []gatev1alpha2.RouteGroupKind + listener gatev1.Listener + supportedRouteKinds []gatev1.RouteGroupKind + wantKinds []gatev1.RouteGroupKind wantErr bool }{ { @@ -5401,90 +5402,90 @@ func Test_getAllowedRoutes(t *testing.T) { }, { desc: "Empty AllowedRoutes", - supportedRouteKinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + supportedRouteKinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, }, - wantKinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + wantKinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, }, }, { desc: "AllowedRoutes with unsupported Group", - listener: gatev1alpha2.Listener{ - AllowedRoutes: &gatev1alpha2.AllowedRoutes{ - Kinds: []gatev1alpha2.RouteGroupKind{{ + listener: gatev1.Listener{ + AllowedRoutes: &gatev1.AllowedRoutes{ + Kinds: []gatev1.RouteGroupKind{{ Kind: kindTLSRoute, Group: groupPtr("foo"), }}, }, }, - supportedRouteKinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + supportedRouteKinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, }, wantErr: true, }, { desc: "AllowedRoutes with nil Group", - listener: gatev1alpha2.Listener{ - AllowedRoutes: &gatev1alpha2.AllowedRoutes{ - Kinds: []gatev1alpha2.RouteGroupKind{{ + listener: gatev1.Listener{ + AllowedRoutes: &gatev1.AllowedRoutes{ + Kinds: []gatev1.RouteGroupKind{{ Kind: kindTLSRoute, Group: nil, }}, }, }, - supportedRouteKinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + supportedRouteKinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, }, wantErr: true, }, { desc: "AllowedRoutes with unsupported Kind", - listener: gatev1alpha2.Listener{ - AllowedRoutes: &gatev1alpha2.AllowedRoutes{ - Kinds: []gatev1alpha2.RouteGroupKind{{ - Kind: "foo", Group: groupPtr(gatev1alpha2.GroupName), + listener: gatev1.Listener{ + AllowedRoutes: &gatev1.AllowedRoutes{ + Kinds: []gatev1.RouteGroupKind{{ + Kind: "foo", Group: groupPtr(gatev1.GroupName), }}, }, }, - supportedRouteKinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + supportedRouteKinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, }, wantErr: true, }, { desc: "Supported AllowedRoutes", - listener: gatev1alpha2.Listener{ - AllowedRoutes: &gatev1alpha2.AllowedRoutes{ - Kinds: []gatev1alpha2.RouteGroupKind{{ - Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName), + listener: gatev1.Listener{ + AllowedRoutes: &gatev1.AllowedRoutes{ + Kinds: []gatev1.RouteGroupKind{{ + Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName), }}, }, }, - supportedRouteKinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + supportedRouteKinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, }, - wantKinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + wantKinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, }, }, { desc: "Supported AllowedRoutes with duplicates", - listener: gatev1alpha2.Listener{ - AllowedRoutes: &gatev1alpha2.AllowedRoutes{ - Kinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, - {Kind: kindTCPRoute, Group: groupPtr(gatev1alpha2.GroupName)}, - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, - {Kind: kindTCPRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + listener: gatev1.Listener{ + AllowedRoutes: &gatev1.AllowedRoutes{ + Kinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, + {Kind: kindTCPRoute, Group: groupPtr(gatev1.GroupName)}, + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, + {Kind: kindTCPRoute, Group: groupPtr(gatev1.GroupName)}, }, }, }, - supportedRouteKinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, - {Kind: kindTCPRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + supportedRouteKinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, + {Kind: kindTCPRoute, Group: groupPtr(gatev1.GroupName)}, }, - wantKinds: []gatev1alpha2.RouteGroupKind{ - {Kind: kindTLSRoute, Group: groupPtr(gatev1alpha2.GroupName)}, - {Kind: kindTCPRoute, Group: groupPtr(gatev1alpha2.GroupName)}, + wantKinds: []gatev1.RouteGroupKind{ + {Kind: kindTLSRoute, Group: groupPtr(gatev1.GroupName)}, + {Kind: kindTCPRoute, Group: groupPtr(gatev1.GroupName)}, }, }, } @@ -5494,13 +5495,13 @@ func Test_getAllowedRoutes(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - got, conditions := getAllowedRouteKinds(test.listener, test.supportedRouteKinds) + got, conditions := getAllowedRouteKinds(&gatev1.Gateway{}, test.listener, test.supportedRouteKinds) if test.wantErr { require.NotEmpty(t, conditions, "no conditions") return } - require.Len(t, conditions, 0) + require.Empty(t, conditions) assert.Equal(t, test.wantKinds, got) }) } @@ -5509,7 +5510,7 @@ func Test_getAllowedRoutes(t *testing.T) { func Test_makeListenerKey(t *testing.T) { testCases := []struct { desc string - listener gatev1alpha2.Listener + listener gatev1.Listener expectedKey string }{ { @@ -5518,18 +5519,18 @@ func Test_makeListenerKey(t *testing.T) { }, { desc: "listener with port, protocol and hostname", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Port: 443, - Protocol: gatev1alpha2.HTTPSProtocolType, + Protocol: gatev1.HTTPSProtocolType, Hostname: hostnamePtr("www.example.com"), }, expectedKey: "HTTPS|www.example.com|443", }, { desc: "listener with port, protocol and nil hostname", - listener: gatev1alpha2.Listener{ + listener: gatev1.Listener{ Port: 443, - Protocol: gatev1alpha2.HTTPSProtocolType, + Protocol: gatev1.HTTPSProtocolType, }, expectedKey: "HTTPS||443", }, @@ -5545,26 +5546,26 @@ func Test_makeListenerKey(t *testing.T) { } } -func hostnamePtr(hostname gatev1alpha2.Hostname) *gatev1alpha2.Hostname { +func hostnamePtr(hostname gatev1.Hostname) *gatev1.Hostname { return &hostname } -func groupPtr(group gatev1alpha2.Group) *gatev1alpha2.Group { +func groupPtr(group gatev1.Group) *gatev1.Group { return &group } -func sectionNamePtr(sectionName gatev1alpha2.SectionName) *gatev1alpha2.SectionName { +func sectionNamePtr(sectionName gatev1.SectionName) *gatev1.SectionName { return §ionName } -func namespacePtr(namespace gatev1alpha2.Namespace) *gatev1alpha2.Namespace { +func namespacePtr(namespace gatev1.Namespace) *gatev1.Namespace { return &namespace } -func kindPtr(kind gatev1alpha2.Kind) *gatev1alpha2.Kind { +func kindPtr(kind gatev1.Kind) *gatev1.Kind { return &kind } -func pathMatchTypePtr(p gatev1alpha2.PathMatchType) *gatev1alpha2.PathMatchType { return &p } +func pathMatchTypePtr(p gatev1.PathMatchType) *gatev1.PathMatchType { return &p } -func headerMatchTypePtr(h gatev1alpha2.HeaderMatchType) *gatev1alpha2.HeaderMatchType { return &h } +func headerMatchTypePtr(h gatev1.HeaderMatchType) *gatev1.HeaderMatchType { return &h } diff --git a/pkg/provider/kubernetes/ingress/client.go b/pkg/provider/kubernetes/ingress/client.go index 8bbdbc637..c29f3c6b3 100644 --- a/pkg/provider/kubernetes/ingress/client.go +++ b/pkg/provider/kubernetes/ingress/client.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/go-version" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" + "github.com/traefik/traefik/v3/pkg/types" traefikversion "github.com/traefik/traefik/v3/pkg/version" corev1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" @@ -81,14 +82,19 @@ func newExternalClusterClientFromFile(file string) (*clientWrapper, error) { // newExternalClusterClient returns a new Provider client that may run outside // of the cluster. // The endpoint parameter must not be empty. -func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrapper, error) { +func newExternalClusterClient(endpoint, caFilePath string, token types.FileOrContent) (*clientWrapper, error) { if endpoint == "" { return nil, errors.New("endpoint missing for external cluster client") } + tokenData, err := token.Read() + if err != nil { + return nil, fmt.Errorf("read token: %w", err) + } + config := &rest.Config{ Host: endpoint, - BearerToken: token, + BearerToken: string(tokenData), } if caFilePath != "" { diff --git a/pkg/provider/kubernetes/ingress/convert.go b/pkg/provider/kubernetes/ingress/convert.go index d5c5006fc..1062e1fea 100644 --- a/pkg/provider/kubernetes/ingress/convert.go +++ b/pkg/provider/kubernetes/ingress/convert.go @@ -12,7 +12,7 @@ type marshaler interface { } type unmarshaler interface { - Unmarshal([]byte) error + Unmarshal(data []byte) error } type LoadBalancerIngress interface { diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 6f5c430c8..8ccbf4f58 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -25,6 +25,7 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" corev1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/labels" @@ -39,17 +40,17 @@ const ( // Provider holds configurations of the provider. type Provider struct { - Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` - CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` - Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` - LabelSelector string `description:"Kubernetes Ingress label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` - IngressClass string `description:"Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` - IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"` - ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` - AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` - AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"` - DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"` + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Token types.FileOrContent `description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` + CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` + Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"` + LabelSelector string `description:"Kubernetes Ingress label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` + IngressClass string `description:"Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` + IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"` + ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` + AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` + AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"` + DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"` lastConfiguration safe.Safe @@ -103,7 +104,7 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) { cl, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) default: logger.Info().Msgf("Creating cluster-external Provider client%s", withEndpoint) - cl, err = newExternalClusterClient(p.Endpoint, p.Token, p.CertAuthFilePath) + cl, err = newExternalClusterClient(p.Endpoint, p.CertAuthFilePath, p.Token) } if err != nil { @@ -324,7 +325,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl portString := pa.Backend.Service.Port.Name if len(pa.Backend.Service.Port.Name) == 0 { - portString = fmt.Sprint(pa.Backend.Service.Port.Number) + portString = strconv.Itoa(int(pa.Backend.Service.Port.Number)) } serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.Service.Name + "-" + portString) @@ -463,8 +464,8 @@ func getCertificates(ctx context.Context, ingress *netv1.Ingress, k8sClient Clie tlsConfigs[configKey] = &tls.CertAndStores{ Certificate: tls.Certificate{ - CertFile: tls.FileOrContent(cert), - KeyFile: tls.FileOrContent(key), + CertFile: types.FileOrContent(cert), + KeyFile: types.FileOrContent(key), }, } } diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index e044cf577..cffccbbcb 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -877,8 +877,8 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), - KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), }, }, }, @@ -1834,14 +1834,14 @@ func TestGetCertificates(t *testing.T) { result: map[string]*tls.CertAndStores{ "testing-test-secret": { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("tls-crt"), - KeyFile: tls.FileOrContent("tls-key"), + CertFile: types.FileOrContent("tls-crt"), + KeyFile: types.FileOrContent("tls-key"), }, }, "testing-test-secret2": { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("tls-crt"), - KeyFile: tls.FileOrContent("tls-key"), + CertFile: types.FileOrContent("tls-crt"), + KeyFile: types.FileOrContent("tls-key"), }, }, }, diff --git a/pkg/provider/kubernetes/k8s/event_handler.go b/pkg/provider/kubernetes/k8s/event_handler.go index 92b450bec..14475c18c 100644 --- a/pkg/provider/kubernetes/k8s/event_handler.go +++ b/pkg/provider/kubernetes/k8s/event_handler.go @@ -11,7 +11,7 @@ type ResourceEventHandler struct { } // OnAdd is called on Add Events. -func (reh *ResourceEventHandler) OnAdd(obj interface{}) { +func (reh *ResourceEventHandler) OnAdd(obj interface{}, isInInitialList bool) { eventHandlerFunc(reh.Ev, obj) } diff --git a/pkg/provider/kv/kv.go b/pkg/provider/kv/kv.go index fd2671306..613320617 100644 --- a/pkg/provider/kv/kv.go +++ b/pkg/provider/kv/kv.go @@ -134,6 +134,16 @@ func (p *Provider) watchKv(ctx context.Context, configurationChan chan<- dynamic func (p *Provider) buildConfiguration(ctx context.Context) (*dynamic.Configuration, error) { pairs, err := p.kvClient.List(ctx, p.RootKey, nil) if err != nil { + if errors.Is(err, store.ErrKeyNotFound) { + // This empty configuration satisfies the pkg/server/configurationwatcher.go isEmptyConfiguration func constraints, + // and will not be discarded by the configuration watcher. + return &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: make(map[string]*dynamic.Router), + }, + }, nil + } + return nil, err } diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index 8ba2cde99..cfe901037 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -811,8 +811,8 @@ func Test_buildConfiguration(t *testing.T) { Certificates: []*tls.CertAndStores{ { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("foobar"), - KeyFile: tls.FileOrContent("foobar"), + CertFile: types.FileOrContent("foobar"), + KeyFile: types.FileOrContent("foobar"), }, Stores: []string{ "foobar", @@ -821,8 +821,8 @@ func Test_buildConfiguration(t *testing.T) { }, { Certificate: tls.Certificate{ - CertFile: tls.FileOrContent("foobar"), - KeyFile: tls.FileOrContent("foobar"), + CertFile: types.FileOrContent("foobar"), + KeyFile: types.FileOrContent("foobar"), }, Stores: []string{ "foobar", @@ -843,9 +843,9 @@ func Test_buildConfiguration(t *testing.T) { "foobar", }, ClientAuth: tls.ClientAuth{ - CAFiles: []tls.FileOrContent{ - tls.FileOrContent("foobar"), - tls.FileOrContent("foobar"), + CAFiles: []types.FileOrContent{ + types.FileOrContent("foobar"), + types.FileOrContent("foobar"), }, ClientAuthType: "foobar", }, @@ -868,9 +868,9 @@ func Test_buildConfiguration(t *testing.T) { "foobar", }, ClientAuth: tls.ClientAuth{ - CAFiles: []tls.FileOrContent{ - tls.FileOrContent("foobar"), - tls.FileOrContent("foobar"), + CAFiles: []types.FileOrContent{ + types.FileOrContent("foobar"), + types.FileOrContent("foobar"), }, ClientAuthType: "foobar", }, @@ -885,14 +885,14 @@ func Test_buildConfiguration(t *testing.T) { Stores: map[string]tls.Store{ "Store0": { DefaultCertificate: &tls.Certificate{ - CertFile: tls.FileOrContent("foobar"), - KeyFile: tls.FileOrContent("foobar"), + CertFile: types.FileOrContent("foobar"), + KeyFile: types.FileOrContent("foobar"), }, }, "Store1": { DefaultCertificate: &tls.Certificate{ - CertFile: tls.FileOrContent("foobar"), - KeyFile: tls.FileOrContent("foobar"), + CertFile: types.FileOrContent("foobar"), + KeyFile: types.FileOrContent("foobar"), }, }, }, diff --git a/pkg/provider/kv/redis/redis.go b/pkg/provider/kv/redis/redis.go index ddd7178cb..874a6b1a1 100644 --- a/pkg/provider/kv/redis/redis.go +++ b/pkg/provider/kv/redis/redis.go @@ -2,6 +2,7 @@ package redis import ( "context" + "errors" "fmt" "github.com/kvtools/redis" @@ -20,6 +21,20 @@ type Provider struct { Username string `description:"Username for authentication." json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" loggable:"false"` Password string `description:"Password for authentication." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"` DB int `description:"Database to be selected after connecting to the server." json:"db,omitempty" toml:"db,omitempty" yaml:"db,omitempty"` + Sentinel *Sentinel `description:"Enable Sentinel support." json:"sentinel,omitempty" toml:"sentinel,omitempty" yaml:"sentinel,omitempty"` +} + +// Sentinel holds the Redis Sentinel configuration. +type Sentinel struct { + MasterName string `description:"Name of the master." json:"masterName,omitempty" toml:"masterName,omitempty" yaml:"masterName,omitempty" export:"true"` + Username string `description:"Username for Sentinel authentication." json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" export:"true"` + Password string `description:"Password for Sentinel authentication." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" export:"true"` + + LatencyStrategy bool `description:"Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy)." json:"latencyStrategy,omitempty" toml:"latencyStrategy,omitempty" yaml:"latencyStrategy,omitempty" export:"true"` + RandomStrategy bool `description:"Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy)." json:"randomStrategy,omitempty" toml:"randomStrategy,omitempty" yaml:"randomStrategy,omitempty" export:"true"` + ReplicaStrategy bool `description:"Defines whether to route all commands to replica nodes (mutually exclusive with LatencyStrategy and RandomStrategy)." json:"replicaStrategy,omitempty" toml:"replicaStrategy,omitempty" yaml:"replicaStrategy,omitempty" export:"true"` + + UseDisconnectedReplicas bool `description:"Use replicas disconnected with master when cannot get connected replicas." json:"useDisconnectedReplicas,omitempty" toml:"useDisconnectedReplicas,omitempty" yaml:"useDisconnectedReplicas,omitempty" export:"true"` } // SetDefaults sets the default values. @@ -44,5 +59,26 @@ func (p *Provider) Init() error { } } + if p.Sentinel != nil { + switch { + case p.Sentinel.LatencyStrategy && !(p.Sentinel.RandomStrategy || p.Sentinel.ReplicaStrategy): + case p.Sentinel.RandomStrategy && !(p.Sentinel.LatencyStrategy || p.Sentinel.ReplicaStrategy): + case p.Sentinel.ReplicaStrategy && !(p.Sentinel.RandomStrategy || p.Sentinel.LatencyStrategy): + return errors.New("latencyStrategy, randomStrategy and replicaStrategy options are mutually exclusive, please use only one of those options") + } + + clusterClient := p.Sentinel.LatencyStrategy || p.Sentinel.RandomStrategy + config.Sentinel = &redis.Sentinel{ + MasterName: p.Sentinel.MasterName, + Username: p.Sentinel.Username, + Password: p.Sentinel.Password, + ClusterClient: clusterClient, + RouteByLatency: p.Sentinel.LatencyStrategy, + RouteRandomly: p.Sentinel.RandomStrategy, + ReplicaOnly: p.Sentinel.ReplicaStrategy, + UseDisconnectedReplicas: p.Sentinel.UseDisconnectedReplicas, + } + } + return p.Provider.Init(redis.StoreName, "redis", config) } diff --git a/pkg/provider/nomad/config_test.go b/pkg/provider/nomad/config_test.go index db7bb3855..1064967d0 100644 --- a/pkg/provider/nomad/config_test.go +++ b/pkg/provider/nomad/config_test.go @@ -45,8 +45,9 @@ func Test_defaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`example.com`)", + Service: "Test", + Rule: "Host(`example.com`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -99,8 +100,9 @@ func Test_defaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: `Host("Test.example.com")`, + Service: "Test", + Rule: `Host("Test.example.com")`, + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -196,8 +198,9 @@ func Test_defaultRule(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test`)", + Service: "Test", + Rule: "Host(`Test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -270,8 +273,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "dev-Test": { - Service: "dev-Test", - Rule: "Host(`dev-Test.traefik.test`)", + Service: "dev-Test", + Rule: "Host(`dev-Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -328,12 +332,14 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test1": { - Service: "Test1", - Rule: "Host(`Test1.traefik.test`)", + Service: "Test1", + Rule: "Host(`Test1.traefik.test`)", + DefaultRule: true, }, "Test2": { - Service: "Test2", - Rule: "Host(`Test2.traefik.test`)", + Service: "Test2", + Rule: "Host(`Test2.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -405,8 +411,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.test`)", + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -468,8 +475,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.test`)", + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -528,8 +536,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.test`)", + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -583,8 +592,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.test`)", + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -866,8 +876,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.test`)", + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -924,8 +935,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.test`)", + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -972,8 +984,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.test`)", + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1027,8 +1040,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.test`)", + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Services: map[string]*dynamic.Service{ @@ -1095,8 +1109,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.test`)", + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{ @@ -1166,8 +1181,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.test`)", + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1346,8 +1362,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.test`)", + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1399,8 +1416,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.test`)", + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1643,8 +1661,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.test`)", + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -1699,6 +1718,7 @@ func Test_buildConfig(t *testing.T) { Service: "Test", Rule: "Host(`Test.traefik.test`)", Middlewares: []string{"Middleware1"}, + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{ @@ -2093,8 +2113,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.test`)", + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2180,8 +2201,9 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Service1", - Rule: "Host(`Test.traefik.test`)", + Service: "Service1", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, @@ -2382,12 +2404,14 @@ func Test_buildConfig(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "Test": { - Service: "Test", - Rule: "Host(`Test.traefik.test`)", + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, "Test-1234154071633021619": { - Service: "Test-1234154071633021619", - Rule: "Host(`Test.traefik.test`)", + Service: "Test-1234154071633021619", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, }, }, Middlewares: map[string]*dynamic.Middleware{}, diff --git a/pkg/provider/tailscale/provider.go b/pkg/provider/tailscale/provider.go index b0b439b86..8e795e5be 100644 --- a/pkg/provider/tailscale/provider.go +++ b/pkg/provider/tailscale/provider.go @@ -17,6 +17,7 @@ import ( "github.com/traefik/traefik/v3/pkg/muxer/tcp" "github.com/traefik/traefik/v3/pkg/safe" traefiktls "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" ) // Provider is the Tailscale certificates provider implementation. It receives @@ -254,8 +255,8 @@ func (p *Provider) fetchCerts(ctx context.Context, domains []string) { p.certByDomainMu.Lock() p.certByDomain[domain] = traefiktls.Certificate{ - CertFile: traefiktls.FileOrContent(cert), - KeyFile: traefiktls.FileOrContent(key), + CertFile: types.FileOrContent(cert), + KeyFile: types.FileOrContent(key), } p.certByDomainMu.Unlock() } diff --git a/pkg/redactor/redactor.go b/pkg/redactor/redactor.go index fc8a9dd14..6761d9eb9 100644 --- a/pkg/redactor/redactor.go +++ b/pkg/redactor/redactor.go @@ -4,11 +4,10 @@ import ( "encoding/json" "fmt" "reflect" - "regexp" "github.com/mitchellh/copystructure" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" "mvdan.cc/xurls/v2" ) @@ -67,8 +66,7 @@ func do(baseConfig interface{}, tag string, redactByDefault, indent bool) (strin } func doOnJSON(input string) string { - mailExp := regexp.MustCompile(`\w[-.\w]*\w@\w[-.\w]*\w\.\w{2,3}"`) - return xurls.Relaxed().ReplaceAllString(mailExp.ReplaceAllString(input, maskLarge+"\""), maskLarge) + return xurls.Relaxed().ReplaceAllString(input, maskLarge) } func doOnStruct(field reflect.Value, tag string, redactByDefault bool) error { @@ -166,8 +164,8 @@ func reset(field reflect.Value, name string) error { } case reflect.String: if field.String() != "" { - if field.Type().AssignableTo(reflect.TypeOf(tls.FileOrContent(""))) { - field.Set(reflect.ValueOf(tls.FileOrContent(maskShort))) + if field.Type().AssignableTo(reflect.TypeOf(types.FileOrContent(""))) { + field.Set(reflect.ValueOf(types.FileOrContent(maskShort))) } else { field.Set(reflect.ValueOf(maskShort)) } diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go index 23ce894d8..c3ef9ef9d 100644 --- a/pkg/redactor/redactor_config_test.go +++ b/pkg/redactor/redactor_config_test.go @@ -30,12 +30,7 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kv/zk" "github.com/traefik/traefik/v3/pkg/provider/rest" traefiktls "github.com/traefik/traefik/v3/pkg/tls" - "github.com/traefik/traefik/v3/pkg/tracing/datadog" - "github.com/traefik/traefik/v3/pkg/tracing/elastic" - "github.com/traefik/traefik/v3/pkg/tracing/haystack" - "github.com/traefik/traefik/v3/pkg/tracing/instana" - "github.com/traefik/traefik/v3/pkg/tracing/jaeger" - "github.com/traefik/traefik/v3/pkg/tracing/zipkin" + "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" "github.com/traefik/traefik/v3/pkg/types" ) @@ -135,7 +130,7 @@ func init() { "foo": { ServerName: "foo", InsecureSkipVerify: true, - RootCAs: []traefiktls.FileOrContent{"rootca.pem"}, + RootCAs: []types.FileOrContent{"rootca.pem"}, Certificates: []traefiktls.Certificate{ { CertFile: "cert.pem", @@ -395,7 +390,7 @@ func init() { TLS: &dynamic.TLSClientConfig{ ServerName: "foo", InsecureSkipVerify: true, - RootCAs: []traefiktls.FileOrContent{"rootca.pem"}, + RootCAs: []types.FileOrContent{"rootca.pem"}, Certificates: []traefiktls.Certificate{ { CertFile: "cert.pem", @@ -446,7 +441,7 @@ func init() { CipherSuites: []string{"foo"}, CurvePreferences: []string{"foo"}, ClientAuth: traefiktls.ClientAuth{ - CAFiles: []traefiktls.FileOrContent{"ca.pem"}, + CAFiles: []types.FileOrContent{"ca.pem"}, ClientAuthType: "RequireAndVerifyClientCert", }, SniStrict: true, @@ -517,7 +512,7 @@ func TestDo_staticConfiguration(t *testing.T) { } config.EntryPoints = static.EntryPoints{ - "foobar": { + "foobar": &static.EntryPoint{ Address: "foo Address", Transport: &static.EntryPointsTransport{ LifeCycle: &static.LifeCycle{ @@ -565,7 +560,7 @@ func TestDo_staticConfiguration(t *testing.T) { config.ServersTransport = &static.ServersTransport{ InsecureSkipVerify: true, - RootCAs: []traefiktls.FileOrContent{"RootCAs 1", "RootCAs 2", "RootCAs 3"}, + RootCAs: []types.FileOrContent{"RootCAs 1", "RootCAs 2", "RootCAs 3"}, MaxIdleConnsPerHost: 111, ForwardingTimeouts: &static.ForwardingTimeouts{ DialTimeout: ptypes.Duration(111 * time.Second), @@ -579,7 +574,7 @@ func TestDo_staticConfiguration(t *testing.T) { DialKeepAlive: ptypes.Duration(111 * time.Second), TLS: &static.TLSClientConfig{ InsecureSkipVerify: true, - RootCAs: []traefiktls.FileOrContent{"RootCAs 1", "RootCAs 2", "RootCAs 3"}, + RootCAs: []types.FileOrContent{"RootCAs 1", "RootCAs 2", "RootCAs 3"}, }, } @@ -850,58 +845,19 @@ func TestDo_staticConfiguration(t *testing.T) { } config.Tracing = &static.Tracing{ - ServiceName: "myServiceName", - SpanNameLimit: 42, - Jaeger: &jaeger.Config{ - SamplingServerURL: "foobar", - SamplingType: "foobar", - SamplingParam: 42, - LocalAgentHostPort: "foobar", - Gen128Bit: true, - Propagation: "foobar", - TraceContextHeaderName: "foobar", - Collector: &jaeger.Collector{ + ServiceName: "myServiceName", + Headers: map[string]string{ + "foobar": "foobar", + }, + GlobalAttributes: map[string]string{ + "foobar": "foobar", + }, + SampleRate: 42, + OTLP: &opentelemetry.Config{ + HTTP: &opentelemetry.HTTP{ Endpoint: "foobar", - User: "foobar", - Password: "foobar", + TLS: nil, }, - DisableAttemptReconnecting: true, - }, - Zipkin: &zipkin.Config{ - HTTPEndpoint: "foobar", - SameSpan: true, - ID128Bit: true, - SampleRate: 42, - }, - Datadog: &datadog.Config{ - LocalAgentHostPort: "foobar", - LocalAgentSocket: "foobar", - GlobalTags: map[string]string{"foobar": "foobar"}, - Debug: true, - PrioritySampling: true, - TraceIDHeaderName: "foobar", - ParentIDHeaderName: "foobar", - SamplingPriorityHeaderName: "foobar", - BagagePrefixHeaderName: "foobar", - }, - Instana: &instana.Config{ - LocalAgentHost: "foobar", - LocalAgentPort: 4242, - LogLevel: "foobar", - }, - Haystack: &haystack.Config{ - LocalAgentHost: "foobar", - LocalAgentPort: 42, - GlobalTag: "foobar", - TraceIDHeaderName: "foobar", - ParentIDHeaderName: "foobar", - SpanIDHeaderName: "foobar", - BaggagePrefixHeaderName: "foobar", - }, - Elastic: &elastic.Config{ - ServerURL: "foobar", - SecretToken: "foobar", - ServiceEnvironment: "foobar", }, } diff --git a/pkg/redactor/redactor_doOnJSON_test.go b/pkg/redactor/redactor_doOnJSON_test.go index e61e1975f..ddae9c579 100644 --- a/pkg/redactor/redactor_doOnJSON_test.go +++ b/pkg/redactor/redactor_doOnJSON_test.go @@ -45,13 +45,15 @@ func Test_doOnJSON_simple(t *testing.T) { "URL": "foo domain.com foo", "URL": "foo sub.domain.com foo", "URL": "foo sub.sub.domain.com foo", - "URL": "foo sub.sub.sub.domain.com.us foo" + "URL": "foo sub.sub.sub.domain.com.us foo", + "URL":"https://hub.example.com","foo":"bar" }`, expectedOutput: `{ "URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo", "URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo", "URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo", - "URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo" + "URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo", + "URL":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","foo":"bar" }`, }, } diff --git a/pkg/redactor/testdata/anonymized-static-config.json b/pkg/redactor/testdata/anonymized-static-config.json index a8db72460..97e678c85 100644 --- a/pkg/redactor/testdata/anonymized-static-config.json +++ b/pkg/redactor/testdata/anonymized-static-config.json @@ -344,57 +344,17 @@ }, "tracing": { "serviceName": "myServiceName", - "spanNameLimit": 42, - "jaeger": { - "samplingServerURL": "xxxx", - "samplingType": "foobar", - "samplingParam": 42, - "localAgentHostPort": "xxxx", - "gen128Bit": true, - "propagation": "foobar", - "traceContextHeaderName": "foobar", - "collector": { - "endpoint": "xxxx", - "user": "xxxx", - "password": "xxxx" - }, - "disableAttemptReconnecting": true + "headers": { + "foobar": "foobar" }, - "zipkin": { - "httpEndpoint": "xxxx", - "sameSpan": true, - "id128Bit": true, - "sampleRate": 42 + "globalAttributes": { + "foobar": "foobar" }, - "datadog": { - "localAgentHostPort": "xxxx", - "localAgentSocket": "xxxx", - "globalTags": { - "foobar": "foobar" - }, - "debug": true, - "prioritySampling": true, - "traceIDHeaderName": "foobar", - "parentIDHeaderName": "foobar", - "samplingPriorityHeaderName": "foobar", - "bagagePrefixHeaderName": "foobar" - }, - "instana": { - "localAgentHost": "xxxx", - "logLevel": "foobar" - }, - "haystack": { - "localAgentHost": "xxxx", - "globalTag": "foobar", - "traceIDHeaderName": "foobar", - "parentIDHeaderName": "foobar", - "spanIDHeaderName": "foobar", - "baggagePrefixHeaderName": "foobar" - }, - "elastic": { - "serverURL": "xxxx", - "secretToken": "xxxx", - "serviceEnvironment": "foobar" + "sampleRate": 42, + "otlp": { + "http": { + "endpoint": "xxxx" + } } }, "hostResolver": { @@ -447,4 +407,4 @@ } } } -} \ No newline at end of file +} diff --git a/pkg/server/configurationwatcher.go b/pkg/server/configurationwatcher.go index da2d66f17..5071f2498 100644 --- a/pkg/server/configurationwatcher.go +++ b/pkg/server/configurationwatcher.go @@ -12,6 +12,7 @@ import ( "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" ) // ConfigurationWatcher watches configuration changes. @@ -188,7 +189,7 @@ func logConfiguration(logger zerolog.Logger, configMsg dynamic.Message) { if copyConf.TLS.Options != nil { cleanedOptions := make(map[string]tls.Options, len(copyConf.TLS.Options)) for name, option := range copyConf.TLS.Options { - option.ClientAuth.CAFiles = []tls.FileOrContent{} + option.ClientAuth.CAFiles = []types.FileOrContent{} cleanedOptions[name] = option } @@ -205,7 +206,7 @@ func logConfiguration(logger zerolog.Logger, configMsg dynamic.Message) { if copyConf.HTTP != nil { for _, transport := range copyConf.HTTP.ServersTransports { transport.Certificates = tls.Certificates{} - transport.RootCAs = []tls.FileOrContent{} + transport.RootCAs = []types.FileOrContent{} } } @@ -213,7 +214,7 @@ func logConfiguration(logger zerolog.Logger, configMsg dynamic.Message) { for _, transport := range copyConf.TCP.ServersTransports { if transport.TLS != nil { transport.TLS.Certificates = tls.Certificates{} - transport.TLS.RootCAs = []tls.FileOrContent{} + transport.TLS.RootCAs = []types.FileOrContent{} } } } diff --git a/pkg/server/configurationwatcher_test.go b/pkg/server/configurationwatcher_test.go index d22cb9538..9cd586614 100644 --- a/pkg/server/configurationwatcher_test.go +++ b/pkg/server/configurationwatcher_test.go @@ -267,7 +267,7 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { providerAggregator := aggregator.ProviderAggregator{} err := providerAggregator.AddProvider(pvd) - assert.Nil(t, err) + assert.NoError(t, err) watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{}, "") @@ -342,7 +342,7 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { // give some time so that the configuration can be processed time.Sleep(100 * time.Millisecond) - assert.Equal(t, configurationReloads, 1, "Same configuration should not be published multiple times") + assert.Equal(t, 1, configurationReloads, "Same configuration should not be published multiple times") } func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { @@ -450,7 +450,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { providerAggregator := aggregator.ProviderAggregator{} err := providerAggregator.AddProvider(pvd) - assert.Nil(t, err) + assert.NoError(t, err) watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{"defaultEP"}, "") @@ -593,7 +593,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { providerAggregator := aggregator.ProviderAggregator{} err := providerAggregator.AddProvider(pvd) - assert.Nil(t, err) + assert.NoError(t, err) watcher := NewConfigurationWatcher(routinesPool, providerAggregator, []string{"defaultEP"}, "") diff --git a/pkg/server/keep_alive_middleware.go b/pkg/server/keep_alive_middleware.go new file mode 100644 index 000000000..cccdb6d70 --- /dev/null +++ b/pkg/server/keep_alive_middleware.go @@ -0,0 +1,29 @@ +package server + +import ( + "net/http" + "time" + + "github.com/rs/zerolog/log" + ptypes "github.com/traefik/paerser/types" +) + +func newKeepAliveMiddleware(next http.Handler, maxRequests int, maxTime ptypes.Duration) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + state, ok := req.Context().Value(connStateKey).(*connState) + if ok { + state.HTTPRequestCount++ + if maxRequests > 0 && state.HTTPRequestCount >= maxRequests { + log.Debug().Msg("Close because of too many requests") + state.KeepAliveState = "Close because of too many requests" + rw.Header().Set("Connection", "close") + } + if maxTime > 0 && time.Now().After(state.Start.Add(time.Duration(maxTime))) { + log.Debug().Msg("Close because of too long connection") + state.KeepAliveState = "Close because of too long connection" + rw.Header().Set("Connection", "close") + } + } + next.ServeHTTP(rw, req) + }) +} diff --git a/pkg/server/middleware/chainbuilder.go b/pkg/server/middleware/chainbuilder.go index 864e7e68b..980d7a2b5 100644 --- a/pkg/server/middleware/chainbuilder.go +++ b/pkg/server/middleware/chainbuilder.go @@ -8,20 +8,20 @@ import ( "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/capture" - metricsmiddleware "github.com/traefik/traefik/v3/pkg/middlewares/metrics" - mTracing "github.com/traefik/traefik/v3/pkg/middlewares/tracing" - "github.com/traefik/traefik/v3/pkg/tracing" + metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" + tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" + "go.opentelemetry.io/otel/trace" ) // ChainBuilder Creates a middleware chain by entry point. It is used for middlewares that are created almost systematically and that need to be created before all others. type ChainBuilder struct { metricsRegistry metrics.Registry accessLoggerMiddleware *accesslog.Handler - tracer *tracing.Tracing + tracer trace.Tracer } // NewChainBuilder Creates a new ChainBuilder. -func NewChainBuilder(metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer *tracing.Tracing) *ChainBuilder { +func NewChainBuilder(metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer) *ChainBuilder { return &ChainBuilder{ metricsRegistry: metricsRegistry, accessLoggerMiddleware: accessLoggerMiddleware, @@ -42,11 +42,12 @@ func (c *ChainBuilder) Build(ctx context.Context, entryPointName string) alice.C } if c.tracer != nil { - chain = chain.Append(mTracing.WrapEntryPointHandler(ctx, c.tracer, entryPointName)) + chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName)) } if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() { - chain = chain.Append(metricsmiddleware.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName)) + metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName) + chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)) } return chain @@ -59,8 +60,4 @@ func (c *ChainBuilder) Close() { log.Error().Err(err).Msg("Could not close the access log file") } } - - if c.tracer != nil { - c.tracer.Close() - } } diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index afeab88e4..72da39663 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/containous/alice" + "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/middlewares/addprefix" "github.com/traefik/traefik/v3/pkg/middlewares/auth" @@ -22,6 +23,7 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares/headers" "github.com/traefik/traefik/v3/pkg/middlewares/inflightreq" "github.com/traefik/traefik/v3/pkg/middlewares/ipallowlist" + "github.com/traefik/traefik/v3/pkg/middlewares/ipwhitelist" "github.com/traefik/traefik/v3/pkg/middlewares/passtlsclientcert" "github.com/traefik/traefik/v3/pkg/middlewares/ratelimiter" "github.com/traefik/traefik/v3/pkg/middlewares/redirect" @@ -236,6 +238,18 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( } } + // IPWhiteList + if config.IPWhiteList != nil { + log.Warn().Msg("IPWhiteList is deprecated, please use IPAllowList instead.") + + if middleware != nil { + return nil, badConf + } + middleware = func(next http.Handler) (http.Handler, error) { + return ipwhitelist.New(ctx, next, *config.IPWhiteList, middlewareName) + } + } + // IPAllowList if config.IPAllowList != nil { if middleware != nil { @@ -372,7 +386,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName) } - return tracing.Wrap(ctx, middleware), nil + return tracing.WrapMiddleware(ctx, middleware), nil } func inSlice(element string, stack []string) bool { diff --git a/pkg/server/middleware/plugins.go b/pkg/server/middleware/plugins.go index 80253b05b..eacf8fba1 100644 --- a/pkg/server/middleware/plugins.go +++ b/pkg/server/middleware/plugins.go @@ -5,12 +5,13 @@ import ( "errors" "net/http" - "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/plugins" - "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" ) +const typeName = "Plugin" + // PluginsBuilder the plugin's builder interface. type PluginsBuilder interface { Build(pName string, config map[string]interface{}, middlewareName string) (plugins.Constructor, error) @@ -54,6 +55,6 @@ func (s *traceablePlugin) ServeHTTP(rw http.ResponseWriter, req *http.Request) { s.h.ServeHTTP(rw, req) } -func (s *traceablePlugin) GetTracingInformation() (string, ext.SpanKindEnum) { - return s.name, tracing.SpanKindNoneEnum +func (s *traceablePlugin) GetTracingInformation() (string, string, trace.SpanKind) { + return s.name, typeName, trace.SpanKindInternal } diff --git a/pkg/server/middleware/tcp/middlewares.go b/pkg/server/middleware/tcp/middlewares.go index e03a4fb84..0a5d30bc8 100644 --- a/pkg/server/middleware/tcp/middlewares.go +++ b/pkg/server/middleware/tcp/middlewares.go @@ -5,9 +5,11 @@ import ( "fmt" "strings" + "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/middlewares/tcp/inflightconn" "github.com/traefik/traefik/v3/pkg/middlewares/tcp/ipallowlist" + "github.com/traefik/traefik/v3/pkg/middlewares/tcp/ipwhitelist" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/tcp" ) @@ -94,6 +96,15 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( } } + // IPWhiteList + if config.IPWhiteList != nil { + log.Warn().Msg("IPWhiteList is deprecated, please use IPAllowList instead.") + + middleware = func(next tcp.Handler) (tcp.Handler, error) { + return ipwhitelist.New(ctx, next, *config.IPWhiteList, middlewareName) + } + } + // IPAllowList if config.IPAllowList != nil { middleware = func(next tcp.Handler) (tcp.Handler, error) { diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 97f7f98b6..4b383e076 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -12,6 +12,7 @@ import ( "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" + "github.com/traefik/traefik/v3/pkg/middlewares/denyrouterrecursion" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/recovery" "github.com/traefik/traefik/v3/pkg/middlewares/tracing" @@ -79,7 +80,7 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t } handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) { - return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.AddOriginFields), nil + return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil }).Then(handler) if err != nil { logger.Error().Err(err).Send() @@ -197,17 +198,20 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares) - tHandler := func(next http.Handler) (http.Handler, error) { - return tracing.NewForwarder(ctx, routerName, router.Service, next), nil - } - chain := alice.New() + chain = chain.Append(tracing.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service))) + if m.metricsRegistry != nil && m.metricsRegistry.IsRouterEnabled() { - chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.metricsRegistry, routerName, provider.GetQualifiedName(ctx, router.Service))) + metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.metricsRegistry, routerName, provider.GetQualifiedName(ctx, router.Service)) + chain = chain.Append(tracing.WrapMiddleware(ctx, metricsHandler)) } - return chain.Extend(*mHandler).Append(tHandler).Then(sHandler) + if router.DefaultRule { + chain = chain.Append(denyrouterrecursion.WrapHandler(routerName)) + } + + return chain.Extend(*mHandler).Then(sHandler) } // BuildDefaultHTTPRouter creates a default HTTP router. diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 8c2e8939b..711a60313 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -266,9 +266,8 @@ func (r *Router) SetHTTPSForwarder(handler tcp.Handler) { } } - // muxerHTTPS only contains single HostSNI rules (and no other kind of rules), - // so there's no need for specifying a priority for them. - if err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler); err != nil { + rule := "HostSNI(`" + sniHost + "`)" + if err := r.muxerHTTPS.AddRoute(rule, tcpmuxer.GetRulePriority(rule), tcpHandler); err != nil { log.Error().Err(err).Msg("Error while adding route for host") } } diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index 280d6cf2b..29bb2c418 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -494,6 +494,21 @@ func Test_Routing(t *testing.T) { }, }, }, + { + desc: "HTTPS router && HTTPS CatchAll router", + routers: []applyRouter{routerHTTPS, routerHTTPSPathPrefix}, + checks: []checkCase{ + { + desc: "HTTPS TLS 1.0 request should fail", + checkRouter: checkHTTPSTLS10, + expectedError: "wrong TLS version", + }, + { + desc: "HTTPS TLS 1.2 request should be handled by HTTPS service", + checkRouter: checkHTTPSTLS12, + }, + }, + }, { desc: "All routers, all checks", routers: []applyRouter{routerTCPCatchAll, routerHTTP, routerHTTPS, routerTCPTLS, routerTCPTLSCatchAll}, @@ -620,12 +635,12 @@ func Test_Routing(t *testing.T) { err := check.checkRouter(epListener.Addr().String(), timeout) if check.expectedError != "" { - require.NotNil(t, err, check.desc) + require.Error(t, err, check.desc) assert.Contains(t, err.Error(), check.expectedError, check.desc) continue } - assert.Nil(t, err, check.desc) + assert.NoError(t, err, check.desc) } epListener.Close() diff --git a/pkg/server/server.go b/pkg/server/server.go index b14ac5cbd..0f4379f04 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -3,6 +3,7 @@ package server import ( "context" "errors" + "io" "os" "os/signal" "time" @@ -27,12 +28,12 @@ type Server struct { stopChan chan bool routinesPool *safe.Pool + + tracerCloser io.Closer } // NewServer returns an initialized Server. -func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, - chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler, -) *Server { +func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler, tracerCloser io.Closer) *Server { srv := &Server{ watcher: watcher, tcpEntryPoints: entryPoints, @@ -42,6 +43,7 @@ func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsU stopChan: make(chan bool, 1), routinesPool: routinesPool, udpEntryPoints: entryPointsUDP, + tracerCloser: tracerCloser, } srv.configureSignals() @@ -73,7 +75,6 @@ func (s *Server) Wait() { // Stop stops the server. func (s *Server) Stop() { - //nolint:zerologlint // false-positive https://github.com/ykadowak/zerologlint/issues/3 defer log.Info().Msg("Server stopped") s.tcpEntryPoints.Stop() @@ -106,6 +107,12 @@ func (s *Server) Close() { s.chainBuilder.Close() + if s.tracerCloser != nil { + if err := s.tracerCloser.Close(); err != nil { + log.Error().Err(err).Msg("Could not close the tracer") + } + } + cancel() } diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 58eb78974..e4d2892a8 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -3,6 +3,7 @@ package server import ( "context" "errors" + "expvar" "fmt" stdlog "log" "net" @@ -36,6 +37,25 @@ import ( "golang.org/x/net/http2/h2c" ) +type key string + +const ( + connStateKey key = "connState" + debugConnectionEnv string = "DEBUG_CONNECTION" +) + +var ( + clientConnectionStates = map[string]*connState{} + clientConnectionStatesMu = sync.RWMutex{} +) + +type connState struct { + State string + KeepAliveState string + Start time.Time + HTTPRequestCount int +} + type httpForwarder struct { net.Listener connChan chan net.Conn @@ -70,6 +90,11 @@ type TCPEntryPoints map[string]*TCPEntryPoint // NewTCPEntryPoints creates a new TCPEntryPoints. func NewTCPEntryPoints(entryPointsConfig static.EntryPoints, hostResolverConfig *types.HostResolverConfig, metricsRegistry metrics.Registry) (TCPEntryPoints, error) { + if os.Getenv(debugConnectionEnv) != "" { + expvar.Publish("clientConnectionStates", expvar.Func(func() any { + return clientConnectionStates + })) + } serverEntryPointsTCP := make(TCPEntryPoints) for entryPointName, config := range entryPointsConfig { protocol, err := config.GetProtocol() @@ -399,7 +424,12 @@ func (ln tcpKeepAliveListener) Accept() (net.Conn, error) { } func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoint, listener net.Listener) (net.Listener, error) { - proxyListener := &proxyproto.Listener{Listener: listener} + timeout := entryPoint.Transport.RespondingTimeouts.ReadTimeout + // proxyproto use 200ms if ReadHeaderTimeout is set to 0 and not no timeout + if timeout == 0 { + timeout = -1 + } + proxyListener := &proxyproto.Listener{Listener: listener, ReadHeaderTimeout: time.Duration(timeout)} if entryPoint.ProxyProtocol.Insecure { log.Ctx(ctx).Info().Msg("Enabling ProxyProtocol without trusted IPs: Insecure") @@ -517,7 +547,7 @@ func (c *connectionTracker) Close() { } type stoppable interface { - Shutdown(context.Context) error + Shutdown(ctx context.Context) error Close() error } @@ -553,6 +583,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati return nil, err } + handler = denyFragment(handler) if configuration.HTTP.EncodeQuerySemicolons { handler = encodeQuerySemicolons(handler) } else { @@ -567,6 +598,11 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati }) } + debugConnection := os.Getenv(debugConnectionEnv) != "" + if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { + handler = newKeepAliveMiddleware(handler, configuration.Transport.KeepAliveMaxRequests, configuration.Transport.KeepAliveMaxTime) + } + serverHTTP := &http.Server{ Handler: handler, ErrorLog: stdlog.New(logs.NoLevel(log.Logger, zerolog.DebugLevel), "", 0), @@ -574,6 +610,27 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout), IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), } + if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { + serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context { + cState := &connState{Start: time.Now()} + if debugConnection { + clientConnectionStatesMu.Lock() + clientConnectionStates[getConnKey(c)] = cState + clientConnectionStatesMu.Unlock() + } + return context.WithValue(ctx, connStateKey, cState) + } + + if debugConnection { + serverHTTP.ConnState = func(c net.Conn, state http.ConnState) { + clientConnectionStatesMu.Lock() + if clientConnectionStates[getConnKey(c)] != nil { + clientConnectionStates[getConnKey(c)].State = state.String() + } + clientConnectionStatesMu.Unlock() + } + } + } // ConfigureServer configures HTTP/2 with the MaxConcurrentStreams option for the given server. // Also keeping behavior the same as @@ -603,6 +660,10 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati }, nil } +func getConnKey(conn net.Conn) string { + return fmt.Sprintf("%s => %s", conn.RemoteAddr(), conn.LocalAddr()) +} + func newTrackedConnection(conn tcp.WriteCloser, tracker *connectionTracker) *trackedConnection { tracker.AddConnection(conn) return &trackedConnection{ @@ -640,3 +701,20 @@ func encodeQuerySemicolons(h http.Handler) http.Handler { } }) } + +// When go receives an HTTP request, it assumes the absence of fragment URL. +// However, it is still possible to send a fragment in the request. +// In this case, Traefik will encode the '#' character, altering the request's intended meaning. +// To avoid this behavior, the following function rejects requests that include a fragment in the URL. +func denyFragment(h http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if strings.Contains(req.URL.RawPath, "#") { + log.Debug().Msgf("Rejecting request because it contains a fragment in the URL path: %s", req.URL.RawPath) + rw.WriteHeader(http.StatusBadRequest) + + return + } + + h.ServeHTTP(rw, req) + }) +} diff --git a/pkg/server/server_entrypoint_tcp_http3_test.go b/pkg/server/server_entrypoint_tcp_http3_test.go index 9b440be4b..66aa48ce4 100644 --- a/pkg/server/server_entrypoint_tcp_http3_test.go +++ b/pkg/server/server_entrypoint_tcp_http3_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/static" tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp" - traefiktls "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" ) // LocalhostCert is a PEM-encoded TLS cert with SAN IPs @@ -20,7 +20,7 @@ import ( // generated from src/crypto/tls: // go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h var ( - localhostCert = traefiktls.FileOrContent(`-----BEGIN CERTIFICATE----- + localhostCert = types.FileOrContent(`-----BEGIN CERTIFICATE----- MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A @@ -42,7 +42,7 @@ WkBKOclmOV2xlTVuPw== -----END CERTIFICATE-----`) // LocalhostKey is the private key for localhostCert. - localhostKey = traefiktls.FileOrContent(`-----BEGIN RSA PRIVATE KEY----- + localhostKey = types.FileOrContent(`-----BEGIN RSA PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoZtrm0dXV0Aqi 4Bpc7f95sNRTiu/AJSD8I1onY9PnEsPg3VVxvytsVJbYdcqr4w99V3AgpH/UNzMS gAZ/8lZBNbsSDOVesJ3euVqMRfYPvd9pYl6QPRRpSDPm+2tNdn3QFAvta9EgJ3sW diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index 3567ca47f..d050803f3 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -230,3 +230,91 @@ func TestReadTimeoutWithFirstByte(t *testing.T) { t.Error("Timeout while read") } } + +func TestKeepAliveMaxRequests(t *testing.T) { + epConfig := &static.EntryPointsTransport{} + epConfig.SetDefaults() + epConfig.KeepAliveMaxRequests = 3 + + entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + Address: ":0", + Transport: epConfig, + ForwardedHeaders: &static.ForwardedHeaders{}, + HTTP2: &static.HTTP2Config{}, + }, nil, nil) + require.NoError(t, err) + + router := &tcprouter.Router{} + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + + conn, err := startEntrypoint(entryPoint, router) + require.NoError(t, err) + + http.DefaultClient.Transport = &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return conn, nil + }, + } + + resp, err := http.Get("http://" + entryPoint.listener.Addr().String()) + require.NoError(t, err) + require.False(t, resp.Close) + err = resp.Body.Close() + require.NoError(t, err) + + resp, err = http.Get("http://" + entryPoint.listener.Addr().String()) + require.NoError(t, err) + require.False(t, resp.Close) + err = resp.Body.Close() + require.NoError(t, err) + + resp, err = http.Get("http://" + entryPoint.listener.Addr().String()) + require.NoError(t, err) + require.True(t, resp.Close) + err = resp.Body.Close() + require.NoError(t, err) +} + +func TestKeepAliveMaxTime(t *testing.T) { + epConfig := &static.EntryPointsTransport{} + epConfig.SetDefaults() + epConfig.KeepAliveMaxTime = ptypes.Duration(time.Millisecond) + + entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + Address: ":0", + Transport: epConfig, + ForwardedHeaders: &static.ForwardedHeaders{}, + HTTP2: &static.HTTP2Config{}, + }, nil, nil) + require.NoError(t, err) + + router := &tcprouter.Router{} + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + + conn, err := startEntrypoint(entryPoint, router) + require.NoError(t, err) + + http.DefaultClient.Transport = &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return conn, nil + }, + } + + resp, err := http.Get("http://" + entryPoint.listener.Addr().String()) + require.NoError(t, err) + require.False(t, resp.Close) + err = resp.Body.Close() + require.NoError(t, err) + + time.Sleep(time.Millisecond) + + resp, err = http.Get("http://" + entryPoint.listener.Addr().String()) + require.NoError(t, err) + require.True(t, resp.Close) + err = resp.Body.Close() + require.NoError(t, err) +} diff --git a/pkg/server/service/loadbalancer/mirror/mirror_test.go b/pkg/server/service/loadbalancer/mirror/mirror_test.go index 57d815f0f..c43d00b95 100644 --- a/pkg/server/service/loadbalancer/mirror/mirror_test.go +++ b/pkg/server/service/loadbalancer/mirror/mirror_test.go @@ -98,7 +98,7 @@ func TestHijack(t *testing.T) { var mirrorRequest bool err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { hijacker, ok := rw.(http.Hijacker) - assert.Equal(t, true, ok) + assert.True(t, ok) _, _, err := hijacker.Hijack() assert.Error(t, err) @@ -109,7 +109,7 @@ func TestHijack(t *testing.T) { mirror.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil)) pool.Stop() - assert.Equal(t, true, mirrorRequest) + assert.True(t, mirrorRequest) } func TestFlush(t *testing.T) { @@ -122,7 +122,7 @@ func TestFlush(t *testing.T) { var mirrorRequest bool err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { hijacker, ok := rw.(http.Flusher) - assert.Equal(t, true, ok) + assert.True(t, ok) hijacker.Flush() @@ -133,7 +133,7 @@ func TestFlush(t *testing.T) { mirror.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil)) pool.Stop() - assert.Equal(t, true, mirrorRequest) + assert.True(t, mirrorRequest) } func TestMirroringWithBody(t *testing.T) { @@ -233,7 +233,7 @@ func TestCloneRequest(t *testing.T) { _, expectedBytes, err := newReusableRequest(req, 2) assert.Error(t, err) - assert.Equal(t, bb[:3], expectedBytes) + assert.Equal(t, expectedBytes, bb[:3]) }) t.Run("valid case with maxBodySize", func(t *testing.T) { @@ -258,7 +258,7 @@ func TestCloneRequest(t *testing.T) { rr, expectedBytes, err := newReusableRequest(req, 20) assert.NoError(t, err) assert.Nil(t, expectedBytes) - assert.Len(t, rr.body, 0) + assert.Empty(t, rr.body) }) t.Run("no request given", func(t *testing.T) { diff --git a/pkg/server/service/loadbalancer/wrr/wrr.go b/pkg/server/service/loadbalancer/wrr/wrr.go index 7442fa3a1..f38facf35 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr.go +++ b/pkg/server/service/loadbalancer/wrr/wrr.go @@ -4,6 +4,8 @@ import ( "container/heap" "context" "errors" + "fmt" + "hash/fnv" "net/http" "sync" @@ -22,6 +24,21 @@ type stickyCookie struct { name string secure bool httpOnly bool + sameSite string + maxAge int +} + +func convertSameSite(sameSite string) http.SameSite { + switch sameSite { + case "none": + return http.SameSiteNoneMode + case "lax": + return http.SameSiteLaxMode + case "strict": + return http.SameSiteStrictMode + default: + return http.SameSiteDefaultMode + } } // Balancer is a WeightedRoundRobin load balancer based on Earliest Deadline First (EDF). @@ -33,7 +50,9 @@ type Balancer struct { stickyCookie *stickyCookie wantsHealthCheck bool - mutex sync.RWMutex + handlersMu sync.RWMutex + // References all the handlers by name and also by the hashed value of the name. + handlerMap map[string]*namedHandler handlers []*namedHandler curDeadline float64 // status is a record of which child services of the Balancer are healthy, keyed @@ -50,6 +69,7 @@ type Balancer struct { func New(sticky *dynamic.Sticky, wantHealthCheck bool) *Balancer { balancer := &Balancer{ status: make(map[string]struct{}), + handlerMap: make(map[string]*namedHandler), wantsHealthCheck: wantHealthCheck, } if sticky != nil && sticky.Cookie != nil { @@ -57,8 +77,11 @@ func New(sticky *dynamic.Sticky, wantHealthCheck bool) *Balancer { name: sticky.Cookie.Name, secure: sticky.Cookie.Secure, httpOnly: sticky.Cookie.HTTPOnly, + sameSite: sticky.Cookie.SameSite, + maxAge: sticky.Cookie.MaxAge, } } + return balancer } @@ -96,8 +119,8 @@ func (b *Balancer) Pop() interface{} { // SetStatus sets on the balancer that its given child is now of the given // status. balancerName is only needed for logging purposes. func (b *Balancer) SetStatus(ctx context.Context, childName string, up bool) { - b.mutex.Lock() - defer b.mutex.Unlock() + b.handlersMu.Lock() + defer b.handlersMu.Unlock() upBefore := len(b.status) > 0 @@ -148,8 +171,8 @@ func (b *Balancer) RegisterStatusUpdater(fn func(up bool)) error { var errNoAvailableServer = errors.New("no available server") func (b *Balancer) nextServer() (*namedHandler, error) { - b.mutex.Lock() - defer b.mutex.Unlock() + b.handlersMu.Lock() + defer b.handlersMu.Unlock() if len(b.handlers) == 0 || len(b.status) == 0 { return nil, errNoAvailableServer @@ -183,22 +206,18 @@ func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) { } if err == nil && cookie != nil { - for _, handler := range b.handlers { - if handler.name != cookie.Value { - continue - } + b.handlersMu.RLock() + handler, ok := b.handlerMap[cookie.Value] + b.handlersMu.RUnlock() - b.mutex.RLock() - _, ok := b.status[handler.name] - b.mutex.RUnlock() - if !ok { - // because we already are in the only iteration that matches the cookie, so none - // of the following iterations are going to be a match for the cookie anyway. - break + if ok && handler != nil { + b.handlersMu.RLock() + _, isHealthy := b.status[handler.name] + b.handlersMu.RUnlock() + if isHealthy { + handler.ServeHTTP(w, req) + return } - - handler.ServeHTTP(w, req) - return } } } @@ -214,7 +233,15 @@ func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) { } if b.stickyCookie != nil { - cookie := &http.Cookie{Name: b.stickyCookie.name, Value: server.name, Path: "/", HttpOnly: b.stickyCookie.httpOnly, Secure: b.stickyCookie.secure} + cookie := &http.Cookie{ + Name: b.stickyCookie.name, + Value: hash(server.name), + Path: "/", + HttpOnly: b.stickyCookie.httpOnly, + Secure: b.stickyCookie.secure, + SameSite: convertSameSite(b.stickyCookie.sameSite), + MaxAge: b.stickyCookie.maxAge, + } http.SetCookie(w, cookie) } @@ -235,9 +262,19 @@ func (b *Balancer) Add(name string, handler http.Handler, weight *int) { h := &namedHandler{Handler: handler, name: name, weight: float64(w)} - b.mutex.Lock() + b.handlersMu.Lock() h.deadline = b.curDeadline + 1/h.weight heap.Push(b, h) b.status[name] = struct{}{} - b.mutex.Unlock() + b.handlerMap[name] = h + b.handlerMap[hash(name)] = h + b.handlersMu.Unlock() +} + +func hash(input string) string { + hasher := fnv.New64() + // We purposely ignore the error because the implementation always returns nil. + _, _ = hasher.Write([]byte(input)) + + return fmt.Sprintf("%x", hasher.Sum64()) } diff --git a/pkg/server/service/loadbalancer/wrr/wrr_test.go b/pkg/server/service/loadbalancer/wrr/wrr_test.go index e0ca68c04..3708fa618 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr_test.go +++ b/pkg/server/service/loadbalancer/wrr/wrr_test.go @@ -219,6 +219,53 @@ func TestBalancerAllServersZeroWeight(t *testing.T) { } func TestSticky(t *testing.T) { + balancer := New(&dynamic.Sticky{ + Cookie: &dynamic.Cookie{ + Name: "test", + Secure: true, + HTTPOnly: true, + SameSite: "none", + MaxAge: 42, + }, + }, false) + + balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "first") + rw.WriteHeader(http.StatusOK) + }), Int(1)) + + balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("server", "second") + rw.WriteHeader(http.StatusOK) + }), Int(2)) + + recorder := &responseRecorder{ + ResponseRecorder: httptest.NewRecorder(), + save: map[string]int{}, + cookies: make(map[string]*http.Cookie), + } + + req := httptest.NewRequest(http.MethodGet, "/", nil) + for i := 0; i < 3; i++ { + for _, cookie := range recorder.Result().Cookies() { + assert.NotContains(t, "test=first", cookie.Value) + assert.NotContains(t, "test=second", cookie.Value) + req.AddCookie(cookie) + } + recorder.ResponseRecorder = httptest.NewRecorder() + + balancer.ServeHTTP(recorder, req) + } + + assert.Equal(t, 0, recorder.save["first"]) + assert.Equal(t, 3, recorder.save["second"]) + assert.True(t, recorder.cookies["test"].HttpOnly) + assert.True(t, recorder.cookies["test"].Secure) + assert.Equal(t, http.SameSiteNoneMode, recorder.cookies["test"].SameSite) + assert.Equal(t, 42, recorder.cookies["test"].MaxAge) +} + +func TestSticky_FallBack(t *testing.T) { balancer := New(&dynamic.Sticky{ Cookie: &dynamic.Cookie{Name: "test"}, }, false) @@ -236,10 +283,8 @@ func TestSticky(t *testing.T) { recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} req := httptest.NewRequest(http.MethodGet, "/", nil) + req.AddCookie(&http.Cookie{Name: "test", Value: "second"}) for i := 0; i < 3; i++ { - for _, cookie := range recorder.Result().Cookies() { - req.AddCookie(cookie) - } recorder.ResponseRecorder = httptest.NewRecorder() balancer.ServeHTTP(recorder, req) @@ -282,11 +327,15 @@ type responseRecorder struct { save map[string]int sequence []string status []int + cookies map[string]*http.Cookie } func (r *responseRecorder) WriteHeader(statusCode int) { r.save[r.Header().Get("server")]++ r.sequence = append(r.sequence, r.Header().Get("server")) r.status = append(r.status, statusCode) + for _, cookie := range r.Result().Cookies() { + r.cookies[cookie.Name] = cookie + } r.ResponseRecorder.WriteHeader(statusCode) } diff --git a/pkg/server/service/proxy.go b/pkg/server/service/proxy.go index 413a97a6c..083875f9e 100644 --- a/pkg/server/service/proxy.go +++ b/pkg/server/service/proxy.go @@ -22,9 +22,13 @@ const StatusClientClosedRequest = 499 const StatusClientClosedRequestText = "Client Closed Request" func buildSingleHostProxy(target *url.URL, passHostHeader bool, flushInterval time.Duration, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) http.Handler { + // Wrapping the roundTripper with the Tracing roundTripper, + // to handle the reverseProxy client span creation. + tracingRoundTripper := newTracingRoundTripper(roundTripper) + return &httputil.ReverseProxy{ Director: directorBuilder(target, passHostHeader), - Transport: roundTripper, + Transport: tracingRoundTripper, FlushInterval: flushInterval, BufferPool: bufferPool, ErrorHandler: errorHandler, @@ -94,23 +98,7 @@ func isWebSocketUpgrade(req *http.Request) bool { } func errorHandler(w http.ResponseWriter, req *http.Request, err error) { - statusCode := http.StatusInternalServerError - - switch { - case errors.Is(err, io.EOF): - statusCode = http.StatusBadGateway - case errors.Is(err, context.Canceled): - statusCode = StatusClientClosedRequest - default: - var netErr net.Error - if errors.As(err, &netErr) { - if netErr.Timeout() { - statusCode = http.StatusGatewayTimeout - } else { - statusCode = http.StatusBadGateway - } - } - } + statusCode := computeStatusCode(err) logger := log.Ctx(req.Context()) logger.Debug().Err(err).Msgf("%d %s", statusCode, statusText(statusCode)) @@ -121,6 +109,26 @@ func errorHandler(w http.ResponseWriter, req *http.Request, err error) { } } +func computeStatusCode(err error) int { + switch { + case errors.Is(err, io.EOF): + return http.StatusBadGateway + case errors.Is(err, context.Canceled): + return StatusClientClosedRequest + default: + var netErr net.Error + if errors.As(err, &netErr) { + if netErr.Timeout() { + return http.StatusGatewayTimeout + } + + return http.StatusBadGateway + } + } + + return http.StatusInternalServerError +} + func statusText(statusCode int) string { if statusCode == StatusClientClosedRequest { return StatusClientClosedRequestText diff --git a/pkg/server/service/proxy_websocket_test.go b/pkg/server/service/proxy_websocket_test.go index d8726a795..ea66ef57e 100644 --- a/pkg/server/service/proxy_websocket_test.go +++ b/pkg/server/service/proxy_websocket_test.go @@ -51,7 +51,7 @@ func TestWebSocketTCPClose(t *testing.T) { serverErr := <-errChan var wsErr *gorillawebsocket.CloseError - require.True(t, errors.As(serverErr, &wsErr)) + require.ErrorAs(t, serverErr, &wsErr) assert.Equal(t, 1006, wsErr.Code) } diff --git a/pkg/server/service/roundtripper.go b/pkg/server/service/roundtripper.go index d7ab54c7a..7b7786a07 100644 --- a/pkg/server/service/roundtripper.go +++ b/pkg/server/service/roundtripper.go @@ -18,6 +18,7 @@ import ( "github.com/spiffe/go-spiffe/v2/svid/x509svid" "github.com/traefik/traefik/v3/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" "golang.org/x/net/http2" ) @@ -185,7 +186,7 @@ func (r *RoundTripperManager) createRoundTripper(cfg *dynamic.ServersTransport) return newSmartRoundTripper(transport, cfg.ForwardingTimeouts) } -func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool { +func createRootCACertPool(rootCAs []types.FileOrContent) *x509.CertPool { if len(rootCAs) == 0 { return nil } diff --git a/pkg/server/service/roundtripper_test.go b/pkg/server/service/roundtripper_test.go index c9af07a17..2d7763888 100644 --- a/pkg/server/service/roundtripper_test.go +++ b/pkg/server/service/roundtripper_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" ) func Int32(i int32) *int32 { @@ -144,7 +145,7 @@ func TestKeepConnectionWhenSameConfiguration(t *testing.T) { dynamicConf := map[string]*dynamic.ServersTransport{ "test": { ServerName: "example.com", - RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)}, + RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)}, }, } @@ -167,7 +168,7 @@ func TestKeepConnectionWhenSameConfiguration(t *testing.T) { dynamicConf = map[string]*dynamic.ServersTransport{ "test": { ServerName: "www.example.com", - RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)}, + RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)}, }, } @@ -213,13 +214,13 @@ func TestMTLS(t *testing.T) { "test": { ServerName: "example.com", // For TLS - RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)}, + RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)}, // For mTLS Certificates: traefiktls.Certificates{ traefiktls.Certificate{ - CertFile: traefiktls.FileOrContent(mTLSCert), - KeyFile: traefiktls.FileOrContent(mTLSKey), + CertFile: types.FileOrContent(mTLSCert), + KeyFile: types.FileOrContent(mTLSKey), }, }, }, diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 9883d79a2..55fcb9fa2 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -2,6 +2,7 @@ package service import ( "context" + "encoding/hex" "errors" "fmt" "hash/fnv" @@ -13,6 +14,7 @@ import ( "strings" "time" + "github.com/containous/alice" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" @@ -21,6 +23,7 @@ import ( "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" + tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/server/cookie" "github.com/traefik/traefik/v3/pkg/server/provider" @@ -287,7 +290,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName hasher := fnv.New64a() _, _ = hasher.Write([]byte(server.URL)) // this will never return an error. - proxyName := fmt.Sprintf("%x", hasher.Sum(nil)) + proxyName := hex.EncodeToString(hasher.Sum(nil)) target, err := url.Parse(server.URL) if err != nil { @@ -301,12 +304,21 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil) proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil) - proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, nil) + proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields) if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() { - proxy = metricsMiddle.NewServiceMiddleware(ctx, proxy, m.metricsRegistry, serviceName) + metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.metricsRegistry, serviceName) + + proxy, err = alice.New(). + Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)). + Then(proxy) + if err != nil { + return nil, fmt.Errorf("error wrapping metrics handler: %w", err) + } } + proxy = tracingMiddle.NewService(ctx, serviceName, proxy) + lb.Add(proxyName, proxy, nil) // servers are considered UP by default. diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 8b5ff946b..5c26770e6 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -393,7 +393,7 @@ func Test1xxResponses(t *testing.T) { } handler, err := sm.getLoadBalancerServiceHandler(context.Background(), "foobar", info) - assert.Nil(t, err) + assert.NoError(t, err) frontend := httptest.NewServer(handler) t.Cleanup(frontend.Close) @@ -439,7 +439,7 @@ func Test1xxResponses(t *testing.T) { req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, frontend.URL, nil) res, err := frontendClient.Do(req) - assert.Nil(t, err) + assert.NoError(t, err) defer res.Body.Close() diff --git a/pkg/server/service/tracing_roundtripper.go b/pkg/server/service/tracing_roundtripper.go new file mode 100644 index 000000000..bdb2ab4f3 --- /dev/null +++ b/pkg/server/service/tracing_roundtripper.go @@ -0,0 +1,42 @@ +package service + +import ( + "context" + "net/http" + + "github.com/traefik/traefik/v3/pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +type wrapper struct { + rt http.RoundTripper +} + +func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) { + var span trace.Span + if tracer := tracing.TracerFromContext(req.Context()); tracer != nil { + var tracingCtx context.Context + tracingCtx, span = tracer.Start(req.Context(), "ReverseProxy", trace.WithSpanKind(trace.SpanKindClient)) + defer span.End() + + req = req.WithContext(tracingCtx) + + tracing.LogClientRequest(span, req) + tracing.InjectContextIntoCarrier(req) + } + + response, err := t.rt.RoundTrip(req) + if err != nil { + statusCode := computeStatusCode(err) + tracing.LogResponseCode(span, statusCode, trace.SpanKindClient) + return response, err + } + + tracing.LogResponseCode(span, response.StatusCode, trace.SpanKindClient) + + return response, nil +} + +func newTracingRoundTripper(rt http.RoundTripper) http.RoundTripper { + return &wrapper{rt: rt} +} diff --git a/pkg/tcp/dialer.go b/pkg/tcp/dialer.go index 267d1b05b..ca08f7d2e 100644 --- a/pkg/tcp/dialer.go +++ b/pkg/tcp/dialer.go @@ -16,6 +16,7 @@ import ( "github.com/spiffe/go-spiffe/v2/svid/x509svid" "github.com/traefik/traefik/v3/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" "golang.org/x/net/proxy" ) @@ -156,7 +157,7 @@ func (d *DialerManager) createDialers(name string, cfg *dynamic.TCPServersTransp return nil } -func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool { +func createRootCACertPool(rootCAs []types.FileOrContent) *x509.CertPool { if len(rootCAs) == 0 { return nil } diff --git a/pkg/tcp/dialer_test.go b/pkg/tcp/dialer_test.go index 603f012b5..2dad7bf38 100644 --- a/pkg/tcp/dialer_test.go +++ b/pkg/tcp/dialer_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" ) // LocalhostCert is a PEM-encoded TLS cert @@ -196,7 +197,7 @@ func TestTLS(t *testing.T) { "test": { TLS: &dynamic.TLSClientConfig{ ServerName: "example.com", - RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)}, + RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)}, }, }, } @@ -246,7 +247,7 @@ func TestTLSWithInsecureSkipVerify(t *testing.T) { "test": { TLS: &dynamic.TLSClientConfig{ ServerName: "bad-domain.com", - RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)}, + RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)}, InsecureSkipVerify: true, }, }, @@ -308,13 +309,13 @@ func TestMTLS(t *testing.T) { TLS: &dynamic.TLSClientConfig{ ServerName: "example.com", // For TLS - RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)}, + RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)}, // For mTLS Certificates: traefiktls.Certificates{ traefiktls.Certificate{ - CertFile: traefiktls.FileOrContent(mTLSCert), - KeyFile: traefiktls.FileOrContent(mTLSKey), + CertFile: types.FileOrContent(mTLSCert), + KeyFile: types.FileOrContent(mTLSKey), }, }, }, diff --git a/pkg/tls/certificate.go b/pkg/tls/certificate.go index 7cddcd526..5649c5ba8 100644 --- a/pkg/tls/certificate.go +++ b/pkg/tls/certificate.go @@ -6,11 +6,11 @@ import ( "errors" "fmt" "net/url" - "os" "sort" "strings" "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/types" ) var ( @@ -48,8 +48,8 @@ var ( // Certificate holds a SSL cert/key pair // Certs and Key could be either a file path, or the file content itself. type Certificate struct { - CertFile FileOrContent `json:"certFile,omitempty" toml:"certFile,omitempty" yaml:"certFile,omitempty"` - KeyFile FileOrContent `json:"keyFile,omitempty" toml:"keyFile,omitempty" yaml:"keyFile,omitempty" loggable:"false"` + CertFile types.FileOrContent `json:"certFile,omitempty" toml:"certFile,omitempty" yaml:"certFile,omitempty"` + KeyFile types.FileOrContent `json:"keyFile,omitempty" toml:"keyFile,omitempty" yaml:"keyFile,omitempty" loggable:"false"` } // Certificates defines traefik certificates type @@ -73,33 +73,6 @@ func (c Certificates) GetCertificates() []tls.Certificate { return certs } -// FileOrContent hold a file path or content. -type FileOrContent string - -func (f FileOrContent) String() string { - return string(f) -} - -// IsPath returns true if the FileOrContent is a file path, otherwise returns false. -func (f FileOrContent) IsPath() bool { - _, err := os.Stat(f.String()) - return err == nil -} - -func (f FileOrContent) Read() ([]byte, error) { - var content []byte - if f.IsPath() { - var err error - content, err = os.ReadFile(f.String()) - if err != nil { - return nil, err - } - } else { - content = []byte(f) - } - return content, nil -} - // AppendCertificate appends a Certificate to a certificates map keyed by store name. func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certificate, storeName string) error { certContent, err := c.CertFile.Read() @@ -229,8 +202,8 @@ func (c *Certificates) Set(value string) error { return fmt.Errorf("bad certificates format: %s", value) } *c = append(*c, Certificate{ - CertFile: FileOrContent(files[0]), - KeyFile: FileOrContent(files[1]), + CertFile: types.FileOrContent(files[0]), + KeyFile: types.FileOrContent(files[1]), }) } return nil diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go index 93a9cab36..fa12cbc85 100644 --- a/pkg/tls/tls.go +++ b/pkg/tls/tls.go @@ -8,7 +8,7 @@ const certificateHeader = "-----BEGIN CERTIFICATE-----\n" // ClientAuth defines the parameters of the client authentication part of the TLS connection, if any. type ClientAuth struct { - CAFiles []FileOrContent `json:"caFiles,omitempty" toml:"caFiles,omitempty" yaml:"caFiles,omitempty"` + CAFiles []types.FileOrContent `json:"caFiles,omitempty" toml:"caFiles,omitempty" yaml:"caFiles,omitempty"` // ClientAuthType defines the client authentication type to apply. // The available values are: "NoClientCert", "RequestClientCert", "VerifyClientCertIfGiven" and "RequireAndVerifyClientCert". ClientAuthType string `json:"clientAuthType,omitempty" toml:"clientAuthType,omitempty" yaml:"clientAuthType,omitempty" export:"true"` diff --git a/pkg/tls/tlsmanager_test.go b/pkg/tls/tlsmanager_test.go index 08acf04a2..0ec367883 100644 --- a/pkg/tls/tlsmanager_test.go +++ b/pkg/tls/tlsmanager_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/types" ) // LocalhostCert is a PEM-encoded TLS cert with SAN IPs @@ -16,7 +17,7 @@ import ( // generated from src/crypto/tls: // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h var ( - localhostCert = FileOrContent(`-----BEGIN CERTIFICATE----- + localhostCert = types.FileOrContent(`-----BEGIN CERTIFICATE----- MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A @@ -38,7 +39,7 @@ WkBKOclmOV2xlTVuPw== -----END CERTIFICATE-----`) // LocalhostKey is the private key for localhostCert. - localhostKey = FileOrContent(`-----BEGIN RSA PRIVATE KEY----- + localhostKey = types.FileOrContent(`-----BEGIN RSA PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoZtrm0dXV0Aqi 4Bpc7f95sNRTiu/AJSD8I1onY9PnEsPg3VVxvytsVJbYdcqr4w99V3AgpH/UNzMS gAZ/8lZBNbsSDOVesJ3euVqMRfYPvd9pYl6QPRRpSDPm+2tNdn3QFAvta9EgJ3sW @@ -173,7 +174,7 @@ func TestManager_Get(t *testing.T) { } require.NoError(t, err) - assert.Equal(t, config.MinVersion, test.expectedMinVersion) + assert.Equal(t, test.expectedMinVersion, config.MinVersion) }) } } @@ -197,7 +198,7 @@ func TestClientAuth(t *testing.T) { }, "vccig": { ClientAuth: ClientAuth{ - CAFiles: []FileOrContent{localhostCert}, + CAFiles: []types.FileOrContent{localhostCert}, ClientAuthType: "VerifyClientCertIfGiven", }, }, @@ -209,13 +210,13 @@ func TestClientAuth(t *testing.T) { }, "ravccwca": { ClientAuth: ClientAuth{ - CAFiles: []FileOrContent{localhostCert}, + CAFiles: []types.FileOrContent{localhostCert}, ClientAuthType: "RequireAndVerifyClientCert", }, }, "ravccwbca": { ClientAuth: ClientAuth{ - CAFiles: []FileOrContent{"Bad content"}, + CAFiles: []types.FileOrContent{"Bad content"}, ClientAuthType: "RequireAndVerifyClientCert", }, }, @@ -317,10 +318,10 @@ func TestClientAuth(t *testing.T) { if test.expectedRawSubject != nil { subjects := config.ClientCAs.Subjects() assert.Len(t, subjects, 1) - assert.Equal(t, subjects[0], test.expectedRawSubject) + assert.Equal(t, test.expectedRawSubject, subjects[0]) } - assert.Equal(t, config.ClientAuth, test.expectedClientAuth) + assert.Equal(t, test.expectedClientAuth, config.ClientAuth) }) } } @@ -330,9 +331,9 @@ func TestManager_Get_DefaultValues(t *testing.T) { // Ensures we won't break things for Traefik users when updating Go config, _ := tlsManager.Get("default", "default") - assert.Equal(t, config.MinVersion, uint16(tls.VersionTLS12)) - assert.Equal(t, config.NextProtos, []string{"h2", "http/1.1", "acme-tls/1"}) - assert.Equal(t, config.CipherSuites, []uint16{ + assert.Equal(t, uint16(tls.VersionTLS12), config.MinVersion) + assert.Equal(t, []string{"h2", "http/1.1", "acme-tls/1"}, config.NextProtos) + assert.Equal(t, []uint16{ tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, @@ -350,5 +351,5 @@ func TestManager_Get_DefaultValues(t *testing.T) { tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - }) + }, config.CipherSuites) } diff --git a/pkg/tls/zz_generated.deepcopy.go b/pkg/tls/zz_generated.deepcopy.go index 6523c97b1..26461ab0d 100644 --- a/pkg/tls/zz_generated.deepcopy.go +++ b/pkg/tls/zz_generated.deepcopy.go @@ -4,7 +4,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -60,7 +60,7 @@ func (in *ClientAuth) DeepCopyInto(out *ClientAuth) { *out = *in if in.CAFiles != nil { in, out := &in.CAFiles, &out.CAFiles - *out = make([]FileOrContent, len(*in)) + *out = make([]types.FileOrContent, len(*in)) copy(*out, *in) } return diff --git a/pkg/tracing/datadog/datadog.go b/pkg/tracing/datadog/datadog.go deleted file mode 100644 index f1a334f22..000000000 --- a/pkg/tracing/datadog/datadog.go +++ /dev/null @@ -1,84 +0,0 @@ -package datadog - -import ( - "io" - "net" - "os" - - "github.com/opentracing/opentracing-go" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" - ddtracer "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentracer" - datadog "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" -) - -// Name sets the name of this tracer. -const Name = "datadog" - -// Config provides configuration settings for a datadog tracer. -type Config struct { - LocalAgentHostPort string `description:"Sets the Datadog Agent host:port." json:"localAgentHostPort,omitempty" toml:"localAgentHostPort,omitempty" yaml:"localAgentHostPort,omitempty"` - LocalAgentSocket string `description:"Sets the socket for the Datadog Agent." json:"localAgentSocket,omitempty" toml:"localAgentSocket,omitempty" yaml:"localAgentSocket,omitempty"` - GlobalTags map[string]string `description:"Sets a list of key:value tags on all spans." json:"globalTags,omitempty" toml:"globalTags,omitempty" yaml:"globalTags,omitempty" export:"true"` - Debug bool `description:"Enables Datadog debug." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"` - PrioritySampling bool `description:"Enables priority sampling. When using distributed tracing, this option must be enabled in order to get all the parts of a distributed trace sampled." json:"prioritySampling,omitempty" toml:"prioritySampling,omitempty" yaml:"prioritySampling,omitempty" export:"true"` - TraceIDHeaderName string `description:"Sets the header name used to store the trace ID." json:"traceIDHeaderName,omitempty" toml:"traceIDHeaderName,omitempty" yaml:"traceIDHeaderName,omitempty" export:"true"` - ParentIDHeaderName string `description:"Sets the header name used to store the parent ID." json:"parentIDHeaderName,omitempty" toml:"parentIDHeaderName,omitempty" yaml:"parentIDHeaderName,omitempty" export:"true"` - SamplingPriorityHeaderName string `description:"Sets the header name used to store the sampling priority." json:"samplingPriorityHeaderName,omitempty" toml:"samplingPriorityHeaderName,omitempty" yaml:"samplingPriorityHeaderName,omitempty" export:"true"` - BagagePrefixHeaderName string `description:"Sets the header name prefix used to store baggage items in a map." json:"bagagePrefixHeaderName,omitempty" toml:"bagagePrefixHeaderName,omitempty" yaml:"bagagePrefixHeaderName,omitempty" export:"true"` -} - -// SetDefaults sets the default values. -func (c *Config) SetDefaults() { - host, ok := os.LookupEnv("DD_AGENT_HOST") - if !ok { - host = "localhost" - } - - port, ok := os.LookupEnv("DD_TRACE_AGENT_PORT") - if !ok { - port = "8126" - } - - c.LocalAgentHostPort = net.JoinHostPort(host, port) -} - -// Setup sets up the tracer. -func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) { - logger := log.With().Str(logs.TracingProviderName, Name).Logger() - - opts := []datadog.StartOption{ - datadog.WithServiceName(serviceName), - datadog.WithDebugMode(c.Debug), - datadog.WithPropagator(datadog.NewPropagator(&datadog.PropagatorConfig{ - TraceHeader: c.TraceIDHeaderName, - ParentHeader: c.ParentIDHeaderName, - PriorityHeader: c.SamplingPriorityHeaderName, - BaggagePrefix: c.BagagePrefixHeaderName, - })), - datadog.WithLogger(logs.NewDatadogLogger(logger)), - } - - if c.LocalAgentSocket != "" { - opts = append(opts, datadog.WithUDS(c.LocalAgentSocket)) - } else { - opts = append(opts, datadog.WithAgentAddr(c.LocalAgentHostPort)) - } - - for k, v := range c.GlobalTags { - opts = append(opts, datadog.WithGlobalTag(k, v)) - } - - if c.PrioritySampling { - opts = append(opts, datadog.WithPrioritySampling()) - } - - tracer := ddtracer.New(opts...) - - // Without this, child spans are getting the NOOP tracer - opentracing.SetGlobalTracer(tracer) - - logger.Debug().Msg("Datadog tracer configured") - - return tracer, nil, nil -} diff --git a/pkg/tracing/elastic/elastic.go b/pkg/tracing/elastic/elastic.go deleted file mode 100644 index 383cca2bd..000000000 --- a/pkg/tracing/elastic/elastic.go +++ /dev/null @@ -1,74 +0,0 @@ -package elastic - -import ( - "io" - "net/url" - - "github.com/opentracing/opentracing-go" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/version" - "go.elastic.co/apm" - "go.elastic.co/apm/module/apmot" - "go.elastic.co/apm/transport" -) - -// Name sets the name of this tracer. -const Name = "elastic" - -func init() { - // The APM lib uses the init() function to create a default tracer. - // So this default tracer must be disabled. - // https://github.com/elastic/apm-agent-go/blob/8dd383d0d21776faad8841fe110f35633d199a03/tracer.go#L61-L65 - apm.DefaultTracer.Close() -} - -// Config provides configuration settings for a elastic.co tracer. -type Config struct { - ServerURL string `description:"Sets the URL of the Elastic APM server." json:"serverURL,omitempty" toml:"serverURL,omitempty" yaml:"serverURL,omitempty"` - SecretToken string `description:"Sets the token used to connect to Elastic APM Server." json:"secretToken,omitempty" toml:"secretToken,omitempty" yaml:"secretToken,omitempty" loggable:"false"` - ServiceEnvironment string `description:"Sets the name of the environment Traefik is deployed in, e.g. 'production' or 'staging'." json:"serviceEnvironment,omitempty" toml:"serviceEnvironment,omitempty" yaml:"serviceEnvironment,omitempty" export:"true"` -} - -// Setup sets up the tracer. -func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) { - // Create default transport. - tr, err := transport.NewHTTPTransport() - if err != nil { - return nil, nil, err - } - - if c.ServerURL != "" { - serverURL, err := url.Parse(c.ServerURL) - if err != nil { - return nil, nil, err - } - tr.SetServerURL(serverURL) - } - - if c.SecretToken != "" { - tr.SetSecretToken(c.SecretToken) - } - - tracer, err := apm.NewTracerOptions(apm.TracerOptions{ - ServiceName: serviceName, - ServiceVersion: version.Version, - ServiceEnvironment: c.ServiceEnvironment, - Transport: tr, - }) - if err != nil { - return nil, nil, err - } - - logger := log.With().Str(logs.TracingProviderName, Name).Logger() - tracer.SetLogger(logs.NewElasticLogger(logger)) - - otTracer := apmot.New(apmot.WithTracer(tracer)) - - // Without this, child spans are getting the NOOP tracer - opentracing.SetGlobalTracer(otTracer) - - log.Debug().Msg("Elastic tracer configured") - - return otTracer, nil, nil -} diff --git a/pkg/tracing/haystack/haystack.go b/pkg/tracing/haystack/haystack.go deleted file mode 100644 index 6e460dcfb..000000000 --- a/pkg/tracing/haystack/haystack.go +++ /dev/null @@ -1,72 +0,0 @@ -package haystack - -import ( - "io" - "strings" - "time" - - "github.com/ExpediaDotCom/haystack-client-go" - "github.com/opentracing/opentracing-go" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" -) - -// Name sets the name of this tracer. -const Name = "haystack" - -// Config provides configuration settings for a haystack tracer. -type Config struct { - LocalAgentHost string `description:"Sets the Haystack Agent host." json:"localAgentHost,omitempty" toml:"localAgentHost,omitempty" yaml:"localAgentHost,omitempty"` - LocalAgentPort int `description:"Sets the Haystack Agent port." json:"localAgentPort,omitempty" toml:"localAgentPort,omitempty" yaml:"localAgentPort,omitempty"` - GlobalTag string `description:"Sets a key:value tag on all spans." json:"globalTag,omitempty" toml:"globalTag,omitempty" yaml:"globalTag,omitempty" export:"true"` - TraceIDHeaderName string `description:"Sets the header name used to store the trace ID." json:"traceIDHeaderName,omitempty" toml:"traceIDHeaderName,omitempty" yaml:"traceIDHeaderName,omitempty" export:"true"` - ParentIDHeaderName string `description:"Sets the header name used to store the parent ID." json:"parentIDHeaderName,omitempty" toml:"parentIDHeaderName,omitempty" yaml:"parentIDHeaderName,omitempty" export:"true"` - SpanIDHeaderName string `description:"Sets the header name used to store the span ID." json:"spanIDHeaderName,omitempty" toml:"spanIDHeaderName,omitempty" yaml:"spanIDHeaderName,omitempty" export:"true"` - BaggagePrefixHeaderName string `description:"Sets the header name prefix used to store baggage items in a map." json:"baggagePrefixHeaderName,omitempty" toml:"baggagePrefixHeaderName,omitempty" yaml:"baggagePrefixHeaderName,omitempty" export:"true"` -} - -// SetDefaults sets the default values. -func (c *Config) SetDefaults() { - c.LocalAgentHost = "127.0.0.1" - c.LocalAgentPort = 35000 -} - -// Setup sets up the tracer. -func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) { - tag := strings.SplitN(c.GlobalTag, ":", 2) - - value := "" - if len(tag) == 2 { - value = tag[1] - } - - host := "localhost" - port := 35000 - if len(c.LocalAgentHost) > 0 { - host = c.LocalAgentHost - } - if c.LocalAgentPort > 0 { - port = c.LocalAgentPort - } - - logger := log.With().Str(logs.TracingProviderName, Name).Logger() - - tracer, closer := haystack.NewTracer(serviceName, haystack.NewAgentDispatcher(host, port, 3*time.Second, 1000), - haystack.TracerOptionsFactory.Tag(tag[0], value), - haystack.TracerOptionsFactory.Propagator(opentracing.HTTPHeaders, - haystack.NewTextMapPropagator(haystack.PropagatorOpts{ - TraceIDKEYName: c.TraceIDHeaderName, - ParentSpanIDKEYName: c.ParentIDHeaderName, - SpanIDKEYName: c.SpanIDHeaderName, - BaggagePrefixKEYName: c.BaggagePrefixHeaderName, - }, haystack.DefaultCodex{})), - haystack.TracerOptionsFactory.Logger(logs.NewHaystackLogger(logger)), - ) - - // Without this, child spans are getting the NOOP tracer - opentracing.SetGlobalTracer(tracer) - - log.Debug().Msg("haystack tracer configured") - - return tracer, closer, nil -} diff --git a/pkg/tracing/instana/instana.go b/pkg/tracing/instana/instana.go deleted file mode 100644 index edb2ee14e..000000000 --- a/pkg/tracing/instana/instana.go +++ /dev/null @@ -1,61 +0,0 @@ -package instana - -import ( - "io" - - instana "github.com/instana/go-sensor" - "github.com/opentracing/opentracing-go" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" -) - -// Name sets the name of this tracer. -const Name = "instana" - -// Config provides configuration settings for an instana tracer. -type Config struct { - LocalAgentHost string `description:"Sets the Instana Agent host." json:"localAgentHost,omitempty" toml:"localAgentHost,omitempty" yaml:"localAgentHost,omitempty"` - LocalAgentPort int `description:"Sets the Instana Agent port." json:"localAgentPort,omitempty" toml:"localAgentPort,omitempty" yaml:"localAgentPort,omitempty"` - LogLevel string `description:"Sets the log level for the Instana tracer. ('error','warn','info','debug')" json:"logLevel,omitempty" toml:"logLevel,omitempty" yaml:"logLevel,omitempty" export:"true"` - EnableAutoProfile bool `description:"Enables automatic profiling for the Traefik process." json:"enableAutoProfile,omitempty" toml:"enableAutoProfile,omitempty" yaml:"enableAutoProfile,omitempty" export:"true"` -} - -// SetDefaults sets the default values. -func (c *Config) SetDefaults() { - c.LocalAgentPort = 42699 - c.LogLevel = "info" -} - -// Setup sets up the tracer. -func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) { - // set default logLevel - logLevel := instana.Info - - // check/set logLevel overrides - switch c.LogLevel { - case "error": - logLevel = instana.Error - case "warn": - logLevel = instana.Warn - case "debug": - logLevel = instana.Debug - } - - logger := log.With().Str(logs.TracingProviderName, Name).Logger() - instana.SetLogger(logs.NewInstanaLogger(logger)) - - tracer := instana.NewTracerWithOptions(&instana.Options{ - Service: serviceName, - LogLevel: logLevel, - AgentPort: c.LocalAgentPort, - AgentHost: c.LocalAgentHost, - EnableAutoProfile: c.EnableAutoProfile, - }) - - // Without this, child spans are getting the NOOP tracer - opentracing.SetGlobalTracer(tracer) - - logger.Debug().Msg("Instana tracer configured") - - return tracer, nil, nil -} diff --git a/pkg/tracing/jaeger/jaeger.go b/pkg/tracing/jaeger/jaeger.go deleted file mode 100644 index 64eeb8e18..000000000 --- a/pkg/tracing/jaeger/jaeger.go +++ /dev/null @@ -1,124 +0,0 @@ -package jaeger - -import ( - "fmt" - "io" - - "github.com/opentracing/opentracing-go" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" - jaegercli "github.com/uber/jaeger-client-go" - jaegercfg "github.com/uber/jaeger-client-go/config" - "github.com/uber/jaeger-client-go/zipkin" - jaegermet "github.com/uber/jaeger-lib/metrics" -) - -// Name sets the name of this tracer. -const Name = "jaeger" - -// Config provides configuration settings for a jaeger tracer. -type Config struct { - SamplingServerURL string `description:"Sets the sampling server URL." json:"samplingServerURL,omitempty" toml:"samplingServerURL,omitempty" yaml:"samplingServerURL,omitempty"` - SamplingType string `description:"Sets the sampling type." json:"samplingType,omitempty" toml:"samplingType,omitempty" yaml:"samplingType,omitempty" export:"true"` - SamplingParam float64 `description:"Sets the sampling parameter." json:"samplingParam,omitempty" toml:"samplingParam,omitempty" yaml:"samplingParam,omitempty" export:"true"` - LocalAgentHostPort string `description:"Sets the Jaeger Agent host:port." json:"localAgentHostPort,omitempty" toml:"localAgentHostPort,omitempty" yaml:"localAgentHostPort,omitempty"` - Gen128Bit bool `description:"Generates 128 bits span IDs." json:"gen128Bit,omitempty" toml:"gen128Bit,omitempty" yaml:"gen128Bit,omitempty" export:"true"` - Propagation string `description:"Sets the propagation format (jaeger/b3)." json:"propagation,omitempty" toml:"propagation,omitempty" yaml:"propagation,omitempty" export:"true"` - TraceContextHeaderName string `description:"Sets the header name used to store the trace ID." json:"traceContextHeaderName,omitempty" toml:"traceContextHeaderName,omitempty" yaml:"traceContextHeaderName,omitempty" export:"true"` - Collector *Collector `description:"Defines the collector information." json:"collector,omitempty" toml:"collector,omitempty" yaml:"collector,omitempty" export:"true"` - DisableAttemptReconnecting bool `description:"Disables the periodic re-resolution of the agent's hostname and reconnection if there was a change." json:"disableAttemptReconnecting,omitempty" toml:"disableAttemptReconnecting,omitempty" yaml:"disableAttemptReconnecting,omitempty" export:"true"` -} - -// SetDefaults sets the default values. -func (c *Config) SetDefaults() { - c.SamplingServerURL = "http://localhost:5778/sampling" - c.SamplingType = "const" - c.SamplingParam = 1.0 - c.LocalAgentHostPort = "127.0.0.1:6831" - c.Propagation = "jaeger" - c.Gen128Bit = false - c.TraceContextHeaderName = jaegercli.TraceContextHeaderName - c.DisableAttemptReconnecting = true -} - -// Collector provides configuration settings for jaeger collector. -type Collector struct { - Endpoint string `description:"Instructs reporter to send spans to jaeger-collector at this URL." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - User string `description:"User for basic http authentication when sending spans to jaeger-collector." json:"user,omitempty" toml:"user,omitempty" yaml:"user,omitempty" loggable:"false"` - Password string `description:"Password for basic http authentication when sending spans to jaeger-collector." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"` -} - -// SetDefaults sets the default values. -func (c *Collector) SetDefaults() { - c.Endpoint = "" - c.User = "" - c.Password = "" -} - -// Setup sets up the tracer. -func (c *Config) Setup(componentName string) (opentracing.Tracer, io.Closer, error) { - reporter := &jaegercfg.ReporterConfig{ - LogSpans: true, - LocalAgentHostPort: c.LocalAgentHostPort, - DisableAttemptReconnecting: c.DisableAttemptReconnecting, - } - - if c.Collector != nil { - reporter.CollectorEndpoint = c.Collector.Endpoint - reporter.User = c.Collector.User - reporter.Password = c.Collector.Password - } - - jcfg := &jaegercfg.Configuration{ - Sampler: &jaegercfg.SamplerConfig{ - SamplingServerURL: c.SamplingServerURL, - Type: c.SamplingType, - Param: c.SamplingParam, - }, - Reporter: reporter, - Headers: &jaegercli.HeadersConfig{ - TraceContextHeaderName: c.TraceContextHeaderName, - }, - } - - // Overrides existing tracer's Configuration with environment variables. - _, err := jcfg.FromEnv() - if err != nil { - return nil, nil, err - } - - jMetricsFactory := jaegermet.NullFactory - - logger := log.With().Str(logs.TracingProviderName, Name).Logger() - - opts := []jaegercfg.Option{ - jaegercfg.Logger(logs.NewJaegerLogger(logger)), - jaegercfg.Metrics(jMetricsFactory), - jaegercfg.Gen128Bit(c.Gen128Bit), - } - - switch c.Propagation { - case "b3": - p := zipkin.NewZipkinB3HTTPHeaderPropagator() - opts = append(opts, - jaegercfg.Injector(opentracing.HTTPHeaders, p), - jaegercfg.Extractor(opentracing.HTTPHeaders, p), - ) - case "jaeger", "": - default: - return nil, nil, fmt.Errorf("unknown propagation format: %s", c.Propagation) - } - - // Initialize tracer with a logger and a metrics factory - closer, err := jcfg.InitGlobalTracer( - componentName, - opts..., - ) - if err != nil { - logger.Warn().Err(err).Msg("Could not initialize jaeger tracer") - return nil, nil, err - } - logger.Debug().Msg("Jaeger tracer configured") - - return opentracing.GlobalTracer(), closer, nil -} diff --git a/pkg/tracing/opentelemetry/opentelemetry.go b/pkg/tracing/opentelemetry/opentelemetry.go index c59fbd410..2e13bc20c 100644 --- a/pkg/tracing/opentelemetry/opentelemetry.go +++ b/pkg/tracing/opentelemetry/opentelemetry.go @@ -5,20 +5,21 @@ import ( "fmt" "io" "net" + "net/url" + "time" - "github.com/opentracing/opentracing-go" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" "go.opentelemetry.io/otel" - oteltracer "go.opentelemetry.io/otel/bridge/opentracing" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.12.0" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding/gzip" @@ -26,85 +27,108 @@ import ( // Config provides configuration settings for the open-telemetry tracer. type Config struct { - // NOTE: as no gRPC option is implemented yet, the type is empty and is used as a boolean for upward compatibility purposes. - GRPC *struct{} `description:"gRPC specific configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + GRPC *GRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" export:"true"` + HTTP *HTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` +} - Address string `description:"Sets the address (host:port) of the collector endpoint." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` - Path string `description:"Sets the URL path of the collector endpoint." json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"` - Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` - Headers map[string]string `description:"Defines additional headers to be sent with the payloads." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` - TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` +// GRPC provides configuration settings for the gRPC open-telemetry tracer. +type GRPC struct { + Endpoint string `description:"Sets the gRPC endpoint (host:port) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + + Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` + TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` } // SetDefaults sets the default values. -func (c *Config) SetDefaults() { - c.Address = "localhost:4318" +func (c *GRPC) SetDefaults() { + c.Endpoint = "localhost:4317" +} + +// HTTP provides configuration settings for the HTTP open-telemetry tracer. +type HTTP struct { + Endpoint string `description:"Sets the HTTP endpoint (scheme://host:port/v1/traces) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` +} + +// SetDefaults sets the default values. +func (c *HTTP) SetDefaults() { + c.Endpoint = "localhost:4318" } // Setup sets up the tracer. -func (c *Config) Setup(componentName string) (opentracing.Tracer, io.Closer, error) { +func (c *Config) Setup(serviceName string, sampleRate float64, globalAttributes map[string]string, headers map[string]string) (trace.Tracer, io.Closer, error) { var ( err error exporter *otlptrace.Exporter ) if c.GRPC != nil { - exporter, err = c.setupGRPCExporter() + exporter, err = c.setupGRPCExporter(headers) } else { - exporter, err = c.setupHTTPExporter() + exporter, err = c.setupHTTPExporter(headers) } if err != nil { return nil, nil, fmt.Errorf("setting up exporter: %w", err) } - bt := oteltracer.NewBridgeTracer() - bt.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) + attr := []attribute.KeyValue{ + semconv.ServiceNameKey.String(serviceName), + semconv.ServiceVersionKey.String(version.Version), + } - bt.SetOpenTelemetryTracer(otel.Tracer(componentName, trace.WithInstrumentationVersion(version.Version))) - opentracing.SetGlobalTracer(bt) + for k, v := range globalAttributes { + attr = append(attr, attribute.String(k, v)) + } res, err := resource.New(context.Background(), - resource.WithAttributes(semconv.ServiceNameKey.String("traefik")), - resource.WithAttributes(semconv.ServiceVersionKey.String(version.Version)), + resource.WithAttributes(attr...), resource.WithFromEnv(), resource.WithTelemetrySDK(), + resource.WithOSType(), + resource.WithProcessCommandArgs(), ) if err != nil { return nil, nil, fmt.Errorf("building resource: %w", err) } + // Register the trace exporter with a TracerProvider, using a batch + // span processor to aggregate spans before export. + bsp := sdktrace.NewBatchSpanProcessor(exporter) tracerProvider := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.TraceIDRatioBased(sampleRate)), sdktrace.WithResource(res), - sdktrace.WithBatcher(exporter), + sdktrace.WithSpanProcessor(bsp), ) + otel.SetTracerProvider(tracerProvider) + otel.SetTextMapPropagator(propagation.TraceContext{}) log.Debug().Msg("OpenTelemetry tracer configured") - return bt, tpCloser{provider: tracerProvider}, err + return tracerProvider.Tracer("github.com/traefik/traefik"), &tpCloser{provider: tracerProvider}, err } -func (c *Config) setupHTTPExporter() (*otlptrace.Exporter, error) { - host, port, err := net.SplitHostPort(c.Address) +func (c *Config) setupHTTPExporter(headers map[string]string) (*otlptrace.Exporter, error) { + endpoint, err := url.Parse(c.HTTP.Endpoint) if err != nil { - return nil, fmt.Errorf("invalid collector address %q: %w", c.Address, err) + return nil, fmt.Errorf("invalid collector endpoint %q: %w", c.HTTP.Endpoint, err) } opts := []otlptracehttp.Option{ - otlptracehttp.WithEndpoint(fmt.Sprintf("%s:%s", host, port)), - otlptracehttp.WithHeaders(c.Headers), + otlptracehttp.WithEndpoint(endpoint.Host), + otlptracehttp.WithHeaders(headers), otlptracehttp.WithCompression(otlptracehttp.GzipCompression), } - if c.Insecure { + if endpoint.Scheme == "http" { opts = append(opts, otlptracehttp.WithInsecure()) } - if c.Path != "" { - opts = append(opts, otlptracehttp.WithURLPath(c.Path)) + if endpoint.Path != "" { + opts = append(opts, otlptracehttp.WithURLPath(endpoint.Path)) } - if c.TLS != nil { - tlsConfig, err := c.TLS.CreateTLSConfig(context.Background()) + if c.HTTP.TLS != nil { + tlsConfig, err := c.HTTP.TLS.CreateTLSConfig(context.Background()) if err != nil { return nil, fmt.Errorf("creating TLS client config: %w", err) } @@ -115,24 +139,24 @@ func (c *Config) setupHTTPExporter() (*otlptrace.Exporter, error) { return otlptrace.New(context.Background(), otlptracehttp.NewClient(opts...)) } -func (c *Config) setupGRPCExporter() (*otlptrace.Exporter, error) { - host, port, err := net.SplitHostPort(c.Address) +func (c *Config) setupGRPCExporter(headers map[string]string) (*otlptrace.Exporter, error) { + host, port, err := net.SplitHostPort(c.GRPC.Endpoint) if err != nil { - return nil, fmt.Errorf("invalid collector address %q: %w", c.Address, err) + return nil, fmt.Errorf("invalid collector address %q: %w", c.GRPC.Endpoint, err) } opts := []otlptracegrpc.Option{ otlptracegrpc.WithEndpoint(fmt.Sprintf("%s:%s", host, port)), - otlptracegrpc.WithHeaders(c.Headers), + otlptracegrpc.WithHeaders(headers), otlptracegrpc.WithCompressor(gzip.Name), } - if c.Insecure { + if c.GRPC.Insecure { opts = append(opts, otlptracegrpc.WithInsecure()) } - if c.TLS != nil { - tlsConfig, err := c.TLS.CreateTLSConfig(context.Background()) + if c.GRPC.TLS != nil { + tlsConfig, err := c.GRPC.TLS.CreateTLSConfig(context.Background()) if err != nil { return nil, fmt.Errorf("creating TLS client config: %w", err) } @@ -148,6 +172,13 @@ type tpCloser struct { provider *sdktrace.TracerProvider } -func (t tpCloser) Close() error { - return t.provider.Shutdown(context.Background()) +func (t *tpCloser) Close() error { + if t == nil { + return nil + } + + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) + defer cancel() + + return t.provider.Shutdown(ctx) } diff --git a/pkg/tracing/opentelemetry/opentelemetry_test.go b/pkg/tracing/opentelemetry/opentelemetry_test.go index 40fcca159..ad7f13a4d 100644 --- a/pkg/tracing/opentelemetry/opentelemetry_test.go +++ b/pkg/tracing/opentelemetry/opentelemetry_test.go @@ -1,4 +1,4 @@ -package opentelemetry +package opentelemetry_test import ( "compress/gzip" @@ -7,14 +7,16 @@ import ( "io" "net/http" "net/http/httptest" - "strings" "testing" "time" + "github.com/containous/alice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - mtracing "github.com/traefik/traefik/v3/pkg/middlewares/tracing" + "github.com/traefik/traefik/v3/pkg/config/static" + tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp" ) @@ -68,14 +70,25 @@ func TestTracing(t *testing.T) { })) t.Cleanup(collector.Close) - newTracing, err := tracing.NewTracing("", 0, &Config{ - Insecure: true, - Address: strings.TrimPrefix(collector.URL, "http://"), - }) - require.NoError(t, err) - t.Cleanup(newTracing.Close) + tracingConfig := &static.Tracing{ + ServiceName: "traefik", + SampleRate: 1.0, + OTLP: &opentelemetry.Config{ + HTTP: &opentelemetry.HTTP{ + Endpoint: collector.URL, + }, + }, + } - epHandler := mtracing.NewEntryPoint(context.Background(), newTracing, "test", http.NotFoundHandler()) + newTracing, closer, err := tracing.NewTracing(tracingConfig) + require.NoError(t, err) + t.Cleanup(func() { + _ = closer.Close() + }) + + chain := alice.New(tracingMiddle.WrapEntryPointHandler(context.Background(), newTracing, "test")) + epHandler, err := chain.Then(http.NotFoundHandler()) + require.NoError(t, err) for _, test := range tests { t.Run(test.desc, func(t *testing.T) { diff --git a/pkg/tracing/operation_name.go b/pkg/tracing/operation_name.go deleted file mode 100644 index 70694747b..000000000 --- a/pkg/tracing/operation_name.go +++ /dev/null @@ -1,65 +0,0 @@ -package tracing - -import ( - "crypto/sha256" - "fmt" - "strings" - - "github.com/rs/zerolog/log" -) - -// TraceNameHashLength defines the number of characters to use from the head of the generated hash. -const TraceNameHashLength = 8 - -// OperationNameMaxLengthNumber defines the number of static characters in a Span Trace name: -// 8 chars for hash + 2 chars for '_'. -const OperationNameMaxLengthNumber = 10 - -func generateOperationName(prefix string, parts []string, sep string, spanLimit int) string { - name := prefix + " " + strings.Join(parts, sep) - - maxLength := OperationNameMaxLengthNumber + len(prefix) + 1 - - if spanLimit > 0 && len(name) > spanLimit { - if spanLimit < maxLength { - log.Warn().Msgf("SpanNameLimit cannot be lesser than %d: falling back on %d", maxLength, maxLength+3) - spanLimit = maxLength + 3 - } - - limit := (spanLimit - maxLength) / 2 - - var fragments []string - for _, value := range parts { - fragments = append(fragments, truncateString(value, limit)) - } - fragments = append(fragments, computeHash(name)) - - name = prefix + " " + strings.Join(fragments, sep) - } - - return name -} - -// truncateString reduces the length of the 'str' argument to 'num' - 3 and adds a '...' suffix to the tail. -func truncateString(str string, num int) string { - text := str - if len(str) > num { - if num > 3 { - num -= 3 - } - text = str[0:num] + "..." - } - return text -} - -// computeHash returns the first TraceNameHashLength character of the sha256 hash for 'name' argument. -func computeHash(name string) string { - data := []byte(name) - hash := sha256.New() - if _, err := hash.Write(data); err != nil { - // Impossible case - log.Error().Str("OperationName", name).Err(err).Msgf("Failed to create Span name hash for %s", name) - } - - return fmt.Sprintf("%x", hash.Sum(nil))[:TraceNameHashLength] -} diff --git a/pkg/tracing/operation_name_test.go b/pkg/tracing/operation_name_test.go deleted file mode 100644 index 599151098..000000000 --- a/pkg/tracing/operation_name_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package tracing - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_generateOperationName(t *testing.T) { - testCases := []struct { - desc string - prefix string - parts []string - sep string - spanLimit int - expected string - }{ - { - desc: "empty", - expected: " ", - }, - { - desc: "with prefix, without parts", - prefix: "foo", - parts: []string{}, - sep: "-", - spanLimit: 0, - expected: "foo ", - }, - { - desc: "with prefix, without parts, too small span limit", - prefix: "foo", - parts: []string{}, - sep: "-", - spanLimit: 1, - expected: "foo 6c2d2c76", - }, - { - desc: "with prefix, with parts", - prefix: "foo", - parts: []string{"fii", "fuu", "fee", "faa"}, - sep: "-", - spanLimit: 0, - expected: "foo fii-fuu-fee-faa", - }, - { - desc: "with prefix, with parts, with span limit", - prefix: "foo", - parts: []string{"fff", "ooo", "ooo", "bbb", "aaa", "rrr"}, - sep: "-", - spanLimit: 20, - expected: "foo fff-ooo-ooo-bbb-aaa-rrr-1a8e8ac1", - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - opName := generateOperationName(test.prefix, test.parts, test.sep, test.spanLimit) - assert.Equal(t, test.expected, opName) - }) - } -} - -func TestComputeHash(t *testing.T) { - testCases := []struct { - desc string - text string - expected string - }{ - { - desc: "hashing", - text: "some very long piece of text", - expected: "0c6e798b", - }, - { - desc: "short text less than limit 10", - text: "short", - expected: "f9b0078b", - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := computeHash(test.text) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestTruncateString(t *testing.T) { - testCases := []struct { - desc string - text string - limit int - expected string - }{ - { - desc: "short text less than limit 10", - text: "short", - limit: 10, - expected: "short", - }, - { - desc: "basic truncate with limit 10", - text: "some very long piece of text", - limit: 10, - expected: "some ve...", - }, - { - desc: "truncate long FQDN to 39 chars", - text: "some-service-100.slug.namespace.environment.domain.tld", - limit: 39, - expected: "some-service-100.slug.namespace.envi...", - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := truncateString(test.text, test.limit) - - assert.Equal(t, test.expected, actual) - assert.True(t, len(actual) <= test.limit) - }) - } -} diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go index d8923f510..6bb382920 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/tracing/tracing.go @@ -2,182 +2,213 @@ package tracing import ( "context" - "errors" "fmt" "io" + "net" "net/http" + "strconv" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/static" + "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/propagation" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" ) -type contextKey int - -const ( - // SpanKindNoneEnum Span kind enum none. - SpanKindNoneEnum ext.SpanKindEnum = "none" - tracingKey contextKey = iota -) - -// WithTracing Adds Tracing into the context. -func WithTracing(ctx context.Context, tracing *Tracing) context.Context { - return context.WithValue(ctx, tracingKey, tracing) -} - -// FromContext Gets Tracing from context. -func FromContext(ctx context.Context) (*Tracing, error) { - if ctx == nil { - panic("nil context") - } - - tracer, ok := ctx.Value(tracingKey).(*Tracing) - if !ok { - return nil, errors.New("unable to find tracing in the context") - } - return tracer, nil -} - -// Backend is an abstraction for tracking backend (Jaeger, Zipkin, ...). +// Backend is an abstraction for tracking backend (OpenTelemetry, ...). type Backend interface { - Setup(componentName string) (opentracing.Tracer, io.Closer, error) -} - -// Tracing middleware. -type Tracing struct { - ServiceName string `description:"Sets the name for this service" export:"true"` - SpanNameLimit int `description:"Sets the maximum character limit for span names (default 0 = no limit)" export:"true"` - - tracer opentracing.Tracer - closer io.Closer + Setup(serviceName string, sampleRate float64, globalAttributes map[string]string, headers map[string]string) (trace.Tracer, io.Closer, error) } // NewTracing Creates a Tracing. -func NewTracing(serviceName string, spanNameLimit int, tracingBackend Backend) (*Tracing, error) { - tracing := &Tracing{ - ServiceName: serviceName, - SpanNameLimit: spanNameLimit, +func NewTracing(conf *static.Tracing) (trace.Tracer, io.Closer, error) { + var backend Backend + + if conf.OTLP != nil { + backend = conf.OTLP } - var err error - tracing.tracer, tracing.closer, err = tracingBackend.Setup(serviceName) + if backend == nil { + log.Debug().Msg("Could not initialize tracing, using OpenTelemetry by default") + defaultBackend := &opentelemetry.Config{} + backend = defaultBackend + } + + return backend.Setup(conf.ServiceName, conf.SampleRate, conf.GlobalAttributes, conf.Headers) +} + +// TracerFromContext extracts the trace.Tracer from the given context. +func TracerFromContext(ctx context.Context) trace.Tracer { + span := trace.SpanFromContext(ctx) + if span != nil && span.TracerProvider() != nil { + return span.TracerProvider().Tracer("github.com/traefik/traefik") + } + + return nil +} + +// ExtractCarrierIntoContext reads cross-cutting concerns from the carrier into a Context. +func ExtractCarrierIntoContext(ctx context.Context, headers http.Header) context.Context { + propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) + return propagator.Extract(ctx, propagation.HeaderCarrier(headers)) +} + +// InjectContextIntoCarrier sets cross-cutting concerns from the request context into the request headers. +func InjectContextIntoCarrier(req *http.Request) { + propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) + propagator.Inject(req.Context(), propagation.HeaderCarrier(req.Header)) +} + +// SetStatusErrorf flags the span as in error and log an event. +func SetStatusErrorf(ctx context.Context, format string, args ...interface{}) { + if span := trace.SpanFromContext(ctx); span != nil { + span.SetStatus(codes.Error, fmt.Sprintf(format, args...)) + } +} + +// LogClientRequest used to add span attributes from the request as a Client. +// TODO: the semconv does not implement Semantic Convention v1.23.0. +func LogClientRequest(span trace.Span, r *http.Request) { + if r == nil || span == nil { + return + } + + // Common attributes https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes + span.SetAttributes(semconv.HTTPRequestMethodKey.String(r.Method)) + span.SetAttributes(semconv.NetworkProtocolVersion(proto(r.Proto))) + + // Client attributes https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#http-client + span.SetAttributes(semconv.URLFull(r.URL.String())) + span.SetAttributes(semconv.URLScheme(r.URL.Scheme)) + span.SetAttributes(semconv.UserAgentOriginal(r.UserAgent())) + + host, port, err := net.SplitHostPort(r.URL.Host) if err != nil { - return nil, err - } - return tracing, nil -} - -// StartSpan delegates to opentracing.Tracer. -func (t *Tracing) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span { - return t.tracer.StartSpan(operationName, opts...) -} - -// StartSpanf delegates to StartSpan. -func (t *Tracing) StartSpanf(r *http.Request, spanKind ext.SpanKindEnum, opPrefix string, opParts []string, separator string, opts ...opentracing.StartSpanOption) (opentracing.Span, *http.Request, func()) { - operationName := generateOperationName(opPrefix, opParts, separator, t.SpanNameLimit) - - return StartSpan(r, operationName, spanKind, opts...) -} - -// Inject delegates to opentracing.Tracer. -func (t *Tracing) Inject(sm opentracing.SpanContext, format, carrier interface{}) error { - return t.tracer.Inject(sm, format, carrier) -} - -// Extract delegates to opentracing.Tracer. -func (t *Tracing) Extract(format, carrier interface{}) (opentracing.SpanContext, error) { - return t.tracer.Extract(format, carrier) -} - -// IsEnabled determines if tracing was successfully activated. -func (t *Tracing) IsEnabled() bool { - return t != nil && t.tracer != nil -} - -// Close tracer. -func (t *Tracing) Close() { - if t.closer != nil { - err := t.closer.Close() - if err != nil { - log.Warn().Err(err).Send() + span.SetAttributes(attribute.String("network.peer.address", host)) + span.SetAttributes(semconv.ServerAddress(r.URL.Host)) + switch r.URL.Scheme { + case "http": + span.SetAttributes(attribute.String("network.peer.port", "80")) + span.SetAttributes(semconv.ServerPort(80)) + case "https": + span.SetAttributes(attribute.String("network.peer.port", "443")) + span.SetAttributes(semconv.ServerPort(443)) } + } else { + span.SetAttributes(attribute.String("network.peer.address", host)) + span.SetAttributes(attribute.String("network.peer.port", port)) + intPort, _ := strconv.Atoi(port) + span.SetAttributes(semconv.ServerAddress(host)) + span.SetAttributes(semconv.ServerPort(intPort)) } } -// LogRequest used to create span tags from the request. -func LogRequest(span opentracing.Span, r *http.Request) { - if span != nil && r != nil { - ext.HTTPMethod.Set(span, r.Method) - ext.HTTPUrl.Set(span, r.URL.String()) - span.SetTag("http.host", r.Host) +// LogServerRequest used to add span attributes from the request as a Server. +// TODO: the semconv does not implement Semantic Convention v1.23.0. +func LogServerRequest(span trace.Span, r *http.Request) { + if r == nil { + return + } + + // Common attributes https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes + span.SetAttributes(semconv.HTTPRequestMethodKey.String(r.Method)) + span.SetAttributes(semconv.NetworkProtocolVersion(proto(r.Proto))) + + // Server attributes https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#http-server-semantic-conventions + span.SetAttributes(semconv.HTTPRequestBodySize(int(r.ContentLength))) + span.SetAttributes(semconv.URLPath(r.URL.Path)) + span.SetAttributes(semconv.URLQuery(r.URL.RawQuery)) + span.SetAttributes(semconv.URLScheme(r.Header.Get("X-Forwarded-Proto"))) + span.SetAttributes(semconv.UserAgentOriginal(r.UserAgent())) + span.SetAttributes(semconv.ServerAddress(r.Host)) + + host, port, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + span.SetAttributes(semconv.ClientAddress(r.RemoteAddr)) + span.SetAttributes(attribute.String("network.peer.address", r.RemoteAddr)) + } else { + span.SetAttributes(attribute.String("network.peer.address", host)) + span.SetAttributes(attribute.String("network.peer.port", port)) + span.SetAttributes(semconv.ClientAddress(host)) + intPort, _ := strconv.Atoi(port) + span.SetAttributes(semconv.ClientPort(intPort)) + } + + span.SetAttributes(semconv.ClientSocketAddress(r.Header.Get("X-Forwarded-For"))) +} + +func proto(proto string) string { + switch proto { + case "HTTP/1.0": + return "1.0" + case "HTTP/1.1": + return "1.1" + case "HTTP/2": + return "2" + case "HTTP/3": + return "3" + default: + return proto } } // LogResponseCode used to log response code in span. -func LogResponseCode(span opentracing.Span, code int) { +func LogResponseCode(span trace.Span, code int, spanKind trace.SpanKind) { if span != nil { - ext.HTTPStatusCode.Set(span, uint16(code)) - if code >= http.StatusInternalServerError { - ext.Error.Set(span, true) + var status codes.Code + var desc string + switch spanKind { + case trace.SpanKindServer: + status, desc = ServerStatus(code) + case trace.SpanKindClient: + status, desc = ClientStatus(code) + default: + status, desc = DefaultStatus(code) + } + span.SetStatus(status, desc) + if code > 0 { + span.SetAttributes(semconv.HTTPResponseStatusCode(code)) } } } -// GetSpan used to retrieve span from request context. -func GetSpan(r *http.Request) opentracing.Span { - return opentracing.SpanFromContext(r.Context()) -} - -// InjectRequestHeaders used to inject OpenTracing headers into the request. -func InjectRequestHeaders(r *http.Request) { - if span := GetSpan(r); span != nil { - err := opentracing.GlobalTracer().Inject( - span.Context(), - opentracing.HTTPHeaders, - opentracing.HTTPHeadersCarrier(r.Header)) - if err != nil { - log.Ctx(r.Context()).Error().Err(err).Send() - } +// ServerStatus returns a span status code and message for an HTTP status code +// value returned by a server. Status codes in the 400-499 range are not +// returned as errors. +func ServerStatus(code int) (codes.Code, string) { + if code < 100 || code >= 600 { + return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } -} - -// LogEventf logs an event to the span in the request context. -func LogEventf(r *http.Request, format string, args ...interface{}) { - if span := GetSpan(r); span != nil { - span.LogKV("event", fmt.Sprintf(format, args...)) + if code >= 500 { + return codes.Error, "" } + return codes.Unset, "" } -// StartSpan starts a new span from the one in the request context. -func StartSpan(r *http.Request, operationName string, spanKind ext.SpanKindEnum, opts ...opentracing.StartSpanOption) (opentracing.Span, *http.Request, func()) { - span, ctx := opentracing.StartSpanFromContext(r.Context(), operationName, opts...) - - switch spanKind { - case ext.SpanKindRPCClientEnum: - ext.SpanKindRPCClient.Set(span) - case ext.SpanKindRPCServerEnum: - ext.SpanKindRPCServer.Set(span) - case ext.SpanKindProducerEnum: - ext.SpanKindProducer.Set(span) - case ext.SpanKindConsumerEnum: - ext.SpanKindConsumer.Set(span) - default: - // noop +// ClientStatus returns a span status code and message for an HTTP status code +// value returned by a server. Status codes in the 400-499 range are not +// returned as errors. +func ClientStatus(code int) (codes.Code, string) { + if code < 100 || code >= 600 { + return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } - - r = r.WithContext(ctx) - return span, r, func() { span.Finish() } -} - -// SetError flags the span associated with this request as in error. -func SetError(r *http.Request) { - if span := GetSpan(r); span != nil { - ext.Error.Set(span, true) + if code >= 400 { + return codes.Error, "" } + return codes.Unset, "" } -// SetErrorWithEvent flags the span associated with this request as in error and log an event. -func SetErrorWithEvent(r *http.Request, format string, args ...interface{}) { - SetError(r) - LogEventf(r, format, args...) +// DefaultStatus returns a span status code and message for an HTTP status code +// value generated internally. +func DefaultStatus(code int) (codes.Code, string) { + if code < 100 || code >= 600 { + return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) + } + if code >= 500 { + return codes.Error, "" + } + return codes.Unset, "" } diff --git a/pkg/tracing/zipkin/zipkin.go b/pkg/tracing/zipkin/zipkin.go deleted file mode 100644 index 598f38037..000000000 --- a/pkg/tracing/zipkin/zipkin.go +++ /dev/null @@ -1,71 +0,0 @@ -package zipkin - -import ( - "io" - "time" - - "github.com/opentracing/opentracing-go" - zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing" - "github.com/openzipkin/zipkin-go" - "github.com/openzipkin/zipkin-go/reporter/http" - "github.com/rs/zerolog/log" -) - -// Name sets the name of this tracer. -const Name = "zipkin" - -// Config provides configuration settings for a zipkin tracer. -type Config struct { - HTTPEndpoint string `description:"Sets the HTTP Endpoint to report traces to." json:"httpEndpoint,omitempty" toml:"httpEndpoint,omitempty" yaml:"httpEndpoint,omitempty"` - SameSpan bool `description:"Uses SameSpan RPC style traces." json:"sameSpan,omitempty" toml:"sameSpan,omitempty" yaml:"sameSpan,omitempty" export:"true"` - ID128Bit bool `description:"Uses 128 bits root span IDs." json:"id128Bit,omitempty" toml:"id128Bit,omitempty" yaml:"id128Bit,omitempty" export:"true"` - SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"` -} - -// SetDefaults sets the default values. -func (c *Config) SetDefaults() { - c.HTTPEndpoint = "http://localhost:9411/api/v2/spans" - c.SameSpan = false - c.ID128Bit = true - c.SampleRate = 1.0 -} - -// Setup sets up the tracer. -func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) { - // create our local endpoint - endpoint, err := zipkin.NewEndpoint(serviceName, "0.0.0.0:0") - if err != nil { - return nil, nil, err - } - - // create our sampler - sampler, err := zipkin.NewBoundarySampler(c.SampleRate, time.Now().Unix()) - if err != nil { - return nil, nil, err - } - - // create the span reporter - reporter := http.NewReporter(c.HTTPEndpoint) - - // create the native Zipkin tracer - nativeTracer, err := zipkin.NewTracer( - reporter, - zipkin.WithLocalEndpoint(endpoint), - zipkin.WithSharedSpans(c.SameSpan), - zipkin.WithTraceID128Bit(c.ID128Bit), - zipkin.WithSampler(sampler), - ) - if err != nil { - return nil, nil, err - } - - // wrap the Zipkin native tracer with the OpenTracing Bridge - tracer := zipkinot.Wrap(nativeTracer) - - // Without this, child spans are getting the NOOP tracer - opentracing.SetGlobalTracer(tracer) - - log.Debug().Msg("Zipkin tracer configured") - - return tracer, reporter, nil -} diff --git a/pkg/types/file_or_content.go b/pkg/types/file_or_content.go new file mode 100644 index 000000000..7b1f679bc --- /dev/null +++ b/pkg/types/file_or_content.go @@ -0,0 +1,32 @@ +package types + +import "os" + +// FileOrContent holds a file path or content. +type FileOrContent string + +// String returns the FileOrContent in string format. +func (f FileOrContent) String() string { + return string(f) +} + +// IsPath returns true if the FileOrContent is a file path, otherwise returns false. +func (f FileOrContent) IsPath() bool { + _, err := os.Stat(f.String()) + return err == nil +} + +// Read returns the content after reading the FileOrContent variable. +func (f FileOrContent) Read() ([]byte, error) { + var content []byte + if f.IsPath() { + var err error + content, err = os.ReadFile(f.String()) + if err != nil { + return nil, err + } + } else { + content = []byte(f) + } + return content, nil +} diff --git a/pkg/types/zz_generated.deepcopy.go b/pkg/types/zz_generated.deepcopy.go index 8c55ace87..f2853fdfc 100644 --- a/pkg/types/zz_generated.deepcopy.go +++ b/pkg/types/zz_generated.deepcopy.go @@ -4,7 +4,7 @@ /* The MIT License (MIT) -Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs +Copyright (c) 2016-2020 Containous SAS; 2020-2024 Traefik Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pkg/udp/conn_test.go b/pkg/udp/conn_test.go index 6351e3309..44e748474 100644 --- a/pkg/udp/conn_test.go +++ b/pkg/udp/conn_test.go @@ -220,10 +220,10 @@ func testTimeout(t *testing.T, withRead bool) { time.Sleep(10 * time.Millisecond) - assert.Equal(t, 10, len(ln.conns)) + assert.Len(t, ln.conns, 10) time.Sleep(ln.timeout + time.Second) - assert.Equal(t, 0, len(ln.conns)) + assert.Empty(t, ln.conns) } func TestShutdown(t *testing.T) { diff --git a/script/binary b/script/binary deleted file mode 100755 index c1de7c9bb..000000000 --- a/script/binary +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -set -e - -rm -f dist/traefik - -FLAGS=() -if [ -n "${VERBOSE}" ]; then - FLAGS+=(-v) -fi - -if [ -z "${VERSION}" ]; then - VERSION=$(git rev-parse HEAD) -fi - -if [ -z "$CODENAME" ]; then - CODENAME=cheddar -fi - -if [ -z "$DATE" ]; then - DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p') -fi - -# Build binaries -# shellcheck disable=SC2086 -# shellcheck disable=SC2048 -CGO_ENABLED=0 GOGC=off go build ${FLAGS[*]} -ldflags "-s -w \ - -X github.com/traefik/traefik/v3/pkg/version.Version=$VERSION \ - -X github.com/traefik/traefik/v3/pkg/version.Codename=$CODENAME \ - -X github.com/traefik/traefik/v3/pkg/version.BuildDate=$DATE" \ - -installsuffix nocgo -o dist/traefik ./cmd/traefik diff --git a/script/code-gen-docker.sh b/script/code-gen-docker.sh new file mode 100755 index 000000000..9b1065507 --- /dev/null +++ b/script/code-gen-docker.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2046 + +set -e -o pipefail + +export PROJECT_MODULE="github.com/traefik/traefik" +export MODULE_VERSION="v3" +IMAGE_NAME="kubernetes-codegen:latest" +CURRENT_DIR="$(pwd)" + +echo "Building codegen Docker image..." +docker build --build-arg KUBE_VERSION=v0.28.3 \ + --build-arg USER="${USER}" \ + --build-arg UID="$(id -u)" \ + --build-arg GID="$(id -g)" \ + -f "./script/codegen.Dockerfile" \ + -t "${IMAGE_NAME}" \ + "." + +echo "Generating Traefik clientSet code and DeepCopy code ..." +docker run --rm \ + -v "${CURRENT_DIR}:/go/src/${PROJECT_MODULE}" \ + -w "/go/src/${PROJECT_MODULE}" \ + -e "PROJECT_MODULE=${PROJECT_MODULE}" \ + -e "MODULE_VERSION=${MODULE_VERSION}" \ + "${IMAGE_NAME}" \ + bash ./script/code-gen.sh + +echo "Generating the CRD definitions for the documentation ..." +docker run --rm \ + -v "${CURRENT_DIR}:/go/src/${PROJECT_MODULE}" \ + -w "/go/src/${PROJECT_MODULE}" \ + "${IMAGE_NAME}" \ + controller-gen crd:crdVersions=v1 \ + paths={./pkg/provider/kubernetes/crd/traefikio/v1alpha1/...} \ + output:dir=./docs/content/reference/dynamic-configuration/ + +echo "Concatenate the CRD definitions for publication and integration tests ..." +cat "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/traefik.io_*.yaml > "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +cp -f "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml "${CURRENT_DIR}"/integration/fixtures/k8s/01-traefik-crd.yml diff --git a/script/code-gen.sh b/script/code-gen.sh index 905ac67cb..0d1f309c2 100755 --- a/script/code-gen.sh +++ b/script/code-gen.sh @@ -1,58 +1,31 @@ -#!/bin/bash -e +#!/usr/bin/env bash # shellcheck disable=SC2046 set -e -o pipefail -PROJECT_MODULE="github.com/traefik/traefik" -MODULE_VERSION="v3" -IMAGE_NAME="kubernetes-codegen:latest" -CURRENT_DIR="$(pwd)" +source /go/src/k8s.io/code-generator/kube_codegen.sh -echo "Building codegen Docker image..." -docker build --build-arg KUBE_VERSION=v0.20.2 \ - --build-arg USER="${USER}" \ - --build-arg UID="$(id -u)" \ - --build-arg GID="$(id -g)" \ - -f "./script/codegen.Dockerfile" \ - -t "${IMAGE_NAME}" \ - "." +git config --global --add safe.directory "/go/src/${PROJECT_MODULE}" -echo "Generating Traefik clientSet code ..." -docker run --rm \ - -v "${CURRENT_DIR}:/go/src/${PROJECT_MODULE}" \ - -w "/go/src/${PROJECT_MODULE}" \ - "${IMAGE_NAME}" \ - /go/src/k8s.io/code-generator/generate-groups.sh all \ - ${PROJECT_MODULE}/${MODULE_VERSION}/pkg/provider/kubernetes/crd/generated \ - ${PROJECT_MODULE}/${MODULE_VERSION}/pkg/provider/kubernetes/crd \ - "traefikio:v1alpha1" \ - --go-header-file=/go/src/${PROJECT_MODULE}/script/boilerplate.go.tmpl +rm -rf "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}" +mkdir -p "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}/" -echo "Generating DeepCopy code ..." +# TODO: remove the workaround when the issue is solved in the code-generator +# (https://github.com/kubernetes/code-generator/issues/165). +# Here, we create the soft link named "${PROJECT_MODULE}" to the parent directory of +# Traefik to ensure the layout required by the kube_codegen.sh script. +ln -s "/go/src/${PROJECT_MODULE}/pkg" "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}/" -docker run --rm \ - -v "${CURRENT_DIR}:/go/src/${PROJECT_MODULE}" \ - -w "/go/src/${PROJECT_MODULE}" \ - "${IMAGE_NAME}" \ - deepcopy-gen \ - --input-dirs ${PROJECT_MODULE}/${MODULE_VERSION}/pkg/config/dynamic \ - --input-dirs ${PROJECT_MODULE}/${MODULE_VERSION}/pkg/tls \ - --input-dirs ${PROJECT_MODULE}/${MODULE_VERSION}/pkg/types \ - --output-package ${PROJECT_MODULE}/${MODULE_VERSION} -O zz_generated.deepcopy \ - --go-header-file=/go/src/${PROJECT_MODULE}/script/boilerplate.go.tmpl +kube::codegen::gen_helpers \ + --input-pkg-root "${PROJECT_MODULE}/pkg" \ + --output-base "$(dirname "${BASH_SOURCE[0]}")/../../../.." \ + --boilerplate "/go/src/${PROJECT_MODULE}/script/boilerplate.go.tmpl" -echo "Generating the CRD definitions for the documentation ..." -docker run --rm \ - -v "${CURRENT_DIR}:/go/src/${PROJECT_MODULE}" \ - -w "/go/src/${PROJECT_MODULE}" \ - "${IMAGE_NAME}" \ - controller-gen crd:crdVersions=v1 \ - paths={./pkg/provider/kubernetes/crd/traefikio/v1alpha1/...} \ - output:dir=./docs/content/reference/dynamic-configuration/ +kube::codegen::gen_client \ + --with-watch \ + --input-pkg-root "${PROJECT_MODULE}/${MODULE_VERSION}/pkg/provider/kubernetes/crd" \ + --output-pkg-root "${PROJECT_MODULE}/${MODULE_VERSION}/pkg/provider/kubernetes/crd/generated" \ + --output-base "$(dirname "${BASH_SOURCE[0]}")/../../../.." \ + --boilerplate "/go/src/${PROJECT_MODULE}/script/boilerplate.go.tmpl" -echo "Concatenate the CRD definitions for publication and integration tests ..." -cat "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/traefik.io_*.yaml > "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml -cp -f "${CURRENT_DIR}"/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml "${CURRENT_DIR}"/integration/fixtures/k8s/01-traefik-crd.yml - -cp -r "${CURRENT_DIR}/${MODULE_VERSION}"/* "${CURRENT_DIR}" -rm -rf "${CURRENT_DIR:?}/${MODULE_VERSION}" +rm -rf "/go/src/${PROJECT_MODULE}/${MODULE_VERSION}" diff --git a/script/codegen.Dockerfile b/script/codegen.Dockerfile index d946b2e16..ffb5430eb 100644 --- a/script/codegen.Dockerfile +++ b/script/codegen.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20 +FROM golang:1.21 ARG USER=$USER ARG UID=$UID @@ -13,10 +13,10 @@ RUN go install k8s.io/code-generator/cmd/client-gen@$KUBE_VERSION RUN go install k8s.io/code-generator/cmd/lister-gen@$KUBE_VERSION RUN go install k8s.io/code-generator/cmd/informer-gen@$KUBE_VERSION RUN go install k8s.io/code-generator/cmd/deepcopy-gen@$KUBE_VERSION -RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.2 +RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.13.0 RUN mkdir -p $GOPATH/src/k8s.io/code-generator RUN cp -R $GOPATH/pkg/mod/k8s.io/code-generator@$KUBE_VERSION/* $GOPATH/src/k8s.io/code-generator/ -RUN chmod +x $GOPATH/src/k8s.io/code-generator/generate-groups.sh +RUN chmod +x $GOPATH/src/k8s.io/code-generator/kube_codegen.sh WORKDIR $GOPATH/src/k8s.io/code-generator diff --git a/script/crossbinary-default b/script/crossbinary-default.sh similarity index 100% rename from script/crossbinary-default rename to script/crossbinary-default.sh diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 6a485662b..2ee1b473a 100644 --- a/script/gcg/traefik-bugfix.toml +++ b/script/gcg/traefik-bugfix.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example new bugfix v2.10.2 -CurrentRef = "v2.10" -PreviousRef = "v2.10.1" -BaseBranch = "v2.10" -FutureCurrentRefName = "v2.10.2" +# example new bugfix v2.11.1 +CurrentRef = "v2.11" +PreviousRef = "v2.11.0" +BaseBranch = "v2.11" +FutureCurrentRefName = "v2.11.1" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/gcg/traefik-final-release-part1.toml b/script/gcg/traefik-final-release-part1.toml index ee2bd3017..2e73738fb 100644 --- a/script/gcg/traefik-final-release-part1.toml +++ b/script/gcg/traefik-final-release-part1.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example final release of v2.10.0 -CurrentRef = "v2.10" -PreviousRef = "v2.10.0-rc1" -BaseBranch = "v2.10" -FutureCurrentRefName = "v2.10.0" +# example final release of v2.11.0 +CurrentRef = "v2.11" +PreviousRef = "v2.11.0-rc1" +BaseBranch = "v2.11" +FutureCurrentRefName = "v2.11.0" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/gcg/traefik-final-release-part2.toml b/script/gcg/traefik-final-release-part2.toml index 9f3abf827..bfd11e0f1 100644 --- a/script/gcg/traefik-final-release-part2.toml +++ b/script/gcg/traefik-final-release-part2.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example final release of v2.10.0 -CurrentRef = "v2.10.0-rc1" -PreviousRef = "v2.9.0-rc1" +# example final release of v2.11.0 +CurrentRef = "v2.11.0-rc1" +PreviousRef = "v2.10.0-rc1" BaseBranch = "master" -FutureCurrentRefName = "v2.10.0-rc1" +FutureCurrentRefName = "v2.11.0-rc1" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/gcg/traefik-rc-first.toml b/script/gcg/traefik-rc-first.toml index c7c2eab61..805c90500 100644 --- a/script/gcg/traefik-rc-first.toml +++ b/script/gcg/traefik-rc-first.toml @@ -6,7 +6,7 @@ FileName = "traefik_changelog.md" # example RC1 of v3.0.0-beta1 CurrentRef = "master" -PreviousRef = "v2.9.0-rc1" +PreviousRef = "v2.11.0-rc1" BaseBranch = "master" FutureCurrentRefName = "v3.0.0-beta1" diff --git a/script/gcg/traefik-rc-new.toml b/script/gcg/traefik-rc-new.toml index d46d2a2ff..d98437fc3 100644 --- a/script/gcg/traefik-rc-new.toml +++ b/script/gcg/traefik-rc-new.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example beta3 of v3.0.0 +# example beta5 of v3.0.0 CurrentRef = "v3.0" -PreviousRef = "v3.0.0-beta2" +PreviousRef = "v3.0.0-beta4" BaseBranch = "v3.0" -FutureCurrentRefName = "v3.0.0-beta3" +FutureCurrentRefName = "v3.0.0-beta5" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/generate b/script/generate deleted file mode 100755 index 423970b5a..000000000 --- a/script/generate +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -e - -go generate diff --git a/script/make.sh b/script/make.sh deleted file mode 100755 index 2f3870e74..000000000 --- a/script/make.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -set -e - -export GO111MODULE=on -export GOPROXY=https://proxy.golang.org - -# List of bundles to create when no argument is passed -DEFAULT_BUNDLES=( - generate - validate-lint - binary - - test-unit - test-integration -) - -SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd -P)" - -bundle() { - local bundle="$1"; shift - echo "---> Making bundle: $(basename "${bundle}") (in $SCRIPT_DIR)" - # shellcheck source=/dev/null - source "${SCRIPT_DIR}/${bundle}" -} - -if [ $# -lt 1 ]; then - bundles=${DEFAULT_BUNDLES[*]} -else - bundles=${*} -fi -# shellcheck disable=SC2048 -for bundle in ${bundles[*]}; do - bundle "${bundle}" - echo -done diff --git a/script/release-packages.sh b/script/release-packages.sh new file mode 100755 index 000000000..f442e12dd --- /dev/null +++ b/script/release-packages.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -e + +if [ -n "${SEMAPHORE_GIT_TAG_NAME}" ]; then + echo "Releasing packages..." +else + echo "Skipping release" + exit 0 +fi + +rm -rf dist + +for os in linux darwin windows freebsd openbsd; do + goreleaser release --snapshot --skip=publish -p 2 --timeout="90m" --config "$(go run ./internal/release "$os")" + go clean -cache +done + +cat dist/**/*_checksums.txt >> dist/traefik_${VERSION}_checksums.txt +rm dist/**/*_checksums.txt +tar cfz dist/traefik-${VERSION}.src.tar.gz \ + --exclude-vcs \ + --exclude .idea \ + --exclude .travis \ + --exclude .semaphoreci \ + --exclude .github \ + --exclude dist . + +chown -R $(id -u):$(id -g) dist/ diff --git a/script/test-integration b/script/test-integration deleted file mode 100755 index 13a4ab342..000000000 --- a/script/test-integration +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -e - -export DEST=. - -TESTFLAGS+=("-test.timeout=20m" -check.v) - -if [ -n "${VERBOSE}" ]; then - TESTFLAGS+=(-v) -elif [ -n "${VERBOSE_INTEGRATION}" ]; then - TESTFLAGS+=(-v) -fi - -cd integration -echo "Testing against..." -docker version - -# shellcheck disable=SC2086 -# shellcheck disable=SC2048 -CGO_ENABLED=0 go test -integration ${TESTFLAGS[*]} diff --git a/script/test-unit b/script/test-unit deleted file mode 100755 index 8f5ef0189..000000000 --- a/script/test-unit +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -e - -RED=$'\033[31m' -GREEN=$'\033[32m' -TEXTRESET=$'\033[0m' # reset the foreground colour - -# -failfast -timeout=5m -TESTFLAGS=(-cover "-coverprofile=cover.out" "${TESTFLAGS}") - -if [ -n "${VERBOSE}" ]; then - TESTFLAGS+=(-v) -elif [ -n "${VERBOSE_UNIT}" ]; then - TESTFLAGS+=(-v) -fi - -set +e - -# shellcheck disable=SC2086 -# shellcheck disable=SC2048 -go test ${TESTFLAGS[*]} ./pkg/... - -CODE=$? -if [ ${CODE} != 0 ]; then - echo "${RED}Tests failed [code ${CODE}].${TEXTRESET}" - exit ${CODE} -else - echo "${GREEN}Tests succeed.${TEXTRESET}" -fi diff --git a/script/validate-lint b/script/validate-lint deleted file mode 100755 index 22d339634..000000000 --- a/script/validate-lint +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -golangci-lint run \ No newline at end of file diff --git a/script/validate-misspell b/script/validate-misspell.sh similarity index 100% rename from script/validate-misspell rename to script/validate-misspell.sh diff --git a/script/validate-vendor b/script/validate-vendor.sh similarity index 100% rename from script/validate-vendor rename to script/validate-vendor.sh diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue index f0a78a23b..3e3229e4f 100644 --- a/webui/src/components/_commons/PanelMiddlewares.vue +++ b/webui/src/components/_commons/PanelMiddlewares.vue @@ -402,6 +402,20 @@ + + +
+
+
Access Control Allow Origin Regex
+ + {{ val }} + +
+
+