diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a6e2e15e3..1e02a3c0d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -51,12 +51,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 env: ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }} with: @@ -64,7 +64,7 @@ jobs: check-latest: true - name: Artifact webui - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: webui.tar.gz diff --git a/.github/workflows/check_doc.yaml b/.github/workflows/check_doc.yaml index 5fea9809c..994566896 100644 --- a/.github/workflows/check_doc.yaml +++ b/.github/workflows/check_doc.yaml @@ -16,7 +16,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 @@ -28,7 +28,7 @@ jobs: run: ./docs/scripts/lint.sh docs - name: Setup python - uses: actions/setup-python@v6 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: '3.12' cache: 'pip' @@ -41,7 +41,7 @@ jobs: mkdocs build --strict - name: Setup ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@e69dcf3ded5967f30d7ef595704928d91cdae930 # v1.285.0 with: ruby-version: '3.4' @@ -54,7 +54,7 @@ jobs: # Comes from https://github.com/gjtorikian/html-proofer?tab=readme-ov-file#caching-with-continuous-integration - name: Cache HTMLProofer - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: tmp/.htmlproofer key: ${{ runner.os }}-htmlproofer diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 30ab2221b..e93a0c545 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -28,17 +28,17 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 if: ${{ matrix.language == 'go' }} with: go-version-file: 'go.mod' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -52,7 +52,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -65,6 +65,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index 4daba089b..7f6b256d4 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -20,12 +20,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Login to DockerHub - uses: docker/login-action@v3 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/experimental.yaml b/.github/workflows/experimental.yaml index a56928fff..3c66efa58 100644 --- a/.github/workflows/experimental.yaml +++ b/.github/workflows/experimental.yaml @@ -23,12 +23,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 env: ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }} with: @@ -42,19 +42,19 @@ jobs: run: echo ${GITHUB_REF##*/} - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Artifact webui - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: webui.tar.gz diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 03d864762..6fa67b1e9 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -30,12 +30,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 env: # Ensure cache consistency on Linux, see https://github.com/actions/setup-go/pull/383 ImageOS: ${{ matrix.os }} @@ -44,7 +44,7 @@ jobs: check-latest: true - name: Artifact webui - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: webui.tar.gz @@ -63,7 +63,7 @@ jobs: echo "GORELEASER_CONFIG_FILE_PATH=$GORELEASER_CONFIG_FILE_PATH" >> $GITHUB_ENV - name: Build with goreleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: distribution: goreleaser # 'latest', 'nightly', or a semver @@ -71,7 +71,7 @@ jobs: args: release --clean --timeout="90m" --config "${{ env.GORELEASER_CONFIG_FILE_PATH }}" - name: Artifact binaries - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ${{ matrix.os }}-binaries path: | @@ -89,12 +89,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Artifact webui - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: webui.tar.gz @@ -111,7 +111,7 @@ jobs: echo "${TRAEFIKER_RSA}" | base64 --decode > ~/.ssh/traefiker_rsa - name: Download All Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: dist/ pattern: "*-binaries" diff --git a/.github/workflows/template-webui.yaml b/.github/workflows/template-webui.yaml index 16af7d1e4..47e44b1fe 100644 --- a/.github/workflows/template-webui.yaml +++ b/.github/workflows/template-webui.yaml @@ -10,7 +10,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 @@ -18,7 +18,7 @@ jobs: run: corepack enable - name: Setup node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: webui/.nvmrc cache: yarn @@ -41,7 +41,7 @@ jobs: tar czvf webui.tar.gz ./webui/static/ - name: Artifact webui - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: webui.tar.gz path: webui.tar.gz diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml index 5125d19c6..482e03ad8 100644 --- a/.github/workflows/test-integration.yaml +++ b/.github/workflows/test-integration.yaml @@ -20,12 +20,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true @@ -38,14 +38,14 @@ jobs: run: make binary-linux-amd64 - name: Save go cache build - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cache/go-build key: ${{ runner.os }}-go-build-cache-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }} - name: Artifact traefik binary - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: traefik path: ./dist/linux/amd64/traefik @@ -63,18 +63,18 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true - name: Download traefik binary - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: traefik path: ./dist/linux/amd64/ @@ -83,7 +83,7 @@ jobs: run: chmod +x ./dist/linux/amd64/traefik - name: Restore go cache build - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cache/go-build @@ -91,7 +91,7 @@ jobs: - name: Generate go test Slice id: test_split - uses: hashicorp-forge/go-test-split-action@v2.0.0 + uses: hashicorp-forge/go-test-split-action@720a77701f0d1b2973126510492a6aad11ed0d5a # v2.0.0 with: packages: ./integration total: ${{ matrix.parallel }} diff --git a/.github/workflows/test-unit.yaml b/.github/workflows/test-unit.yaml index d60e359f0..b4112e098 100644 --- a/.github/workflows/test-unit.yaml +++ b/.github/workflows/test-unit.yaml @@ -20,12 +20,12 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true @@ -46,12 +46,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true @@ -65,7 +65,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 @@ -73,7 +73,7 @@ jobs: run: corepack enable - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: webui/.nvmrc cache: 'yarn' diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index e81b4aa99..9e21b67ba 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -17,18 +17,18 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@v7 + uses: golangci/golangci-lint-action@9fae48acfc02a90574d7c304a1758ef9895495fa # v7.0.1 with: version: "${{ env.GOLANGCI_LINT_VERSION }}" @@ -37,12 +37,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true @@ -58,12 +58,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ env.GO_VERSION }} check-latest: true diff --git a/docs/Makefile b/docs/Makefile index 501cbdbad..9a2e41ff3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -21,7 +21,7 @@ docs: docs-clean docs-image docs-lint docs-build docs-verify # Writer Mode: build and serve docs on http://localhost:8000 with livereload .PHONY: docs-serve docs-serve: docs-image - docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) mkdocs serve + docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) mkdocs serve -a 0.0.0.0:8000 ## Pull image for doc building .PHONY: docs-pull-images diff --git a/docs/requirements.txt b/docs/requirements.txt index f0d99127c..7801cf737 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -8,7 +8,7 @@ click==8.1.7 colorama==0.4.6 ghp-import==2.1.0 importlib_metadata==7.1.0 -Jinja2==3.1.3 +Jinja2==3.1.6 Markdown==3.3.6 MarkupSafe==2.1.5 mergedeep==1.3.4 @@ -21,4 +21,4 @@ PyYAML==6.0.1 pyyaml_env_tag==0.1 six==1.16.0 watchdog==4.0.0 -zipp==3.18.1 +zipp==3.19.1 diff --git a/go.mod b/go.mod index 0582836fd..79a1e9a94 100644 --- a/go.mod +++ b/go.mod @@ -180,7 +180,7 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect - github.com/containerd/containerd v1.7.23 // indirect + github.com/containerd/containerd v1.7.29 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect diff --git a/go.sum b/go.sum index 3bad3f623..00b764cce 100644 --- a/go.sum +++ b/go.sum @@ -297,8 +297,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= -github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= +github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= +github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= diff --git a/pkg/observability/metrics/prometheus_test.go b/pkg/observability/metrics/prometheus_test.go index f3b8c9f49..b7e052de1 100644 --- a/pkg/observability/metrics/prometheus_test.go +++ b/pkg/observability/metrics/prometheus_test.go @@ -413,13 +413,13 @@ func TestPrometheusMetricRemoval(t *testing.T) { th.WithRouter("foo@providerName", th.WithServiceName("bar")), th.WithRouter("router2", th.WithServiceName("bar@providerName")), ), - th.WithLoadBalancerServices( - th.WithService("bar@providerName", th.WithServers( + th.WithServices( + th.WithService("bar@providerName", th.WithServiceServersLoadBalancer(th.WithServers( th.WithServer("http://localhost:9000"), th.WithServer("http://localhost:9999"), th.WithServer("http://localhost:9998"), - )), - th.WithService("service1", th.WithServers(th.WithServer("http://localhost:9000"))), + ))), + th.WithService("service1", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))), ), ), } @@ -429,8 +429,8 @@ func TestPrometheusMetricRemoval(t *testing.T) { th.WithRouters( th.WithRouter("foo@providerName", th.WithServiceName("bar")), ), - th.WithLoadBalancerServices( - th.WithService("bar@providerName", th.WithServers(th.WithServer("http://localhost:9000"))), + th.WithServices( + th.WithService("bar@providerName", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))), ), ), } @@ -500,8 +500,8 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) { conf1 := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithLoadBalancerServices( - th.WithService("service1", th.WithServers(th.WithServer("http://localhost:9000"))), + th.WithServices( + th.WithService("service1", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))), ), ), } @@ -512,8 +512,8 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) { conf3 := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithLoadBalancerServices( - th.WithService("service1", th.WithServers(th.WithServer("http://localhost:9001"))), + th.WithServices( + th.WithService("service1", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9001")))), ), ), } @@ -539,8 +539,8 @@ func TestPrometheusRemovedMetricsReset(t *testing.T) { conf1 := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithLoadBalancerServices(th.WithService("service", - th.WithServers(th.WithServer("http://localhost:9000"))), + th.WithServices( + th.WithService("service", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))), ), ), } diff --git a/pkg/server/configurationwatcher_test.go b/pkg/server/configurationwatcher_test.go index 99a196008..66812feac 100644 --- a/pkg/server/configurationwatcher_test.go +++ b/pkg/server/configurationwatcher_test.go @@ -87,7 +87,7 @@ func TestNewConfigurationWatcher(t *testing.T) { th.WithServiceName("scv"), th.WithObservability())), th.WithMiddlewares(), - th.WithLoadBalancerServices(), + th.WithServices(), ), TCP: &dynamic.TCPConfiguration{ Routers: map[string]*dynamic.TCPRouter{}, @@ -126,7 +126,9 @@ func TestWaitForRequiredProvider(t *testing.T) { config := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } @@ -170,14 +172,18 @@ func TestIgnoreTransientConfiguration(t *testing.T) { config := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } expectedConfig := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())), - th.WithLoadBalancerServices(th.WithService("bar@mock")), + th.WithServices( + th.WithService("bar@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -202,7 +208,9 @@ func TestIgnoreTransientConfiguration(t *testing.T) { expectedConfig3 := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())), - th.WithLoadBalancerServices(th.WithService("bar-config3@mock")), + th.WithServices( + th.WithService("bar-config3@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -227,14 +235,18 @@ func TestIgnoreTransientConfiguration(t *testing.T) { config2 := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("baz", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("toto")), + th.WithServices( + th.WithService("toto", th.WithServiceServersLoadBalancer()), + ), ), } config3 := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar-config3")), + th.WithServices( + th.WithService("bar-config3", th.WithServiceServersLoadBalancer()), + ), ), } watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "") @@ -318,7 +330,9 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { Configuration: &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), }, }) @@ -378,7 +392,9 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { Configuration: &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), }, } @@ -410,14 +426,18 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } transientConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bad")), + th.WithServices( + th.WithService("bad", th.WithServiceServersLoadBalancer()), + ), ), } @@ -449,7 +469,9 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())), - th.WithLoadBalancerServices(th.WithService("bar@mock")), + th.WithServices( + th.WithService("bar@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -480,14 +502,18 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } transientConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bad")), + th.WithServices( + th.WithService("bad", th.WithServiceServersLoadBalancer()), + ), ), } @@ -540,7 +566,9 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())), - th.WithLoadBalancerServices(th.WithService("bar@mock")), + th.WithServices( + th.WithService("bar@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -581,7 +609,9 @@ func TestApplyConfigUnderStress(t *testing.T) { case watcher.allProvidersConfigs <- dynamic.Message{ProviderName: "mock", Configuration: &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), }}: } @@ -616,28 +646,36 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } transientConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bad")), + th.WithServices( + th.WithService("bad", th.WithServiceServersLoadBalancer()), + ), ), } transientConfiguration2 := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("bad2", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bad2")), + th.WithServices( + th.WithService("bad2", th.WithServiceServersLoadBalancer()), + ), ), } finalConfiguration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("final", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("final")), + th.WithServices( + th.WithService("final", th.WithServiceServersLoadBalancer()), + ), ), } @@ -676,7 +714,9 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"), th.WithObservability())), - th.WithLoadBalancerServices(th.WithService("final@mock")), + th.WithServices( + th.WithService("final@mock", th.WithServiceServersLoadBalancer()), + ), th.WithMiddlewares(), ), TCP: &dynamic.TCPConfiguration{ @@ -709,7 +749,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ), } @@ -742,9 +784,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability()), th.WithRouter("foo@mock2", th.WithEntryPoints("ep"), th.WithObservability()), ), - th.WithLoadBalancerServices( - th.WithService("bar@mock"), - th.WithService("bar@mock2"), + th.WithServices( + th.WithService("bar@mock", th.WithServiceServersLoadBalancer()), + th.WithService("bar@mock2", th.WithServiceServersLoadBalancer()), ), th.WithMiddlewares(), ), diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index 624a3c9c9..42d83ddd4 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -6,8 +6,6 @@ import ( "fmt" "net/http" "reflect" - "slices" - "strings" "github.com/containous/alice" "github.com/rs/zerolog/log" @@ -38,12 +36,7 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares/stripprefix" "github.com/traefik/traefik/v3/pkg/middlewares/stripprefixregex" "github.com/traefik/traefik/v3/pkg/server/provider" -) - -type middlewareStackType int - -const ( - middlewareStackKey middlewareStackType = iota + "github.com/traefik/traefik/v3/pkg/server/recursion" ) // Builder the middleware builder. @@ -75,7 +68,7 @@ func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.C } var err error - if constructorContext, err = checkRecursion(constructorContext, middlewareName); err != nil { + if constructorContext, err = recursion.CheckRecursion(constructorContext, "middleware", middlewareName); err != nil { b.configs[middlewareName].AddError(err, true) return nil, err } @@ -98,17 +91,6 @@ func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.C return &chain } -func checkRecursion(ctx context.Context, middlewareName string) (context.Context, error) { - currentStack, ok := ctx.Value(middlewareStackKey).([]string) - if !ok { - currentStack = []string{} - } - if slices.Contains(currentStack, middlewareName) { - return ctx, fmt.Errorf("could not instantiate middleware %s: recursion detected in %s", middlewareName, strings.Join(append(currentStack, middlewareName), "->")) - } - return context.WithValue(ctx, middlewareStackKey, append(currentStack, middlewareName)), nil -} - // it is the responsibility of the caller to make sure that b.configs[middlewareName].Middleware exists. func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (alice.Constructor, error) { config := b.configs[middlewareName] diff --git a/pkg/server/middleware/middlewares_test.go b/pkg/server/middleware/middlewares_test.go index 92cebc050..facaa716c 100644 --- a/pkg/server/middleware/middlewares_test.go +++ b/pkg/server/middleware/middlewares_test.go @@ -172,7 +172,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m1: recursion detected in m1->m2->m3->m1"), + expectedError: errors.New("could not instantiate middleware m1: recursion detected in middleware:m1->middleware:m2->middleware:m3->middleware:m1"), }, { desc: "Detects recursion in Middleware chain", @@ -197,9 +197,10 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m1@provider: recursion detected in m1@provider->m2@provider2->m3@provider->m1@provider"), + expectedError: errors.New("could not instantiate middleware m1@provider: recursion detected in middleware:m1@provider->middleware:m2@provider2->middleware:m3@provider->middleware:m1@provider"), }, { + desc: "Detects recursion in Middleware chain", buildChain: []string{"ok", "m0"}, configuration: map[string]*dynamic.Middleware{ "ok": { @@ -211,7 +212,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m0: recursion detected in m0->m0"), + expectedError: errors.New("could not instantiate middleware m0: recursion detected in middleware:m0->middleware:m0"), }, { desc: "Detects MiddlewareChain that references a Chain that references a Chain with a missing middleware", @@ -238,7 +239,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m2: recursion detected in m0->m1->m2->m3->m2"), + expectedError: errors.New("could not instantiate middleware m2: recursion detected in middleware:m0->middleware:m1->middleware:m2->middleware:m3->middleware:m2"), }, { desc: "--", @@ -250,7 +251,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { }, }, }, - expectedError: errors.New("could not instantiate middleware m0: recursion detected in m0->m0"), + expectedError: errors.New("could not instantiate middleware m0: recursion detected in middleware:m0->middleware:m0"), }, } diff --git a/pkg/server/recursion/recursion.go b/pkg/server/recursion/recursion.go new file mode 100644 index 000000000..40998a37c --- /dev/null +++ b/pkg/server/recursion/recursion.go @@ -0,0 +1,26 @@ +package recursion + +import ( + "context" + "fmt" + "slices" + "strings" +) + +type stackType int + +const ( + stackKey stackType = iota +) + +func CheckRecursion(ctx context.Context, itemType, itemName string) (context.Context, error) { + currentStack, ok := ctx.Value(stackKey).([]string) + if !ok { + currentStack = []string{} + } + name := itemType + ":" + itemName + if slices.Contains(currentStack, name) { + return ctx, fmt.Errorf("could not instantiate %s %s: recursion detected in %s", itemType, itemName, strings.Join(append(currentStack, name), "->")) + } + return context.WithValue(ctx, stackKey, append(currentStack, name)), nil +} diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go index ba77f5e31..a287c22ee 100644 --- a/pkg/server/routerfactory_test.go +++ b/pkg/server/routerfactory_test.go @@ -46,8 +46,8 @@ func TestReuseService(t *testing.T) { th.WithMiddlewares(th.WithMiddleware("basicauth", th.WithBasicAuth(&dynamic.BasicAuth{Users: []string{"foo:bar"}}), )), - th.WithLoadBalancerServices(th.WithService("bar", - th.WithServers(th.WithServer(testServer.URL))), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer(testServer.URL)))), ), ) @@ -98,8 +98,8 @@ func TestServerResponseEmptyBackend(t *testing.T) { th.WithServiceName("bar"), th.WithRule(routeRule)), ), - th.WithLoadBalancerServices(th.WithService("bar", - th.WithServers(th.WithServer(testServerURL))), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer(testServerURL)))), ), ) }, @@ -121,7 +121,9 @@ func TestServerResponseEmptyBackend(t *testing.T) { th.WithServiceName("bar"), th.WithRule(routeRule)), ), - th.WithLoadBalancerServices(th.WithService("bar")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer()), + ), ) }, expectedStatusCode: http.StatusServiceUnavailable, @@ -135,27 +137,13 @@ func TestServerResponseEmptyBackend(t *testing.T) { th.WithServiceName("bar"), th.WithRule(routeRule)), ), - th.WithLoadBalancerServices(th.WithService("bar", - th.WithSticky("test")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithSticky("test"))), ), ) }, expectedStatusCode: http.StatusServiceUnavailable, }, - { - desc: "Empty Backend LB", - config: func(testServerURL string) *dynamic.HTTPConfiguration { - return th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo", - th.WithEntryPoints("web"), - th.WithServiceName("bar"), - th.WithRule(routeRule)), - ), - th.WithLoadBalancerServices(th.WithService("bar")), - ) - }, - expectedStatusCode: http.StatusServiceUnavailable, - }, { desc: "Empty Backend LB Sticky", config: func(testServerURL string) *dynamic.HTTPConfiguration { @@ -165,8 +153,8 @@ func TestServerResponseEmptyBackend(t *testing.T) { th.WithServiceName("bar"), th.WithRule(routeRule)), ), - th.WithLoadBalancerServices(th.WithService("bar", - th.WithSticky("test")), + th.WithServices( + th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithSticky("test"))), ), ) }, @@ -256,6 +244,61 @@ func TestInternalServices(t *testing.T) { assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code") } +func TestRecursionService(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + defer testServer.Close() + + staticConfig := static.Configuration{ + EntryPoints: map[string]*static.EntryPoint{ + "web": {}, + }, + } + + dynamicConfigs := th.BuildConfiguration( + th.WithRouters( + th.WithRouter("foo@provider1", + th.WithEntryPoints("web"), + th.WithServiceName("bar"), + th.WithRule("Path(`/ok`)")), + ), + th.WithMiddlewares(th.WithMiddleware("customerror", + th.WithErrorPage(&dynamic.ErrorPage{Service: "bar"}), + )), + th.WithServices( + th.WithService("bar@provider1", th.WithServiceWRR(th.WithWRRServices(th.WithWRRService("foo")))), + th.WithService("foo@provider1", th.WithServiceWRR(th.WithWRRServices(th.WithWRRService("bar")))), + ), + ) + + transportManager := service.NewTransportManager(nil) + transportManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) + + managerFactory := service.NewManagerFactory(staticConfig, nil, nil, transportManager, nil, nil) + tlsManager := tls.NewManager(nil) + + dialerManager := tcp.NewDialerManager(nil) + dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) + factory, err := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager) + require.NoError(t, err) + + rtConf := runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}) + entryPointsHandlers, _ := factory.CreateRouters(rtConf) + + // Test that the /ok path returns a status 404. + responseRecorderOk := &httptest.ResponseRecorder{} + requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/ok", nil) + entryPointsHandlers["web"].GetHTTPHandler().ServeHTTP(responseRecorderOk, requestOk) + + assert.Equal(t, http.StatusNotFound, responseRecorderOk.Result().StatusCode, "status code") + + require.NotNil(t, rtConf.Routers["foo@provider1"]) + assert.Contains(t, rtConf.Routers["foo@provider1"].Err, "building HTTP service \"foo\": building HTTP service \"bar\": could not instantiate service bar@provider1: recursion detected in service:bar@provider1->service:foo@provider1->service:bar@provider1") + require.NotNil(t, rtConf.Services["bar@provider1"]) + assert.Contains(t, rtConf.Services["bar@provider1"].Err, "could not instantiate service bar@provider1: recursion detected in service:bar@provider1->service:foo@provider1->service:bar@provider1") +} + type proxyBuilderMock struct{} func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) { diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 40b1ae474..23d1daf6d 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -27,6 +27,7 @@ import ( "github.com/traefik/traefik/v3/pkg/server/cookie" "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/provider" + "github.com/traefik/traefik/v3/pkg/server/recursion" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/failover" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/hrw" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/leasttime" @@ -122,6 +123,12 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H return nil, err } + var errRecursion error + if ctx, errRecursion = recursion.CheckRecursion(ctx, "service", serviceName); errRecursion != nil { + conf.AddError(errRecursion, true) + return nil, errRecursion + } + var lb http.Handler switch { @@ -319,7 +326,7 @@ func (m *Manager) getServiceHandler(ctx context.Context, service dynamic.WRRServ svcHandler, err := m.BuildHTTP(ctx, service.Name) if err != nil { - return nil, fmt.Errorf("building HTTP service: %w", err) + return nil, fmt.Errorf("building HTTP service %q: %w", service.Name, err) } if service.Headers != nil { diff --git a/pkg/testhelpers/config.go b/pkg/testhelpers/config.go index afd298599..344dab19a 100644 --- a/pkg/testhelpers/config.go +++ b/pkg/testhelpers/config.go @@ -66,32 +66,75 @@ func WithObservability() func(*dynamic.Router) { } } -// WithLoadBalancerServices is a helper to create a configuration. -func WithLoadBalancerServices(opts ...func(service *dynamic.ServersLoadBalancer) string) func(*dynamic.HTTPConfiguration) { +// WithServices is a helper to create a configuration. +func WithServices(opts ...func(service *dynamic.Service) string) func(*dynamic.HTTPConfiguration) { return func(c *dynamic.HTTPConfiguration) { c.Services = make(map[string]*dynamic.Service) for _, opt := range opts { - b := &dynamic.ServersLoadBalancer{} - b.SetDefaults() - + b := &dynamic.Service{} name := opt(b) - c.Services[name] = &dynamic.Service{ - LoadBalancer: b, - } + c.Services[name] = b } } } // WithService is a helper to create a configuration. -func WithService(name string, opts ...func(*dynamic.ServersLoadBalancer)) func(*dynamic.ServersLoadBalancer) string { - return func(r *dynamic.ServersLoadBalancer) string { +func WithService(name string, opts ...func(*dynamic.Service)) func(*dynamic.Service) string { + return func(s *dynamic.Service) string { for _, opt := range opts { - opt(r) + opt(s) } + return name } } +func WithServiceServersLoadBalancer(opts ...func(*dynamic.ServersLoadBalancer)) func(*dynamic.Service) { + return func(s *dynamic.Service) { + b := &dynamic.ServersLoadBalancer{} + b.SetDefaults() + + for _, opt := range opts { + opt(b) + } + + s.LoadBalancer = b + } +} + +func WithServiceWRR(opts ...func(*dynamic.WeightedRoundRobin)) func(*dynamic.Service) { + return func(s *dynamic.Service) { + b := &dynamic.WeightedRoundRobin{} + + for _, opt := range opts { + opt(b) + } + + s.Weighted = b + } +} + +// WithWRRServices is a helper to create a configuration. +func WithWRRServices(opts ...func(*dynamic.WRRService)) func(*dynamic.WeightedRoundRobin) { + return func(b *dynamic.WeightedRoundRobin) { + for _, opt := range opts { + service := dynamic.WRRService{} + opt(&service) + b.Services = append(b.Services, service) + } + } +} + +// WithWRRService is a helper to create a configuration. +func WithWRRService(name string, opts ...func(*dynamic.WRRService)) func(*dynamic.WRRService) { + return func(s *dynamic.WRRService) { + for _, opt := range opts { + opt(s) + } + s.Name = name + } +} + // WithMiddlewares is a helper to create a configuration. func WithMiddlewares(opts ...func(*dynamic.Middleware) string) func(*dynamic.HTTPConfiguration) { return func(c *dynamic.HTTPConfiguration) { @@ -121,6 +164,13 @@ func WithBasicAuth(auth *dynamic.BasicAuth) func(*dynamic.Middleware) { } } +// WithErrorPage is a helper to create a configuration. +func WithErrorPage(errorPage *dynamic.ErrorPage) func(*dynamic.Middleware) { + return func(r *dynamic.Middleware) { + r.Errors = errorPage + } +} + // WithEntryPoints is a helper to create a configuration. func WithEntryPoints(eps ...string) func(*dynamic.Router) { return func(f *dynamic.Router) {