From c69246d93edf139a29267e41cb2e1a4d0cfe085c Mon Sep 17 00:00:00 2001 From: "Arthur K." Date: Wed, 11 Jun 2025 15:04:44 +0300 Subject: [PATCH 1/6] chore: remove github for local deployments --- .github/FUNDING.yml | 3 - .github/ISSUE_TEMPLATE.md | 77 ------------ .github/ISSUE_TEMPLATE/bug_report.yml | 82 ------------ .github/ISSUE_TEMPLATE/config.yml | 8 -- .github/ISSUE_TEMPLATE/feature-request.yml | 33 ----- .github/PULL_REQUEST_TEMPLATE.md | 37 ------ .github/PULL_REQUEST_TEMPLATE/mergeback.md | 7 -- .github/PULL_REQUEST_TEMPLATE/release.md | 7 -- .github/workflows/build.yaml | 81 ------------ .github/workflows/check_doc.yml | 25 ---- .github/workflows/codeql.yml | 70 ----------- .github/workflows/documentation.yml | 52 -------- .github/workflows/experimental.yaml | 70 ----------- .github/workflows/release.yaml | 138 --------------------- .github/workflows/sync-docker-images.yaml | 26 ---- .github/workflows/template-webui.yaml | 37 ------ .github/workflows/test-conformance.yaml | 39 ------ .github/workflows/test-integration.yaml | 78 ------------ .github/workflows/test-unit.yaml | 57 --------- .github/workflows/validate.yaml | 87 ------------- Dockerfile | 1 + 21 files changed, 1 insertion(+), 1014 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/mergeback.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/release.md delete mode 100644 .github/workflows/build.yaml delete mode 100644 .github/workflows/check_doc.yml delete mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/documentation.yml delete mode 100644 .github/workflows/experimental.yaml delete mode 100644 .github/workflows/release.yaml delete mode 100644 .github/workflows/sync-docker-images.yaml delete mode 100644 .github/workflows/template-webui.yaml delete mode 100644 .github/workflows/test-conformance.yaml delete mode 100644 .github/workflows/test-integration.yaml delete mode 100644 .github/workflows/test-unit.yaml delete mode 100644 .github/workflows/validate.yaml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index b920e1793..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: traefik diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 8d124b341..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,77 +0,0 @@ - - -### Do you want to request a *feature* or report a *bug*? - - - -Bug - - - -### What did you do? - - - -### What did you expect to see? - - - -### What did you see instead? - - - -### Output of `traefik version`: (_What version of Traefik are you using?_) - - - -``` -(paste your output here) -``` - -### What is your environment & configuration (arguments, toml, provider, platform, ...)? - -```toml -# (paste your configuration here) -``` - - - - -### If applicable, please paste the log output in DEBUG level (`--log.level=DEBUG` switch) - -``` -(paste your output here) -``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index ce21d35ee..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Bug Report (Traefik) -description: Create a report to help us improve. -body: - - type: checkboxes - id: terms - attributes: - label: Welcome! - description: | - The issue tracker is for reporting bugs and feature requests only. - For end-user related support questions, please use the [Traefik community forum](https://community.traefik.io/). - - All new/updated issues are triaged regularly by the maintainers. - All issues closed by a bot are subsequently double-checked by the maintainers. - - DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS. - - options: - - label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any. - required: true - - label: Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any. - required: true - - - type: textarea - attributes: - label: What did you do? - description: | - How to write a good bug report? - - - Respect the issue template as much as possible. - - The title should be short and descriptive. - - Explain the conditions which led you to report this issue: the context. - - The context should lead to something, an idea or a problem that you’re facing. - - Remain clear and concise. - - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown) - placeholder: What did you do? - validations: - required: true - - - type: textarea - attributes: - label: What did you see instead? - placeholder: What did you see instead? - validations: - required: true - - - type: textarea - attributes: - label: What version of Traefik are you using? - description: | - `latest` is not considered as a valid version. - - Output of `traefik version`. - - For the Traefik Docker image (`docker run [IMAGE] version`), example: - ```console - $ docker run traefik version - ``` - placeholder: Paste your output here. - validations: - required: true - - - type: textarea - attributes: - label: What is your environment & configuration? - description: arguments, toml, provider, platform, ... - placeholder: Add information here. - value: | - ```yaml - # (paste your configuration here) - ``` - - Add more configuration information here. - validations: - required: true - - - type: textarea - attributes: - label: If applicable, please paste the log output in DEBUG level - description: "`--log.level=DEBUG` switch." - placeholder: Paste your output here. - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 80c8c86b5..000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Traefik Community Support - url: https://community.traefik.io/ - about: If you have a question, or are looking for advice, please post on our Discuss forum! The community loves to chime in to help. Happy Coding! - - name: Traefik Helm Chart Issues - url: https://github.com/traefik/traefik-helm-chart - about: Are you submitting an issue or feature enhancement for the Traefik helm chart? Please post in the traefik-helm-chart GitHub Issues. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index 5a092594d..000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Feature Request (Traefik) -description: Suggest an idea for this project. -body: - - type: checkboxes - id: terms - attributes: - label: Welcome! - description: | - The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please refer to one of the following: - - the Traefik community forum: https://community.traefik.io/ - - DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS. - options: - - label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any. - required: true - - label: Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any. - required: true - - - type: textarea - attributes: - label: What did you expect to see? - description: | - How to write a good issue? - - - Respect the issue template as much as possible. - - The title should be short and descriptive. - - Explain the conditions which led you to report this issue: the context. - - The context should lead to something, an idea or a problem that you’re facing. - - Remain clear and concise. - - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown) - placeholder: What did you expect to see? - validations: - required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 2594684f6..000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,37 +0,0 @@ - - -### What does this PR do? - - - - -### Motivation - - - - -### More - -- [ ] Added/updated tests -- [ ] Added/updated documentation - -### Additional Notes - - diff --git a/.github/PULL_REQUEST_TEMPLATE/mergeback.md b/.github/PULL_REQUEST_TEMPLATE/mergeback.md deleted file mode 100644 index dfc856011..000000000 --- a/.github/PULL_REQUEST_TEMPLATE/mergeback.md +++ /dev/null @@ -1,7 +0,0 @@ -### What does this PR do? - -Merge v{{.Version}} into master - -### Motivation - -Be sync. diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md deleted file mode 100644 index 226a85a73..000000000 --- a/.github/PULL_REQUEST_TEMPLATE/release.md +++ /dev/null @@ -1,7 +0,0 @@ -### What does this PR do? - -Prepare release v{{.Version}}. - -### Motivation - -Create a new release. diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 0122f29e6..000000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,81 +0,0 @@ -name: Build Binaries - -on: - pull_request: - branches: - - '*' - paths-ignore: - - 'docs/**' - - '**.md' - - 'script/gcg/**' - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - -jobs: - - build-webui: - uses: ./.github/workflows/template-webui.yaml - - build: - runs-on: ubuntu-latest - - strategy: - matrix: - os: [ darwin, freebsd, linux, openbsd, windows ] - arch: [ amd64, arm64 ] - include: - - os: freebsd - arch: 386 - - os: linux - arch: 386 - - os: linux - arch: arm - goarm: 6 - - os: linux - arch: arm - goarm: 7 - - os: linux - arch: ppc64le - - os: linux - arch: riscv64 - - os: linux - arch: s390x - - os: openbsd - arch: 386 - - os: windows - arch: 386 - needs: - - build-webui - - 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 - env: - ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }} - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - - - name: Artifact webui - uses: actions/download-artifact@v4 - with: - name: webui.tar.gz - - - name: Untar webui - run: | - tar xvf webui.tar.gz - rm webui.tar.gz - - - name: Build - env: - GOOS: ${{ matrix.os }} - GOARCH: ${{ matrix.arch }} - GOARM: ${{ matrix.goarm }} - run: make binary diff --git a/.github/workflows/check_doc.yml b/.github/workflows/check_doc.yml deleted file mode 100644 index c5fbddcec..000000000 --- a/.github/workflows/check_doc.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Check Documentation - -on: - pull_request: - branches: - - '*' - -jobs: - - docs: - name: Check, verify and build documentation - runs-on: ubuntu-latest - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check documentation - run: make docs-pull-images docs - env: - # These variables are not passed to workflows that are triggered by a pull request from a fork. - DOCS_VERIFY_SKIP: ${{ vars.DOCS_VERIFY_SKIP }} - DOCS_LINT_SKIP: ${{ vars.DOCS_LINT_SKIP }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index f921d7789..000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: - - master - - v* - schedule: - - cron: '11 22 * * 1' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'javascript', 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Use only 'java' to analyze code written in Java, Kotlin or both - # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: setup go - uses: actions/setup-go@v5 - 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 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # 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 - - # ℹ️ 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 - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml deleted file mode 100644 index adfdcc74e..000000000 --- a/.github/workflows/documentation.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Build and Publish Documentation - -on: - push: - branches: - - master - - v* - -env: - STRUCTOR_VERSION: v1.13.2 - MIXTUS_VERSION: v0.4.1 - -jobs: - - docs: - name: Doc Process - runs-on: ubuntu-latest - if: github.repository == 'traefik/traefik' - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Install Structor ${{ env.STRUCTOR_VERSION }} - run: curl -sSfL https://raw.githubusercontent.com/traefik/structor/master/godownloader.sh | sh -s -- -b $HOME/bin ${STRUCTOR_VERSION} - - - name: Install Seo-doc - run: curl -sSfL https://raw.githubusercontent.com/traefik/seo-doc/master/godownloader.sh | sh -s -- -b "${HOME}/bin" - - - name: Install Mixtus ${{ env.MIXTUS_VERSION }} - run: curl -sSfL https://raw.githubusercontent.com/traefik/mixtus/master/godownloader.sh | sh -s -- -b $HOME/bin ${MIXTUS_VERSION} - - - name: Build documentation - run: $HOME/bin/structor -o traefik -r traefik --dockerfile-url="https://raw.githubusercontent.com/traefik/traefik/v1.7/docs.Dockerfile" --menu.js-url="https://raw.githubusercontent.com/traefik/structor/master/traefik-menu.js.gotmpl" --rqts-url="https://raw.githubusercontent.com/traefik/structor/master/requirements-override.txt" --force-edit-url --exp-branch=master --debug - env: - STRUCTOR_LATEST_TAG: ${{ vars.STRUCTOR_LATEST_TAG }} - - - name: Apply seo - run: $HOME/bin/seo -path=./site -product=traefik - - - name: Publish documentation - run: $HOME/bin/mixtus --dst-doc-path="./traefik" --dst-owner=traefik --dst-repo-name=doc --git-user-email="30906710+traefiker@users.noreply.github.com" --git-user-name=traefiker --src-doc-path="./site" --src-owner=traefik --src-repo-name=traefik - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }} diff --git a/.github/workflows/experimental.yaml b/.github/workflows/experimental.yaml deleted file mode 100644 index 76959bbd5..000000000 --- a/.github/workflows/experimental.yaml +++ /dev/null @@ -1,70 +0,0 @@ -name: Build experimental image on branch - -on: - push: - branches: - - master - - v* - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - -jobs: - - build-webui: - if: github.repository == 'traefik/traefik' - uses: ./.github/workflows/template-webui.yaml - - experimental: - if: github.repository == 'traefik/traefik' - name: Build experimental image on branch - runs-on: ubuntu-latest - - 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 - env: - ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }} - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - - - name: Build - run: make generate binary - - - name: Branch name - run: echo ${GITHUB_REF##*/} - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Artifact webui - uses: actions/download-artifact@v4 - with: - name: webui.tar.gz - - - name: Untar webui - run: | - tar xvf webui.tar.gz - rm webui.tar.gz - - - name: Build docker experimental image - env: - DOCKER_BUILDX_ARGS: "--push" - run: | - make multi-arch-image-experimental-${GITHUB_REF##*/} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 03a94ec91..000000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,138 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*.*.*' - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - VERSION: ${{ github.ref_name }} - TRAEFIKER_EMAIL: "traefiker@traefik.io" - CODENAME: chaource - -jobs: - - build-webui: - if: github.ref_type == 'tag' && github.repository == 'traefik/traefik' - uses: ./.github/workflows/template-webui.yaml - - build: - if: github.ref_type == 'tag' && github.repository == 'traefik/traefik' - runs-on: ubuntu-latest - - strategy: - matrix: - os: [ linux-amd64, linux-386, linux-arm, linux-arm64, linux-ppc64le, linux-s390x, linux-riscv64, darwin, windows-amd64, windows-arm64, windows-386, freebsd, openbsd ] - needs: - - build-webui - - 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 - env: - # Ensure cache consistency on Linux, see https://github.com/actions/setup-go/pull/383 - ImageOS: ${{ matrix.os }} - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - - - name: Artifact webui - uses: actions/download-artifact@v4 - with: - name: webui.tar.gz - - - name: Untar webui - run: | - tar xvf webui.tar.gz - rm webui.tar.gz - - - name: Go generate - run: go generate - - - - name: Generate goreleaser file - run: | - GORELEASER_CONFIG_FILE_PATH=$(go run ./internal/release "${{ matrix.os }}") - echo "GORELEASER_CONFIG_FILE_PATH=$GORELEASER_CONFIG_FILE_PATH" >> $GITHUB_ENV - - - name: Build with goreleaser - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser - # 'latest', 'nightly', or a semver - version: '~> v2' - args: release --clean --timeout="90m" --config "${{ env.GORELEASER_CONFIG_FILE_PATH }}" - - - name: Artifact binaries - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.os }}-binaries - path: | - dist/**/*_checksums.txt - dist/**/*.tar.gz - dist/**/*.zip - retention-days: 1 - - release: - if: github.ref_type == 'tag' && github.repository == 'traefik/traefik' - runs-on: ubuntu-latest - - needs: - - build - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Artifact webui - uses: actions/download-artifact@v4 - with: - name: webui.tar.gz - - - name: Untar webui - run: | - tar xvf webui.tar.gz - rm webui.tar.gz - - - name: Retrieve the secret and decode it to a file - env: - TRAEFIKER_RSA: ${{ secrets.TRAEFIKER_RSA }} - run: | - mkdir -p ~/.ssh - echo "${TRAEFIKER_RSA}" | base64 --decode > ~/.ssh/traefiker_rsa - - - name: Download All Artifacts - uses: actions/download-artifact@v4 - with: - path: dist/ - pattern: "*-binaries" - merge-multiple: true - - - name: Publish Release - env: - GH_TOKEN: ${{ github.token }} - run: | - 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/ - gh release create ${VERSION} ./dist/**/traefik*.{zip,tar.gz} ./dist/traefik*.{tar.gz,txt} --repo traefik/traefik --title ${VERSION} --notes ${VERSION} - - ./script/deploy.sh - diff --git a/.github/workflows/sync-docker-images.yaml b/.github/workflows/sync-docker-images.yaml deleted file mode 100644 index 6f0b3c103..000000000 --- a/.github/workflows/sync-docker-images.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: Sync Docker Images - -on: - workflow_dispatch: - schedule: - - cron: "0 0 * * *" # Run every day - -jobs: - sync: - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - if: github.repository == 'traefik/traefik' - - steps: - - uses: actions/checkout@v4 - - - uses: imjasonh/setup-crane@v0.4 - - - name: Sync - run: | - EXCLUDED_TAGS="1.7.9-alpine v1.0.0-beta.392 v1.0.0-beta.404 v1.0.0-beta.704 v1.0.0-rc1 v1.7.9-alpine" - EXCLUDED_REGEX=$(echo $EXCLUDED_TAGS | sed 's/ /|/g') - diff <(crane ls traefik) <(crane ls ghcr.io/traefik/traefik) | grep '^<' | awk '{print $2}' | while read -r tag; do [[ "$tag" =~ ^($EXCLUDED_REGEX)$ ]] || (echo "Processing image: traefik:$tag"; crane cp "traefik:$tag" "ghcr.io/traefik/traefik:$tag"); done - crane cp traefik:latest ghcr.io/traefik/traefik:latest diff --git a/.github/workflows/template-webui.yaml b/.github/workflows/template-webui.yaml deleted file mode 100644 index df52d75c1..000000000 --- a/.github/workflows/template-webui.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build Web UI -on: - workflow_call: {} -jobs: - - build-webui: - runs-on: ubuntu-latest - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version-file: webui/.nvmrc - cache: yarn - cache-dependency-path: webui/yarn.lock - - - name: Build webui - working-directory: ./webui - run: | - yarn install - yarn build - - - name: Package webui - run: | - tar czvf webui.tar.gz ./webui/static/ - - - name: Artifact webui - uses: actions/upload-artifact@v4 - with: - name: webui.tar.gz - path: webui.tar.gz - retention-days: 1 diff --git a/.github/workflows/test-conformance.yaml b/.github/workflows/test-conformance.yaml deleted file mode 100644 index f1f02709b..000000000 --- a/.github/workflows/test-conformance.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Test K8s Gateway API conformance - -on: - pull_request: - branches: - - '*' - paths: - - '.github/workflows/test-conformance.yaml' - - 'pkg/provider/kubernetes/gateway/**' - - 'integration/fixtures/k8s-conformance/**' - - 'integration/k8s_conformance_test.go' - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - -jobs: - - test-conformance: - runs-on: ubuntu-latest - - 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: K8s Gateway API conformance test and report - run: | - make test-gateway-api-conformance - git diff --exit-code diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml deleted file mode 100644 index f8eac5dc6..000000000 --- a/.github/workflows/test-integration.yaml +++ /dev/null @@ -1,78 +0,0 @@ -name: Test Integration - -on: - pull_request: - branches: - - '*' - paths-ignore: - - 'docs/**' - - '**.md' - - 'script/gcg/**' - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - -jobs: - - build: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: Avoid generating webui - run: touch webui/static/index.html - - - name: Build binary - run: make binary - - test-integration: - runs-on: ubuntu-latest - 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 }} - check-latest: true - - - 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@v2.0.0 - 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 deleted file mode 100644 index 7d4a0fa66..000000000 --- a/.github/workflows/test-unit.yaml +++ /dev/null @@ -1,57 +0,0 @@ -name: Test Unit - -on: - pull_request: - branches: - - '*' - paths-ignore: - - 'docs/**' - - '**.md' - - 'script/gcg/**' - -env: - GO_VERSION: '1.23' - -jobs: - - test-unit: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: Avoid generating webui - run: touch webui/static/index.html - - - name: Tests - run: make test-unit - - test-ui-unit: - runs-on: ubuntu-latest - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v4 - with: - node-version-file: webui/.nvmrc - cache: 'yarn' - cache-dependency-path: webui/yarn.lock - - - name: UI unit tests - run: | - yarn --cwd webui install - yarn --cwd webui test:unit:ci diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml deleted file mode 100644 index 4ed5faec5..000000000 --- a/.github/workflows/validate.yaml +++ /dev/null @@ -1,87 +0,0 @@ -name: Validate - -on: - pull_request: - branches: - - '*' - -env: - GO_VERSION: '1.23' - GOLANGCI_LINT_VERSION: v2.0.2 - MISSPELL_VERSION: v0.6.0 - -jobs: - - lint: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: golangci-lint - uses: golangci/golangci-lint-action@v7 - with: - version: "${{ env.GOLANGCI_LINT_VERSION }}" - - validate: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: Install misspell ${{ env.MISSPELL_VERSION }} - run: curl -sfL https://raw.githubusercontent.com/golangci/misspell/HEAD/install-misspell.sh | sh -s -- -b $(go env GOPATH)/bin ${MISSPELL_VERSION} - - - name: Avoid generating webui - run: touch webui/static/index.html - - - name: Validate - run: make validate-files - - validate-generate: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: go generate - run: | - make generate - git diff --exit-code - - - name: go mod tidy - run: | - go mod tidy - git diff --exit-code - - - name: make generate-crd - run: | - make generate-crd - git diff --exit-code diff --git a/Dockerfile b/Dockerfile index fcf9c49b9..710d31a74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ RUN apk add --no-cache --no-progress ca-certificates tzdata ARG TARGETPLATFORM COPY ./dist/$TARGETPLATFORM/traefik / +COPY ./traefik.yml /etc/traefik/traefik.yml EXPOSE 80 VOLUME ["/tmp"] From c826cc97e65487ba65992cd74af0b45ee0d97ecf Mon Sep 17 00:00:00 2001 From: "Arthur K." Date: Wed, 11 Jun 2025 15:04:44 +0300 Subject: [PATCH 2/6] feat: callbacks --- cmd/traefik/traefik.go | 6 + .../reference/static-configuration/cli-ref.md | 3 + .../reference/static-configuration/env-ref.md | 3 + .../reference/static-configuration/file.toml | 1 + .../reference/static-configuration/file.yaml | 3 + pkg/config/static/static_config.go | 1 + pkg/updater/provider.go | 49 + schema.json | 1851 +++++++++++++++++ 8 files changed, 1917 insertions(+) create mode 100644 pkg/updater/provider.go create mode 100644 schema.json diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 77930754b..413907f17 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -49,6 +49,7 @@ import ( "github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" + "github.com/traefik/traefik/v3/pkg/updater" ) func main() { @@ -197,6 +198,8 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err tsProviders := initTailscaleProviders(staticConfiguration, providerAggregator) + updaterProvider := updater.New(staticConfiguration); + // Observability metricRegistries := registerMetricClients(staticConfiguration.Metrics) @@ -386,6 +389,9 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err } }) + // Updater + watcher.AddListener(updaterProvider.HandleConfigUpdate) + return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, observabilityMgr), nil } diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index b35cbc982..2eaa9861b 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -369,6 +369,9 @@ 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 disabled by default. (Default: ```false```) +`--global.updatercallbacks`: +Callback urls for updater script (example: https://localhost:8080/callback) + `--hostresolver`: Enable CNAME Flattening. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 8f6e25c05..474fdc9ef 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -369,6 +369,9 @@ 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 disabled by default. (Default: ```false```) +`TRAEFIK_GLOBAL_UPDATERCALLBACKS`: +Callback urls for updater script (example: https://localhost:8080/callback) + `TRAEFIK_HOSTRESOLVER`: Enable CNAME Flattening. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index e548ee76d..8e96fd52e 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -3,6 +3,7 @@ [global] checkNewVersion = true sendAnonymousUsage = true + updaterCallbacks = ["foobar", "foobar"] [serversTransport] insecureSkipVerify = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 760802591..3c79d9089 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -3,6 +3,9 @@ global: checkNewVersion: true sendAnonymousUsage: true + updaterCallbacks: + - foobar + - foobar serversTransport: insecureSkipVerify: true rootCAs: diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index af60efbb8..73f740472 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -108,6 +108,7 @@ type CertificateResolver struct { 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 disabled by default." json:"sendAnonymousUsage,omitempty" toml:"sendAnonymousUsage,omitempty" yaml:"sendAnonymousUsage,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + UpdaterCallbacks []string `description:"Callback urls for updater script (example: https://localhost:8080/callback)" json:"updaterCallbacks,omitempty" toml:"updaterCallbacks,omitempty" yaml:"updaterCallbacks,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // ServersTransport options to configure communication between Traefik and the servers. diff --git a/pkg/updater/provider.go b/pkg/updater/provider.go new file mode 100644 index 000000000..26d1fc469 --- /dev/null +++ b/pkg/updater/provider.go @@ -0,0 +1,49 @@ +package updater + +import ( + "bytes" + "encoding/json" + "net/http" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/config/static" + "github.com/traefik/traefik/v3/pkg/safe" +) + +type Updater struct { + callbackUrls []string +} + +func New(config *static.Configuration) *Updater { + updater := &Updater{ + callbackUrls: config.Global.UpdaterCallbacks, + } + + return updater +} + +func (u *Updater) HandleConfigUpdate(cfg dynamic.Configuration) { + body, err := json.Marshal(cfg) + + if err != nil { + // should never happen? + log.Error().Err(err).Msg("Error while marshalling dynamic configuration data to json") + return + } + + requestBody := bytes.NewBuffer(body) + + for _, url := range u.callbackUrls { + safe.Go(func() { + resp, err := http.Post(url, "application/json", requestBody) + + if err != nil { + log.Error().Err(err).Str("url", url).Msg("Error while sending configuration data to callback") + } else { + log.Debug().Str("url", url).Msg("Configuration data sent") + resp.Body.Close() + } + }) + } +} diff --git a/schema.json b/schema.json new file mode 100644 index 000000000..0f8823ad9 --- /dev/null +++ b/schema.json @@ -0,0 +1,1851 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/traefik-v3.json", + "$defs": { + "CertificateResolverTailscaleStruct": { + "additionalProperties": false, + "type": "object" + }, + "acmeConfiguration": { + "additionalProperties": false, + "properties": { + "caCertificates": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "caServer": { + "type": "string" + }, + "caServerName": { + "type": "string" + }, + "caSystemCertPool": { + "type": "boolean" + }, + "certificatesDuration": { + "type": "integer" + }, + "dnsChallenge": { + "$ref": "#/$defs/acmeDNSChallenge" + }, + "eab": { + "$ref": "#/$defs/acmeEAB" + }, + "email": { + "type": "string" + }, + "httpChallenge": { + "$ref": "#/$defs/acmeHTTPChallenge" + }, + "keyType": { + "type": "string" + }, + "preferredChain": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "tlsChallenge": { + "$ref": "#/$defs/acmeTLSChallenge" + } + }, + "type": "object" + }, + "acmeDNSChallenge": { + "additionalProperties": false, + "properties": { + "delayBeforeCheck": { + "type": "string" + }, + "disablePropagationCheck": { + "type": "boolean" + }, + "propagation": { + "$ref": "#/$defs/acmePropagation" + }, + "provider": { + "type": "string" + }, + "resolvers": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "acmeEAB": { + "additionalProperties": false, + "properties": { + "hmacEncoded": { + "type": "string" + }, + "kid": { + "type": "string" + } + }, + "type": "object" + }, + "acmeHTTPChallenge": { + "additionalProperties": false, + "properties": { + "entryPoint": { + "type": "string" + } + }, + "type": "object" + }, + "acmePropagation": { + "additionalProperties": false, + "properties": { + "delayBeforeChecks": { + "type": "string" + }, + "disableANSChecks": { + "type": "boolean" + }, + "disableChecks": { + "type": "boolean" + }, + "requireAllRNS": { + "type": "boolean" + } + }, + "type": "object" + }, + "acmeTLSChallenge": { + "additionalProperties": false, + "type": "object" + }, + "consulProviderBuilder": { + "additionalProperties": false, + "properties": { + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "rootKey": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "consulcatalogEndpointConfig": { + "additionalProperties": false, + "properties": { + "address": { + "type": "string" + }, + "datacenter": { + "type": "string" + }, + "endpointWaitTime": { + "type": "string" + }, + "httpAuth": { + "$ref": "#/$defs/consulcatalogEndpointHTTPAuthConfig" + }, + "scheme": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "consulcatalogEndpointHTTPAuthConfig": { + "additionalProperties": false, + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "consulcatalogProviderBuilder": { + "additionalProperties": false, + "properties": { + "cache": { + "type": "boolean" + }, + "connectAware": { + "type": "boolean" + }, + "connectByDefault": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "$ref": "#/$defs/consulcatalogEndpointConfig" + }, + "exposedByDefault": { + "type": "boolean" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "prefix": { + "type": "string" + }, + "refreshInterval": { + "type": "string" + }, + "requireConsistent": { + "type": "boolean" + }, + "serviceName": { + "type": "string" + }, + "stale": { + "type": "boolean" + }, + "strictChecks": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "crdProvider": { + "additionalProperties": false, + "properties": { + "allowCrossNamespace": { + "type": "boolean" + }, + "allowEmptyServices": { + "type": "boolean" + }, + "allowExternalNameServices": { + "type": "boolean" + }, + "certAuthFilePath": { + "type": "string" + }, + "disableClusterScopeResources": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "ingressClass": { + "type": "string" + }, + "labelSelector": { + "type": "string" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "nativeLBByDefault": { + "type": "boolean" + }, + "throttleDuration": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "dockerProvider": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "exposedByDefault": { + "type": "boolean" + }, + "httpClientTimeout": { + "type": "string" + }, + "network": { + "type": "string" + }, + "password": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "useBindPortIP": { + "type": "boolean" + }, + "username": { + "type": "string" + }, + "watch": { + "type": "boolean" + }, + "labelMap": { + "type": "array", + "items": { + "type": "object", + "properties": { + "from": { + "type": "string", + "description": "Shorthand label." + }, + "to": { + "type": "string", + "description": "Full label with templates." + }, + "value": { + "type": "string", + "description": "Optional override; used instead of user input if set." + } + }, + "required": ["from", "to"], + "additionalProperties": false + } + } + }, + "type": "object" + }, + "dockerSwarmProvider": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "exposedByDefault": { + "type": "boolean" + }, + "httpClientTimeout": { + "type": "string" + }, + "network": { + "type": "string" + }, + "password": { + "type": "string" + }, + "refreshSeconds": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "useBindPortIP": { + "type": "boolean" + }, + "username": { + "type": "string" + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "ecsProvider": { + "additionalProperties": false, + "properties": { + "accessKeyID": { + "type": "string" + }, + "autoDiscoverClusters": { + "type": "boolean" + }, + "clusters": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "ecsAnywhere": { + "type": "boolean" + }, + "exposedByDefault": { + "type": "boolean" + }, + "healthyTasksOnly": { + "type": "boolean" + }, + "refreshSeconds": { + "type": "integer" + }, + "region": { + "type": "string" + }, + "secretAccessKey": { + "type": "string" + } + }, + "type": "object" + }, + "etcdProvider": { + "additionalProperties": false, + "properties": { + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "password": { + "type": "string" + }, + "rootKey": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "fileProvider": { + "additionalProperties": false, + "properties": { + "debugLogGeneratedTemplate": { + "type": "boolean" + }, + "directory": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "gatewayProvider": { + "additionalProperties": false, + "properties": { + "certAuthFilePath": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "experimentalChannel": { + "type": "boolean" + }, + "labelSelector": { + "type": "string" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "nativeLBByDefault": { + "type": "boolean" + }, + "statusAddress": { + "$ref": "#/$defs/gatewayStatusAddress" + }, + "throttleDuration": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "gatewayServiceRef": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "type": "object" + }, + "gatewayStatusAddress": { + "additionalProperties": false, + "properties": { + "hostname": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "service": { + "$ref": "#/$defs/gatewayServiceRef" + } + }, + "type": "object" + }, + "httpProvider": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "pollInterval": { + "type": "string" + }, + "pollTimeout": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + } + }, + "required": ["endpoint"], + "type": "object" + }, + "ingressEndpointIngress": { + "additionalProperties": false, + "properties": { + "hostname": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "publishedService": { + "type": "string" + } + }, + "type": "object" + }, + "ingressProvider": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "allowExternalNameServices": { + "type": "boolean" + }, + "certAuthFilePath": { + "type": "string" + }, + "disableClusterScopeResources": { + "type": "boolean" + }, + "disableIngressClassLookup": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "ingressClass": { + "type": "string" + }, + "ingressEndpoint": { + "$ref": "#/$defs/ingressEndpointIngress" + }, + "labelSelector": { + "type": "string" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "nativeLBByDefault": { + "type": "boolean" + }, + "throttleDuration": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "nomadEndpointConfig": { + "additionalProperties": false, + "properties": { + "address": { + "type": "string" + }, + "endpointWaitTime": { + "type": "string" + }, + "region": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "nomadProviderBuilder": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "$ref": "#/$defs/nomadEndpointConfig" + }, + "exposedByDefault": { + "type": "boolean" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "prefix": { + "type": "string" + }, + "refreshInterval": { + "type": "string" + }, + "stale": { + "type": "boolean" + }, + "throttleDuration": { + "type": "string" + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "pingHandler": { + "additionalProperties": false, + "properties": { + "entryPoint": { + "type": "string" + }, + "manualRouting": { + "type": "boolean" + }, + "terminatingStatusCode": { + "type": "integer" + } + }, + "type": "object" + }, + "pluginsDescriptor": { + "additionalProperties": false, + "properties": { + "moduleName": { + "type": "string" + }, + "settings": { + "$ref": "#/$defs/pluginsSettings" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "pluginsLocalDescriptor": { + "additionalProperties": false, + "properties": { + "moduleName": { + "type": "string" + }, + "settings": { + "$ref": "#/$defs/pluginsSettings" + } + }, + "type": "object" + }, + "pluginsSettings": { + "additionalProperties": false, + "properties": { + "envs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "mounts": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "redisProvider": { + "additionalProperties": false, + "properties": { + "db": { + "type": "integer" + }, + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "password": { + "type": "string" + }, + "rootKey": { + "type": "string" + }, + "sentinel": { + "$ref": "#/$defs/redisSentinel" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "redisSentinel": { + "additionalProperties": false, + "properties": { + "latencyStrategy": { + "type": "boolean" + }, + "masterName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "randomStrategy": { + "type": "boolean" + }, + "replicaStrategy": { + "type": "boolean" + }, + "useDisconnectedReplicas": { + "type": "boolean" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "restProvider": { + "additionalProperties": false, + "properties": { + "insecure": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticAPI": { + "additionalProperties": false, + "properties": { + "basePath": { + "type": "string" + }, + "dashboard": { + "type": "boolean" + }, + "debug": { + "type": "boolean" + }, + "disableDashboardAd": { + "type": "boolean" + }, + "insecure": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticCertificateResolver": { + "additionalProperties": false, + "properties": { + "acme": { + "$ref": "#/$defs/acmeConfiguration" + }, + "tailscale": { + "$ref": "#/$defs/CertificateResolverTailscaleStruct" + } + }, + "type": "object" + }, + "staticCore": { + "additionalProperties": false, + "properties": { + "defaultRuleSyntax": { + "type": "string" + } + }, + "type": "object" + }, + "staticEntryPoint": { + "additionalProperties": false, + "properties": { + "address": { + "type": "string" + }, + "allowACMEByPass": { + "type": "boolean" + }, + "asDefault": { + "type": "boolean" + }, + "forwardedHeaders": { + "$ref": "#/$defs/staticForwardedHeaders" + }, + "http": { + "$ref": "#/$defs/staticHTTPConfig" + }, + "http2": { + "$ref": "#/$defs/staticHTTP2Config" + }, + "http3": { + "$ref": "#/$defs/staticHTTP3Config" + }, + "observability": { + "$ref": "#/$defs/staticObservabilityConfig" + }, + "proxyProtocol": { + "$ref": "#/$defs/staticProxyProtocol" + }, + "reusePort": { + "type": "boolean" + }, + "transport": { + "$ref": "#/$defs/staticEntryPointsTransport" + }, + "udp": { + "$ref": "#/$defs/staticUDPConfig" + } + }, + "type": "object" + }, + "staticEntryPointsTransport": { + "additionalProperties": false, + "properties": { + "keepAliveMaxRequests": { + "type": "integer" + }, + "keepAliveMaxTime": { + "type": "string" + }, + "lifeCycle": { + "$ref": "#/$defs/staticLifeCycle" + }, + "respondingTimeouts": { + "$ref": "#/$defs/staticRespondingTimeouts" + } + }, + "type": "object" + }, + "staticExperimental": { + "additionalProperties": false, + "properties": { + "abortOnPluginFailure": { + "type": "boolean" + }, + "fastProxy": { + "$ref": "#/$defs/staticFastProxyConfig" + }, + "kubernetesGateway": { + "type": "boolean" + }, + "localPlugins": { + "additionalProperties": { + "$ref": "#/$defs/pluginsLocalDescriptor" + }, + "type": "object" + }, + "otlplogs": { + "type": "boolean" + }, + "plugins": { + "additionalProperties": { + "$ref": "#/$defs/pluginsDescriptor" + }, + "type": "object" + } + }, + "type": "object" + }, + "staticFastProxyConfig": { + "additionalProperties": false, + "properties": { + "debug": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticForwardedHeaders": { + "additionalProperties": false, + "properties": { + "connection": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "insecure": { + "type": "boolean" + }, + "trustedIPs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "staticForwardingTimeouts": { + "additionalProperties": false, + "properties": { + "dialTimeout": { + "type": "string" + }, + "idleConnTimeout": { + "type": "string" + }, + "responseHeaderTimeout": { + "type": "string" + } + }, + "type": "object" + }, + "staticGlobal": { + "additionalProperties": false, + "properties": { + "checkNewVersion": { + "type": "boolean" + }, + "sendAnonymousUsage": { + "type": "boolean" + }, + "updaterCallbacks": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" + }, + "staticHTTP2Config": { + "additionalProperties": false, + "properties": { + "maxConcurrentStreams": { + "type": "integer" + } + }, + "type": "object" + }, + "staticHTTP3Config": { + "additionalProperties": false, + "properties": { + "advertisedPort": { + "type": "integer" + } + }, + "type": "object" + }, + "staticHTTPConfig": { + "additionalProperties": false, + "properties": { + "encodeQuerySemicolons": { + "type": "boolean" + }, + "maxHeaderBytes": { + "type": "integer" + }, + "middlewares": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "redirections": { + "$ref": "#/$defs/staticRedirections" + }, + "tls": { + "$ref": "#/$defs/staticTLSConfig" + } + }, + "type": "object" + }, + "staticLifeCycle": { + "additionalProperties": false, + "properties": { + "graceTimeOut": { + "type": "string" + }, + "requestAcceptGraceTimeout": { + "type": "string" + } + }, + "type": "object" + }, + "staticObservabilityConfig": { + "additionalProperties": false, + "properties": { + "accessLogs": { + "type": "boolean" + }, + "metrics": { + "type": "boolean" + }, + "tracing": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticProviders": { + "additionalProperties": false, + "properties": { + "consul": { + "$ref": "#/$defs/consulProviderBuilder" + }, + "consulCatalog": { + "$ref": "#/$defs/consulcatalogProviderBuilder" + }, + "docker": { + "$ref": "#/$defs/dockerProvider" + }, + "ecs": { + "$ref": "#/$defs/ecsProvider" + }, + "etcd": { + "$ref": "#/$defs/etcdProvider" + }, + "file": { + "$ref": "#/$defs/fileProvider" + }, + "http": { + "$ref": "#/$defs/httpProvider" + }, + "kubernetesCRD": { + "$ref": "#/$defs/crdProvider" + }, + "kubernetesGateway": { + "$ref": "#/$defs/gatewayProvider" + }, + "kubernetesIngress": { + "$ref": "#/$defs/ingressProvider" + }, + "nomad": { + "$ref": "#/$defs/nomadProviderBuilder" + }, + "plugin": { + "additionalProperties": { + "additionalProperties": {}, + "type": "object" + }, + "type": "object" + }, + "providersThrottleDuration": { + "type": "string" + }, + "redis": { + "$ref": "#/$defs/redisProvider" + }, + "rest": { + "$ref": "#/$defs/restProvider" + }, + "swarm": { + "$ref": "#/$defs/dockerSwarmProvider" + }, + "zooKeeper": { + "$ref": "#/$defs/zkProvider" + } + }, + "type": "object" + }, + "staticProxyProtocol": { + "additionalProperties": false, + "properties": { + "insecure": { + "type": "boolean" + }, + "trustedIPs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "staticRedirectEntryPoint": { + "additionalProperties": false, + "properties": { + "permanent": { + "type": "boolean" + }, + "priority": { + "type": "integer" + }, + "scheme": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "type": "object" + }, + "staticRedirections": { + "additionalProperties": false, + "properties": { + "entryPoint": { + "$ref": "#/$defs/staticRedirectEntryPoint" + } + }, + "type": "object" + }, + "staticRespondingTimeouts": { + "additionalProperties": false, + "properties": { + "idleTimeout": { + "type": "string" + }, + "readTimeout": { + "type": "string" + }, + "writeTimeout": { + "type": "string" + } + }, + "type": "object" + }, + "staticServersTransport": { + "additionalProperties": false, + "properties": { + "forwardingTimeouts": { + "$ref": "#/$defs/staticForwardingTimeouts" + }, + "insecureSkipVerify": { + "type": "boolean" + }, + "maxIdleConnsPerHost": { + "type": "integer" + }, + "rootCAs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "spiffe": { + "$ref": "#/$defs/staticSpiffe" + } + }, + "type": "object" + }, + "staticSpiffe": { + "additionalProperties": false, + "properties": { + "ids": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "trustDomain": { + "type": "string" + } + }, + "type": "object" + }, + "staticSpiffeClientConfig": { + "additionalProperties": false, + "properties": { + "workloadAPIAddr": { + "type": "string" + } + }, + "type": "object" + }, + "staticTCPServersTransport": { + "additionalProperties": false, + "properties": { + "dialKeepAlive": { + "type": "string" + }, + "dialTimeout": { + "type": "string" + }, + "terminationDelay": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/staticTLSClientConfig" + } + }, + "type": "object" + }, + "staticTLSClientConfig": { + "additionalProperties": false, + "properties": { + "insecureSkipVerify": { + "type": "boolean" + }, + "rootCAs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "spiffe": { + "$ref": "#/$defs/staticSpiffe" + } + }, + "type": "object" + }, + "staticTLSConfig": { + "additionalProperties": false, + "properties": { + "certResolver": { + "type": "string" + }, + "domains": { + "items": { + "$ref": "#/$defs/typesDomain" + }, + "type": ["array", "null"] + }, + "options": { + "type": "string" + } + }, + "type": "object" + }, + "staticTracing": { + "additionalProperties": false, + "properties": { + "addInternals": { + "type": "boolean" + }, + "capturedRequestHeaders": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "capturedResponseHeaders": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "globalAttributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "otlp": { + "$ref": "#/$defs/typesOTelTracing" + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "safeQueryParams": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "sampleRate": { + "type": "number" + }, + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "staticUDPConfig": { + "additionalProperties": false, + "properties": { + "timeout": { + "type": "string" + } + }, + "type": "object" + }, + "typesAccessLog": { + "additionalProperties": false, + "properties": { + "addInternals": { + "type": "boolean" + }, + "bufferingSize": { + "type": "integer" + }, + "fields": { + "$ref": "#/$defs/typesAccessLogFields" + }, + "filePath": { + "type": "string" + }, + "filters": { + "$ref": "#/$defs/typesAccessLogFilters" + }, + "format": { + "type": "string" + }, + "otlp": { + "$ref": "#/$defs/typesOTelLog" + } + }, + "type": "object" + }, + "typesAccessLogFields": { + "additionalProperties": false, + "properties": { + "defaultMode": { + "type": "string" + }, + "headers": { + "$ref": "#/$defs/typesFieldHeaders" + }, + "names": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "typesAccessLogFilters": { + "additionalProperties": false, + "properties": { + "minDuration": { + "type": "string" + }, + "retryAttempts": { + "type": "boolean" + }, + "statusCodes": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "typesClientTLS": { + "additionalProperties": false, + "properties": { + "ca": { + "type": "string" + }, + "cert": { + "type": "string" + }, + "insecureSkipVerify": { + "type": "boolean" + }, + "key": { + "type": "string" + } + }, + "type": "object" + }, + "typesDatadog": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "address": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "pushInterval": { + "type": "string" + } + }, + "type": "object" + }, + "typesDomain": { + "additionalProperties": false, + "properties": { + "main": { + "type": "string" + }, + "sans": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "typesFieldHeaders": { + "additionalProperties": false, + "properties": { + "defaultMode": { + "type": "string" + }, + "names": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "typesHostResolverConfig": { + "additionalProperties": false, + "properties": { + "cnameFlattening": { + "type": "boolean" + }, + "resolvConfig": { + "type": "string" + }, + "resolvDepth": { + "type": "integer" + } + }, + "type": "object" + }, + "typesInfluxDB2": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "additionalLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "address": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "org": { + "type": "string" + }, + "pushInterval": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "typesMetrics": { + "additionalProperties": false, + "properties": { + "addInternals": { + "type": "boolean" + }, + "datadog": { + "$ref": "#/$defs/typesDatadog" + }, + "influxDB2": { + "$ref": "#/$defs/typesInfluxDB2" + }, + "otlp": { + "$ref": "#/$defs/typesOTLP" + }, + "prometheus": { + "$ref": "#/$defs/typesPrometheus" + }, + "statsD": { + "$ref": "#/$defs/typesStatsd" + } + }, + "type": "object" + }, + "typesOTLP": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "explicitBoundaries": { + "items": { + "type": "number" + }, + "type": ["array", "null"] + }, + "grpc": { + "$ref": "#/$defs/typesOTelGRPC" + }, + "http": { + "$ref": "#/$defs/typesOTelHTTP" + }, + "pushInterval": { + "type": "string" + }, + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "typesOTelGRPC": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "insecure": { + "type": "boolean" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + } + }, + "type": "object" + }, + "typesOTelHTTP": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + } + }, + "type": "object" + }, + "typesOTelLog": { + "additionalProperties": false, + "properties": { + "grpc": { + "$ref": "#/$defs/typesOTelGRPC" + }, + "http": { + "$ref": "#/$defs/typesOTelHTTP" + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "typesOTelTracing": { + "additionalProperties": false, + "properties": { + "grpc": { + "$ref": "#/$defs/typesOTelGRPC" + }, + "http": { + "$ref": "#/$defs/typesOTelHTTP" + } + }, + "type": "object" + }, + "typesPrometheus": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "buckets": { + "items": { + "type": "number" + }, + "type": ["array", "null"] + }, + "entryPoint": { + "type": "string" + }, + "headerLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "manualRouting": { + "type": "boolean" + } + }, + "type": "object" + }, + "typesStatsd": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "address": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "pushInterval": { + "type": "string" + } + }, + "type": "object" + }, + "typesTraefikLog": { + "additionalProperties": false, + "properties": { + "compress": { + "type": "boolean" + }, + "filePath": { + "type": "string" + }, + "format": { + "type": "string" + }, + "level": { + "type": "string" + }, + "maxAge": { + "type": "integer" + }, + "maxBackups": { + "type": "integer" + }, + "maxSize": { + "type": "integer" + }, + "noColor": { + "type": "boolean" + }, + "otlp": { + "$ref": "#/$defs/typesOTelLog" + } + }, + "type": "object" + }, + "zkProvider": { + "additionalProperties": false, + "properties": { + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "password": { + "type": "string" + }, + "rootKey": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + } + }, + "title": "Traefik v3 Static Configuration", + "properties": { + "accessLog": { + "$ref": "#/$defs/typesAccessLog" + }, + "api": { + "$ref": "#/$defs/staticAPI" + }, + "certificatesResolvers": { + "additionalProperties": { + "$ref": "#/$defs/staticCertificateResolver" + }, + "type": "object" + }, + "core": { + "$ref": "#/$defs/staticCore" + }, + "entryPoints": { + "additionalProperties": { + "$ref": "#/$defs/staticEntryPoint" + }, + "type": "object" + }, + "experimental": { + "$ref": "#/$defs/staticExperimental" + }, + "global": { + "$ref": "#/$defs/staticGlobal" + }, + "hostResolver": { + "$ref": "#/$defs/typesHostResolverConfig" + }, + "log": { + "$ref": "#/$defs/typesTraefikLog" + }, + "metrics": { + "$ref": "#/$defs/typesMetrics" + }, + "ping": { + "$ref": "#/$defs/pingHandler" + }, + "providers": { + "$ref": "#/$defs/staticProviders" + }, + "serversTransport": { + "$ref": "#/$defs/staticServersTransport" + }, + "spiffe": { + "$ref": "#/$defs/staticSpiffeClientConfig" + }, + "tcpServersTransport": { + "$ref": "#/$defs/staticTCPServersTransport" + }, + "tracing": { + "$ref": "#/$defs/staticTracing" + } + }, + "type": "object" +} From b4ef1baf65f63b77cfe71a7121a16e5d7e99599e Mon Sep 17 00:00:00 2001 From: "Arthur K." Date: Wed, 11 Jun 2025 15:04:44 +0300 Subject: [PATCH 3/6] feat: custom label shorthands --- .../reference/static-configuration/cli-ref.md | 24 ++++++++++ .../reference/static-configuration/env-ref.md | 24 ++++++++++ .../reference/static-configuration/file.toml | 20 ++++++++ .../reference/static-configuration/file.yaml | 14 ++++++ pkg/provider/configuration.go | 11 +++-- pkg/provider/docker/config.go | 48 ++++++++++++++----- pkg/provider/docker/pdocker.go | 9 +++- pkg/provider/docker/shared.go | 10 ++++ 8 files changed, 144 insertions(+), 16 deletions(-) diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 2eaa9861b..193f0c65d 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -768,6 +768,18 @@ Expose containers by default. (Default: ```true```) `--providers.docker.httpclienttimeout`: Client timeout for HTTP connections. (Default: ```0```) +`--providers.docker.labelmap`: +Label shorthands. + +`--providers.docker.labelmap[n].from`: +Shorthand label. + +`--providers.docker.labelmap[n].to`: +Full label with templates. + +`--providers.docker.labelmap[n].value`: +Optional override; used instead of user input if set. + `--providers.docker.network`: Default Docker network used. @@ -1167,6 +1179,18 @@ Expose containers by default. (Default: ```true```) `--providers.swarm.httpclienttimeout`: Client timeout for HTTP connections. (Default: ```0```) +`--providers.swarm.labelmap`: +Label shorthands. + +`--providers.swarm.labelmap[n].from`: +Shorthand label. + +`--providers.swarm.labelmap[n].to`: +Full label with templates. + +`--providers.swarm.labelmap[n].value`: +Optional override; used instead of user input if set. + `--providers.swarm.network`: Default Docker network used. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 474fdc9ef..849d058ab 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -768,6 +768,18 @@ Expose containers by default. (Default: ```true```) `TRAEFIK_PROVIDERS_DOCKER_HTTPCLIENTTIMEOUT`: Client timeout for HTTP connections. (Default: ```0```) +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP`: +Label shorthands. + +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP_n_FROM`: +Shorthand label. + +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP_n_TO`: +Full label with templates. + +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP_n_VALUE`: +Optional override; used instead of user input if set. + `TRAEFIK_PROVIDERS_DOCKER_NETWORK`: Default Docker network used. @@ -1167,6 +1179,18 @@ Expose containers by default. (Default: ```true```) `TRAEFIK_PROVIDERS_SWARM_HTTPCLIENTTIMEOUT`: Client timeout for HTTP connections. (Default: ```0```) +`TRAEFIK_PROVIDERS_SWARM_LABELMAP`: +Label shorthands. + +`TRAEFIK_PROVIDERS_SWARM_LABELMAP_n_FROM`: +Shorthand label. + +`TRAEFIK_PROVIDERS_SWARM_LABELMAP_n_TO`: +Full label with templates. + +`TRAEFIK_PROVIDERS_SWARM_LABELMAP_n_VALUE`: +Optional override; used instead of user input if set. + `TRAEFIK_PROVIDERS_SWARM_NETWORK`: Default Docker network used. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 8e96fd52e..13fd6b2bc 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -98,6 +98,16 @@ password = "foobar" endpoint = "foobar" httpClientTimeout = "42s" + + [[providers.docker.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" + + [[providers.docker.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" [providers.docker.tls] ca = "foobar" cert = "foobar" @@ -116,6 +126,16 @@ endpoint = "foobar" httpClientTimeout = "42s" refreshSeconds = "42s" + + [[providers.swarm.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" + + [[providers.swarm.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" [providers.swarm.tls] ca = "foobar" cert = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 3c79d9089..4c4fdc109 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -109,6 +109,13 @@ providers: useBindPortIP: true watch: true defaultRule: foobar + labelMap: + - from: foobar + to: foobar + value: foobar + - from: foobar + to: foobar + value: foobar username: foobar password: foobar endpoint: foobar @@ -126,6 +133,13 @@ providers: useBindPortIP: true watch: true defaultRule: foobar + labelMap: + - from: foobar + to: foobar + value: foobar + - from: foobar + to: foobar + value: foobar username: foobar password: foobar endpoint: foobar diff --git a/pkg/provider/configuration.go b/pkg/provider/configuration.go index bbb393425..70dd14144 100644 --- a/pkg/provider/configuration.go +++ b/pkg/provider/configuration.go @@ -396,8 +396,8 @@ func AddStore(configuration *dynamic.TLSConfiguration, storeName string, store t return reflect.DeepEqual(configuration.Stores[storeName], store) } -// MakeDefaultRuleTemplate creates the default rule template. -func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*template.Template, error) { +// MakeAnyTemplate creates a template with any name +func MakeAnyTemplate(name string, value string, funcMap template.FuncMap) (*template.Template, error) { defaultFuncMap := sprig.TxtFuncMap() defaultFuncMap["normalize"] = Normalize @@ -405,7 +405,12 @@ func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*tem defaultFuncMap[k] = fn } - return template.New("defaultRule").Funcs(defaultFuncMap).Parse(defaultRule) + return template.New(name).Funcs(defaultFuncMap).Parse(value) +} + +// MakeDefaultRuleTemplate creates the default rule template. +func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*template.Template, error) { + return MakeAnyTemplate("defaultRule", defaultRule, funcMap) } // BuildTCPRouterConfiguration builds a router configuration. diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go index b3bbe2411..3a6e75bee 100644 --- a/pkg/provider/docker/config.go +++ b/pkg/provider/docker/config.go @@ -1,6 +1,7 @@ package docker import ( + "bytes" "context" "errors" "fmt" @@ -28,15 +29,50 @@ func NewDynConfBuilder(configuration Shared, apiClient client.APIClient, swarm b return &DynConfBuilder{Shared: configuration, apiClient: apiClient, swarm: swarm} } +func (p *DynConfBuilder) applyLabels(container *dockerData, model interface{}) { + for _, item := range p.LabelMap { + value, ok := container.Labels[item.From] + if !ok { // label doesn't exist + continue + } + + writer := &bytes.Buffer{} + if err := item.toTpl.Execute(writer, model); err != nil { + continue // should never happen? + } + + to := writer.String() + if item.Value != nil { + container.Labels[to] = *item.Value + } else { + container.Labels[to] = value + } + } +} + func (p *DynConfBuilder) build(ctx context.Context, containersInspected []dockerData) *dynamic.Configuration { configurations := make(map[string]*dynamic.Configuration) for _, container := range containersInspected { + serviceName := getServiceName(container) + + model := struct { + Name string + ContainerName string + Labels *map[string]string + }{ + Name: serviceName, + ContainerName: strings.TrimPrefix(container.Name, "/"), + Labels: &container.Labels, + } + containerName := getServiceName(container) + "-" + container.ID logger := log.Ctx(ctx).With().Str("container", containerName).Logger() ctxContainer := logger.WithContext(ctx) + p.applyLabels(&container, model) + if !p.keepContainer(ctxContainer, container) { continue } @@ -83,18 +119,6 @@ func (p *DynConfBuilder) build(ctx context.Context, containersInspected []docker continue } - serviceName := getServiceName(container) - - model := struct { - Name string - ContainerName string - Labels map[string]string - }{ - Name: serviceName, - ContainerName: strings.TrimPrefix(container.Name, "/"), - Labels: container.Labels, - } - provider.BuildRouterConfiguration(ctx, confFromLabel.HTTP, serviceName, p.defaultRuleTpl, model) configurations[containerName] = confFromLabel diff --git a/pkg/provider/docker/pdocker.go b/pkg/provider/docker/pdocker.go index 44ff3470d..8deb4f146 100644 --- a/pkg/provider/docker/pdocker.go +++ b/pkg/provider/docker/pdocker.go @@ -49,8 +49,15 @@ func (p *Provider) Init() error { if err != nil { return fmt.Errorf("error while parsing default rule: %w", err) } - p.defaultRuleTpl = defaultRuleTpl + + for _, item := range p.LabelMap { + toTpl, err := provider.MakeAnyTemplate(item.From, item.To, nil) + if err != nil { + return fmt.Errorf("error while parsing label %v: %w", item.To, err) + } + item.toTpl = toTpl + } return nil } diff --git a/pkg/provider/docker/shared.go b/pkg/provider/docker/shared.go index 97c8d519b..027c398fd 100644 --- a/pkg/provider/docker/shared.go +++ b/pkg/provider/docker/shared.go @@ -33,9 +33,19 @@ type Shared struct { Watch bool `description:"Watch Docker events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"` DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"` + LabelMap []*LabelMapItem `description:"Label shorthands." json:"labelMap,omitempty" toml:"labelMap,omitempty" yaml:"labelMap,omitempty"` + defaultRuleTpl *template.Template } +type LabelMapItem struct { + From string `description:"Shorthand label." json:"from,omitempty" toml:"from,omitempty" yaml:"from,omitempty"` + To string `description:"Full label with templates." json:"to,omitempty" toml:"to,omitempty" yaml:"to,omitempty"` + Value *string `description:"Optional override; used instead of user input if set." json:"value,omitempty" toml:"value,omitempty" yaml:"value,omitempty"` + toTpl *template.Template +} + + func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData { containerInspected, err := dockerClient.ContainerInspect(ctx, containerID) if err != nil { From 74601ca0c791adb8a66fb113b7e127630b4101f7 Mon Sep 17 00:00:00 2001 From: "Arthur K." Date: Wed, 11 Jun 2025 15:04:44 +0300 Subject: [PATCH 4/6] chore: stuff for local deployment --- .github/FUNDING.yml | 3 - .github/ISSUE_TEMPLATE.md | 77 ------------ .github/ISSUE_TEMPLATE/bug_report.yml | 82 ------------ .github/ISSUE_TEMPLATE/config.yml | 8 -- .github/ISSUE_TEMPLATE/feature-request.yml | 33 ----- .github/PULL_REQUEST_TEMPLATE.md | 37 ------ .github/PULL_REQUEST_TEMPLATE/mergeback.md | 7 -- .github/PULL_REQUEST_TEMPLATE/release.md | 7 -- .github/workflows/build.yaml | 81 ------------ .github/workflows/check_doc.yml | 25 ---- .github/workflows/codeql.yml | 70 ----------- .github/workflows/documentation.yml | 52 -------- .github/workflows/experimental.yaml | 70 ----------- .github/workflows/release.yaml | 138 --------------------- .github/workflows/sync-docker-images.yaml | 26 ---- .github/workflows/template-webui.yaml | 37 ------ .github/workflows/test-conformance.yaml | 39 ------ .github/workflows/test-integration.yaml | 78 ------------ .github/workflows/test-unit.yaml | 57 --------- .github/workflows/validate.yaml | 87 ------------- Dockerfile | 1 + README.md | 12 +- 22 files changed, 9 insertions(+), 1018 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/mergeback.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/release.md delete mode 100644 .github/workflows/build.yaml delete mode 100644 .github/workflows/check_doc.yml delete mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/documentation.yml delete mode 100644 .github/workflows/experimental.yaml delete mode 100644 .github/workflows/release.yaml delete mode 100644 .github/workflows/sync-docker-images.yaml delete mode 100644 .github/workflows/template-webui.yaml delete mode 100644 .github/workflows/test-conformance.yaml delete mode 100644 .github/workflows/test-integration.yaml delete mode 100644 .github/workflows/test-unit.yaml delete mode 100644 .github/workflows/validate.yaml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index b920e1793..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: traefik diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 8d124b341..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,77 +0,0 @@ - - -### Do you want to request a *feature* or report a *bug*? - - - -Bug - - - -### What did you do? - - - -### What did you expect to see? - - - -### What did you see instead? - - - -### Output of `traefik version`: (_What version of Traefik are you using?_) - - - -``` -(paste your output here) -``` - -### What is your environment & configuration (arguments, toml, provider, platform, ...)? - -```toml -# (paste your configuration here) -``` - - - - -### If applicable, please paste the log output in DEBUG level (`--log.level=DEBUG` switch) - -``` -(paste your output here) -``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index ce21d35ee..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Bug Report (Traefik) -description: Create a report to help us improve. -body: - - type: checkboxes - id: terms - attributes: - label: Welcome! - description: | - The issue tracker is for reporting bugs and feature requests only. - For end-user related support questions, please use the [Traefik community forum](https://community.traefik.io/). - - All new/updated issues are triaged regularly by the maintainers. - All issues closed by a bot are subsequently double-checked by the maintainers. - - DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS. - - options: - - label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any. - required: true - - label: Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any. - required: true - - - type: textarea - attributes: - label: What did you do? - description: | - How to write a good bug report? - - - Respect the issue template as much as possible. - - The title should be short and descriptive. - - Explain the conditions which led you to report this issue: the context. - - The context should lead to something, an idea or a problem that you’re facing. - - Remain clear and concise. - - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown) - placeholder: What did you do? - validations: - required: true - - - type: textarea - attributes: - label: What did you see instead? - placeholder: What did you see instead? - validations: - required: true - - - type: textarea - attributes: - label: What version of Traefik are you using? - description: | - `latest` is not considered as a valid version. - - Output of `traefik version`. - - For the Traefik Docker image (`docker run [IMAGE] version`), example: - ```console - $ docker run traefik version - ``` - placeholder: Paste your output here. - validations: - required: true - - - type: textarea - attributes: - label: What is your environment & configuration? - description: arguments, toml, provider, platform, ... - placeholder: Add information here. - value: | - ```yaml - # (paste your configuration here) - ``` - - Add more configuration information here. - validations: - required: true - - - type: textarea - attributes: - label: If applicable, please paste the log output in DEBUG level - description: "`--log.level=DEBUG` switch." - placeholder: Paste your output here. - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 80c8c86b5..000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Traefik Community Support - url: https://community.traefik.io/ - about: If you have a question, or are looking for advice, please post on our Discuss forum! The community loves to chime in to help. Happy Coding! - - name: Traefik Helm Chart Issues - url: https://github.com/traefik/traefik-helm-chart - about: Are you submitting an issue or feature enhancement for the Traefik helm chart? Please post in the traefik-helm-chart GitHub Issues. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index 5a092594d..000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Feature Request (Traefik) -description: Suggest an idea for this project. -body: - - type: checkboxes - id: terms - attributes: - label: Welcome! - description: | - The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please refer to one of the following: - - the Traefik community forum: https://community.traefik.io/ - - DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS. - options: - - label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any. - required: true - - label: Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any. - required: true - - - type: textarea - attributes: - label: What did you expect to see? - description: | - How to write a good issue? - - - Respect the issue template as much as possible. - - The title should be short and descriptive. - - Explain the conditions which led you to report this issue: the context. - - The context should lead to something, an idea or a problem that you’re facing. - - Remain clear and concise. - - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown) - placeholder: What did you expect to see? - validations: - required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 2594684f6..000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,37 +0,0 @@ - - -### What does this PR do? - - - - -### Motivation - - - - -### More - -- [ ] Added/updated tests -- [ ] Added/updated documentation - -### Additional Notes - - diff --git a/.github/PULL_REQUEST_TEMPLATE/mergeback.md b/.github/PULL_REQUEST_TEMPLATE/mergeback.md deleted file mode 100644 index dfc856011..000000000 --- a/.github/PULL_REQUEST_TEMPLATE/mergeback.md +++ /dev/null @@ -1,7 +0,0 @@ -### What does this PR do? - -Merge v{{.Version}} into master - -### Motivation - -Be sync. diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md deleted file mode 100644 index 226a85a73..000000000 --- a/.github/PULL_REQUEST_TEMPLATE/release.md +++ /dev/null @@ -1,7 +0,0 @@ -### What does this PR do? - -Prepare release v{{.Version}}. - -### Motivation - -Create a new release. diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 0122f29e6..000000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,81 +0,0 @@ -name: Build Binaries - -on: - pull_request: - branches: - - '*' - paths-ignore: - - 'docs/**' - - '**.md' - - 'script/gcg/**' - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - -jobs: - - build-webui: - uses: ./.github/workflows/template-webui.yaml - - build: - runs-on: ubuntu-latest - - strategy: - matrix: - os: [ darwin, freebsd, linux, openbsd, windows ] - arch: [ amd64, arm64 ] - include: - - os: freebsd - arch: 386 - - os: linux - arch: 386 - - os: linux - arch: arm - goarm: 6 - - os: linux - arch: arm - goarm: 7 - - os: linux - arch: ppc64le - - os: linux - arch: riscv64 - - os: linux - arch: s390x - - os: openbsd - arch: 386 - - os: windows - arch: 386 - needs: - - build-webui - - 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 - env: - ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }} - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - - - name: Artifact webui - uses: actions/download-artifact@v4 - with: - name: webui.tar.gz - - - name: Untar webui - run: | - tar xvf webui.tar.gz - rm webui.tar.gz - - - name: Build - env: - GOOS: ${{ matrix.os }} - GOARCH: ${{ matrix.arch }} - GOARM: ${{ matrix.goarm }} - run: make binary diff --git a/.github/workflows/check_doc.yml b/.github/workflows/check_doc.yml deleted file mode 100644 index c5fbddcec..000000000 --- a/.github/workflows/check_doc.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Check Documentation - -on: - pull_request: - branches: - - '*' - -jobs: - - docs: - name: Check, verify and build documentation - runs-on: ubuntu-latest - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check documentation - run: make docs-pull-images docs - env: - # These variables are not passed to workflows that are triggered by a pull request from a fork. - DOCS_VERIFY_SKIP: ${{ vars.DOCS_VERIFY_SKIP }} - DOCS_LINT_SKIP: ${{ vars.DOCS_LINT_SKIP }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index f921d7789..000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: - - master - - v* - schedule: - - cron: '11 22 * * 1' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'javascript', 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Use only 'java' to analyze code written in Java, Kotlin or both - # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: setup go - uses: actions/setup-go@v5 - 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 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # 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 - - # ℹ️ 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 - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml deleted file mode 100644 index adfdcc74e..000000000 --- a/.github/workflows/documentation.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Build and Publish Documentation - -on: - push: - branches: - - master - - v* - -env: - STRUCTOR_VERSION: v1.13.2 - MIXTUS_VERSION: v0.4.1 - -jobs: - - docs: - name: Doc Process - runs-on: ubuntu-latest - if: github.repository == 'traefik/traefik' - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Install Structor ${{ env.STRUCTOR_VERSION }} - run: curl -sSfL https://raw.githubusercontent.com/traefik/structor/master/godownloader.sh | sh -s -- -b $HOME/bin ${STRUCTOR_VERSION} - - - name: Install Seo-doc - run: curl -sSfL https://raw.githubusercontent.com/traefik/seo-doc/master/godownloader.sh | sh -s -- -b "${HOME}/bin" - - - name: Install Mixtus ${{ env.MIXTUS_VERSION }} - run: curl -sSfL https://raw.githubusercontent.com/traefik/mixtus/master/godownloader.sh | sh -s -- -b $HOME/bin ${MIXTUS_VERSION} - - - name: Build documentation - run: $HOME/bin/structor -o traefik -r traefik --dockerfile-url="https://raw.githubusercontent.com/traefik/traefik/v1.7/docs.Dockerfile" --menu.js-url="https://raw.githubusercontent.com/traefik/structor/master/traefik-menu.js.gotmpl" --rqts-url="https://raw.githubusercontent.com/traefik/structor/master/requirements-override.txt" --force-edit-url --exp-branch=master --debug - env: - STRUCTOR_LATEST_TAG: ${{ vars.STRUCTOR_LATEST_TAG }} - - - name: Apply seo - run: $HOME/bin/seo -path=./site -product=traefik - - - name: Publish documentation - run: $HOME/bin/mixtus --dst-doc-path="./traefik" --dst-owner=traefik --dst-repo-name=doc --git-user-email="30906710+traefiker@users.noreply.github.com" --git-user-name=traefiker --src-doc-path="./site" --src-owner=traefik --src-repo-name=traefik - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }} diff --git a/.github/workflows/experimental.yaml b/.github/workflows/experimental.yaml deleted file mode 100644 index 76959bbd5..000000000 --- a/.github/workflows/experimental.yaml +++ /dev/null @@ -1,70 +0,0 @@ -name: Build experimental image on branch - -on: - push: - branches: - - master - - v* - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - -jobs: - - build-webui: - if: github.repository == 'traefik/traefik' - uses: ./.github/workflows/template-webui.yaml - - experimental: - if: github.repository == 'traefik/traefik' - name: Build experimental image on branch - runs-on: ubuntu-latest - - 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 - env: - ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }} - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - - - name: Build - run: make generate binary - - - name: Branch name - run: echo ${GITHUB_REF##*/} - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Artifact webui - uses: actions/download-artifact@v4 - with: - name: webui.tar.gz - - - name: Untar webui - run: | - tar xvf webui.tar.gz - rm webui.tar.gz - - - name: Build docker experimental image - env: - DOCKER_BUILDX_ARGS: "--push" - run: | - make multi-arch-image-experimental-${GITHUB_REF##*/} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 03a94ec91..000000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,138 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*.*.*' - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - VERSION: ${{ github.ref_name }} - TRAEFIKER_EMAIL: "traefiker@traefik.io" - CODENAME: chaource - -jobs: - - build-webui: - if: github.ref_type == 'tag' && github.repository == 'traefik/traefik' - uses: ./.github/workflows/template-webui.yaml - - build: - if: github.ref_type == 'tag' && github.repository == 'traefik/traefik' - runs-on: ubuntu-latest - - strategy: - matrix: - os: [ linux-amd64, linux-386, linux-arm, linux-arm64, linux-ppc64le, linux-s390x, linux-riscv64, darwin, windows-amd64, windows-arm64, windows-386, freebsd, openbsd ] - needs: - - build-webui - - 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 - env: - # Ensure cache consistency on Linux, see https://github.com/actions/setup-go/pull/383 - ImageOS: ${{ matrix.os }} - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - - - name: Artifact webui - uses: actions/download-artifact@v4 - with: - name: webui.tar.gz - - - name: Untar webui - run: | - tar xvf webui.tar.gz - rm webui.tar.gz - - - name: Go generate - run: go generate - - - - name: Generate goreleaser file - run: | - GORELEASER_CONFIG_FILE_PATH=$(go run ./internal/release "${{ matrix.os }}") - echo "GORELEASER_CONFIG_FILE_PATH=$GORELEASER_CONFIG_FILE_PATH" >> $GITHUB_ENV - - - name: Build with goreleaser - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser - # 'latest', 'nightly', or a semver - version: '~> v2' - args: release --clean --timeout="90m" --config "${{ env.GORELEASER_CONFIG_FILE_PATH }}" - - - name: Artifact binaries - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.os }}-binaries - path: | - dist/**/*_checksums.txt - dist/**/*.tar.gz - dist/**/*.zip - retention-days: 1 - - release: - if: github.ref_type == 'tag' && github.repository == 'traefik/traefik' - runs-on: ubuntu-latest - - needs: - - build - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Artifact webui - uses: actions/download-artifact@v4 - with: - name: webui.tar.gz - - - name: Untar webui - run: | - tar xvf webui.tar.gz - rm webui.tar.gz - - - name: Retrieve the secret and decode it to a file - env: - TRAEFIKER_RSA: ${{ secrets.TRAEFIKER_RSA }} - run: | - mkdir -p ~/.ssh - echo "${TRAEFIKER_RSA}" | base64 --decode > ~/.ssh/traefiker_rsa - - - name: Download All Artifacts - uses: actions/download-artifact@v4 - with: - path: dist/ - pattern: "*-binaries" - merge-multiple: true - - - name: Publish Release - env: - GH_TOKEN: ${{ github.token }} - run: | - 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/ - gh release create ${VERSION} ./dist/**/traefik*.{zip,tar.gz} ./dist/traefik*.{tar.gz,txt} --repo traefik/traefik --title ${VERSION} --notes ${VERSION} - - ./script/deploy.sh - diff --git a/.github/workflows/sync-docker-images.yaml b/.github/workflows/sync-docker-images.yaml deleted file mode 100644 index 6f0b3c103..000000000 --- a/.github/workflows/sync-docker-images.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: Sync Docker Images - -on: - workflow_dispatch: - schedule: - - cron: "0 0 * * *" # Run every day - -jobs: - sync: - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - if: github.repository == 'traefik/traefik' - - steps: - - uses: actions/checkout@v4 - - - uses: imjasonh/setup-crane@v0.4 - - - name: Sync - run: | - EXCLUDED_TAGS="1.7.9-alpine v1.0.0-beta.392 v1.0.0-beta.404 v1.0.0-beta.704 v1.0.0-rc1 v1.7.9-alpine" - EXCLUDED_REGEX=$(echo $EXCLUDED_TAGS | sed 's/ /|/g') - diff <(crane ls traefik) <(crane ls ghcr.io/traefik/traefik) | grep '^<' | awk '{print $2}' | while read -r tag; do [[ "$tag" =~ ^($EXCLUDED_REGEX)$ ]] || (echo "Processing image: traefik:$tag"; crane cp "traefik:$tag" "ghcr.io/traefik/traefik:$tag"); done - crane cp traefik:latest ghcr.io/traefik/traefik:latest diff --git a/.github/workflows/template-webui.yaml b/.github/workflows/template-webui.yaml deleted file mode 100644 index df52d75c1..000000000 --- a/.github/workflows/template-webui.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build Web UI -on: - workflow_call: {} -jobs: - - build-webui: - runs-on: ubuntu-latest - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version-file: webui/.nvmrc - cache: yarn - cache-dependency-path: webui/yarn.lock - - - name: Build webui - working-directory: ./webui - run: | - yarn install - yarn build - - - name: Package webui - run: | - tar czvf webui.tar.gz ./webui/static/ - - - name: Artifact webui - uses: actions/upload-artifact@v4 - with: - name: webui.tar.gz - path: webui.tar.gz - retention-days: 1 diff --git a/.github/workflows/test-conformance.yaml b/.github/workflows/test-conformance.yaml deleted file mode 100644 index f1f02709b..000000000 --- a/.github/workflows/test-conformance.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Test K8s Gateway API conformance - -on: - pull_request: - branches: - - '*' - paths: - - '.github/workflows/test-conformance.yaml' - - 'pkg/provider/kubernetes/gateway/**' - - 'integration/fixtures/k8s-conformance/**' - - 'integration/k8s_conformance_test.go' - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - -jobs: - - test-conformance: - runs-on: ubuntu-latest - - 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: K8s Gateway API conformance test and report - run: | - make test-gateway-api-conformance - git diff --exit-code diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml deleted file mode 100644 index f8eac5dc6..000000000 --- a/.github/workflows/test-integration.yaml +++ /dev/null @@ -1,78 +0,0 @@ -name: Test Integration - -on: - pull_request: - branches: - - '*' - paths-ignore: - - 'docs/**' - - '**.md' - - 'script/gcg/**' - -env: - GO_VERSION: '1.23' - CGO_ENABLED: 0 - -jobs: - - build: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: Avoid generating webui - run: touch webui/static/index.html - - - name: Build binary - run: make binary - - test-integration: - runs-on: ubuntu-latest - 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 }} - check-latest: true - - - 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@v2.0.0 - 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 deleted file mode 100644 index 7d4a0fa66..000000000 --- a/.github/workflows/test-unit.yaml +++ /dev/null @@ -1,57 +0,0 @@ -name: Test Unit - -on: - pull_request: - branches: - - '*' - paths-ignore: - - 'docs/**' - - '**.md' - - 'script/gcg/**' - -env: - GO_VERSION: '1.23' - -jobs: - - test-unit: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: Avoid generating webui - run: touch webui/static/index.html - - - name: Tests - run: make test-unit - - test-ui-unit: - runs-on: ubuntu-latest - - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v4 - with: - node-version-file: webui/.nvmrc - cache: 'yarn' - cache-dependency-path: webui/yarn.lock - - - name: UI unit tests - run: | - yarn --cwd webui install - yarn --cwd webui test:unit:ci diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml deleted file mode 100644 index 4ed5faec5..000000000 --- a/.github/workflows/validate.yaml +++ /dev/null @@ -1,87 +0,0 @@ -name: Validate - -on: - pull_request: - branches: - - '*' - -env: - GO_VERSION: '1.23' - GOLANGCI_LINT_VERSION: v2.0.2 - MISSPELL_VERSION: v0.6.0 - -jobs: - - lint: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: golangci-lint - uses: golangci/golangci-lint-action@v7 - with: - version: "${{ env.GOLANGCI_LINT_VERSION }}" - - validate: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: Install misspell ${{ env.MISSPELL_VERSION }} - run: curl -sfL https://raw.githubusercontent.com/golangci/misspell/HEAD/install-misspell.sh | sh -s -- -b $(go env GOPATH)/bin ${MISSPELL_VERSION} - - - name: Avoid generating webui - run: touch webui/static/index.html - - - name: Validate - run: make validate-files - - validate-generate: - runs-on: ubuntu-latest - - 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 }} - check-latest: true - - - name: go generate - run: | - make generate - git diff --exit-code - - - name: go mod tidy - run: | - go mod tidy - git diff --exit-code - - - name: make generate-crd - run: | - make generate-crd - git diff --exit-code diff --git a/Dockerfile b/Dockerfile index fcf9c49b9..710d31a74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ RUN apk add --no-cache --no-progress ca-certificates tzdata ARG TARGETPLATFORM COPY ./dist/$TARGETPLATFORM/traefik / +COPY ./traefik.yml /etc/traefik/traefik.yml EXPOSE 80 VOLUME ["/tmp"] diff --git a/README.md b/README.md index 652b4e5b7..ec120999d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# My modified version of Traefik +slightly modified version of traefik, will be rebased on the latest tag from time to time +this version includes callbacks to external services on configuration update and custom docker label mappings +feel free to check out last 2 commits, they are like 100sloc together

@@ -43,14 +47,14 @@ Pointing Traefik at your orchestrator should be the _only_ configuration step yo Imagine that you have deployed a bunch of microservices with the help of an orchestrator (like Swarm or Kubernetes) or a service registry (like etcd or consul). Now you want users to access these microservices, and you need a reverse proxy. -Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice. -In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious. +Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice. +In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious. **This is when Traefik can help you!** -Traefik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part. +Traefik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part. -**Run Traefik and let it do the work for you!** +**Run Traefik and let it do the work for you!** _(But if you'd rather configure some of your routes manually, Traefik supports that too!)_ ![Architecture](docs/content/assets/img/traefik-architecture.png) From 95497fa669dbf8f4d4e829cbad660e4c70763754 Mon Sep 17 00:00:00 2001 From: "Arthur K." Date: Wed, 11 Jun 2025 15:04:44 +0300 Subject: [PATCH 5/6] feat: callbacks --- cmd/traefik/traefik.go | 6 + .../reference/static-configuration/cli-ref.md | 3 + .../reference/static-configuration/env-ref.md | 3 + .../reference/static-configuration/file.toml | 1 + .../reference/static-configuration/file.yaml | 3 + pkg/config/static/static_config.go | 1 + pkg/updater/provider.go | 49 + schema.json | 1851 +++++++++++++++++ 8 files changed, 1917 insertions(+) create mode 100644 pkg/updater/provider.go create mode 100644 schema.json diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 77930754b..413907f17 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -49,6 +49,7 @@ import ( "github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" + "github.com/traefik/traefik/v3/pkg/updater" ) func main() { @@ -197,6 +198,8 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err tsProviders := initTailscaleProviders(staticConfiguration, providerAggregator) + updaterProvider := updater.New(staticConfiguration); + // Observability metricRegistries := registerMetricClients(staticConfiguration.Metrics) @@ -386,6 +389,9 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err } }) + // Updater + watcher.AddListener(updaterProvider.HandleConfigUpdate) + return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, observabilityMgr), nil } diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index b35cbc982..2eaa9861b 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -369,6 +369,9 @@ 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 disabled by default. (Default: ```false```) +`--global.updatercallbacks`: +Callback urls for updater script (example: https://localhost:8080/callback) + `--hostresolver`: Enable CNAME Flattening. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 8f6e25c05..474fdc9ef 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -369,6 +369,9 @@ 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 disabled by default. (Default: ```false```) +`TRAEFIK_GLOBAL_UPDATERCALLBACKS`: +Callback urls for updater script (example: https://localhost:8080/callback) + `TRAEFIK_HOSTRESOLVER`: Enable CNAME Flattening. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index e548ee76d..8e96fd52e 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -3,6 +3,7 @@ [global] checkNewVersion = true sendAnonymousUsage = true + updaterCallbacks = ["foobar", "foobar"] [serversTransport] insecureSkipVerify = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 760802591..3c79d9089 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -3,6 +3,9 @@ global: checkNewVersion: true sendAnonymousUsage: true + updaterCallbacks: + - foobar + - foobar serversTransport: insecureSkipVerify: true rootCAs: diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index af60efbb8..73f740472 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -108,6 +108,7 @@ type CertificateResolver struct { 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 disabled by default." json:"sendAnonymousUsage,omitempty" toml:"sendAnonymousUsage,omitempty" yaml:"sendAnonymousUsage,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + UpdaterCallbacks []string `description:"Callback urls for updater script (example: https://localhost:8080/callback)" json:"updaterCallbacks,omitempty" toml:"updaterCallbacks,omitempty" yaml:"updaterCallbacks,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // ServersTransport options to configure communication between Traefik and the servers. diff --git a/pkg/updater/provider.go b/pkg/updater/provider.go new file mode 100644 index 000000000..26d1fc469 --- /dev/null +++ b/pkg/updater/provider.go @@ -0,0 +1,49 @@ +package updater + +import ( + "bytes" + "encoding/json" + "net/http" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/config/static" + "github.com/traefik/traefik/v3/pkg/safe" +) + +type Updater struct { + callbackUrls []string +} + +func New(config *static.Configuration) *Updater { + updater := &Updater{ + callbackUrls: config.Global.UpdaterCallbacks, + } + + return updater +} + +func (u *Updater) HandleConfigUpdate(cfg dynamic.Configuration) { + body, err := json.Marshal(cfg) + + if err != nil { + // should never happen? + log.Error().Err(err).Msg("Error while marshalling dynamic configuration data to json") + return + } + + requestBody := bytes.NewBuffer(body) + + for _, url := range u.callbackUrls { + safe.Go(func() { + resp, err := http.Post(url, "application/json", requestBody) + + if err != nil { + log.Error().Err(err).Str("url", url).Msg("Error while sending configuration data to callback") + } else { + log.Debug().Str("url", url).Msg("Configuration data sent") + resp.Body.Close() + } + }) + } +} diff --git a/schema.json b/schema.json new file mode 100644 index 000000000..0f8823ad9 --- /dev/null +++ b/schema.json @@ -0,0 +1,1851 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/traefik-v3.json", + "$defs": { + "CertificateResolverTailscaleStruct": { + "additionalProperties": false, + "type": "object" + }, + "acmeConfiguration": { + "additionalProperties": false, + "properties": { + "caCertificates": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "caServer": { + "type": "string" + }, + "caServerName": { + "type": "string" + }, + "caSystemCertPool": { + "type": "boolean" + }, + "certificatesDuration": { + "type": "integer" + }, + "dnsChallenge": { + "$ref": "#/$defs/acmeDNSChallenge" + }, + "eab": { + "$ref": "#/$defs/acmeEAB" + }, + "email": { + "type": "string" + }, + "httpChallenge": { + "$ref": "#/$defs/acmeHTTPChallenge" + }, + "keyType": { + "type": "string" + }, + "preferredChain": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "tlsChallenge": { + "$ref": "#/$defs/acmeTLSChallenge" + } + }, + "type": "object" + }, + "acmeDNSChallenge": { + "additionalProperties": false, + "properties": { + "delayBeforeCheck": { + "type": "string" + }, + "disablePropagationCheck": { + "type": "boolean" + }, + "propagation": { + "$ref": "#/$defs/acmePropagation" + }, + "provider": { + "type": "string" + }, + "resolvers": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "acmeEAB": { + "additionalProperties": false, + "properties": { + "hmacEncoded": { + "type": "string" + }, + "kid": { + "type": "string" + } + }, + "type": "object" + }, + "acmeHTTPChallenge": { + "additionalProperties": false, + "properties": { + "entryPoint": { + "type": "string" + } + }, + "type": "object" + }, + "acmePropagation": { + "additionalProperties": false, + "properties": { + "delayBeforeChecks": { + "type": "string" + }, + "disableANSChecks": { + "type": "boolean" + }, + "disableChecks": { + "type": "boolean" + }, + "requireAllRNS": { + "type": "boolean" + } + }, + "type": "object" + }, + "acmeTLSChallenge": { + "additionalProperties": false, + "type": "object" + }, + "consulProviderBuilder": { + "additionalProperties": false, + "properties": { + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "rootKey": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "consulcatalogEndpointConfig": { + "additionalProperties": false, + "properties": { + "address": { + "type": "string" + }, + "datacenter": { + "type": "string" + }, + "endpointWaitTime": { + "type": "string" + }, + "httpAuth": { + "$ref": "#/$defs/consulcatalogEndpointHTTPAuthConfig" + }, + "scheme": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "consulcatalogEndpointHTTPAuthConfig": { + "additionalProperties": false, + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "consulcatalogProviderBuilder": { + "additionalProperties": false, + "properties": { + "cache": { + "type": "boolean" + }, + "connectAware": { + "type": "boolean" + }, + "connectByDefault": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "$ref": "#/$defs/consulcatalogEndpointConfig" + }, + "exposedByDefault": { + "type": "boolean" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "prefix": { + "type": "string" + }, + "refreshInterval": { + "type": "string" + }, + "requireConsistent": { + "type": "boolean" + }, + "serviceName": { + "type": "string" + }, + "stale": { + "type": "boolean" + }, + "strictChecks": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "crdProvider": { + "additionalProperties": false, + "properties": { + "allowCrossNamespace": { + "type": "boolean" + }, + "allowEmptyServices": { + "type": "boolean" + }, + "allowExternalNameServices": { + "type": "boolean" + }, + "certAuthFilePath": { + "type": "string" + }, + "disableClusterScopeResources": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "ingressClass": { + "type": "string" + }, + "labelSelector": { + "type": "string" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "nativeLBByDefault": { + "type": "boolean" + }, + "throttleDuration": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "dockerProvider": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "exposedByDefault": { + "type": "boolean" + }, + "httpClientTimeout": { + "type": "string" + }, + "network": { + "type": "string" + }, + "password": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "useBindPortIP": { + "type": "boolean" + }, + "username": { + "type": "string" + }, + "watch": { + "type": "boolean" + }, + "labelMap": { + "type": "array", + "items": { + "type": "object", + "properties": { + "from": { + "type": "string", + "description": "Shorthand label." + }, + "to": { + "type": "string", + "description": "Full label with templates." + }, + "value": { + "type": "string", + "description": "Optional override; used instead of user input if set." + } + }, + "required": ["from", "to"], + "additionalProperties": false + } + } + }, + "type": "object" + }, + "dockerSwarmProvider": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "exposedByDefault": { + "type": "boolean" + }, + "httpClientTimeout": { + "type": "string" + }, + "network": { + "type": "string" + }, + "password": { + "type": "string" + }, + "refreshSeconds": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "useBindPortIP": { + "type": "boolean" + }, + "username": { + "type": "string" + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "ecsProvider": { + "additionalProperties": false, + "properties": { + "accessKeyID": { + "type": "string" + }, + "autoDiscoverClusters": { + "type": "boolean" + }, + "clusters": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "ecsAnywhere": { + "type": "boolean" + }, + "exposedByDefault": { + "type": "boolean" + }, + "healthyTasksOnly": { + "type": "boolean" + }, + "refreshSeconds": { + "type": "integer" + }, + "region": { + "type": "string" + }, + "secretAccessKey": { + "type": "string" + } + }, + "type": "object" + }, + "etcdProvider": { + "additionalProperties": false, + "properties": { + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "password": { + "type": "string" + }, + "rootKey": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "fileProvider": { + "additionalProperties": false, + "properties": { + "debugLogGeneratedTemplate": { + "type": "boolean" + }, + "directory": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "gatewayProvider": { + "additionalProperties": false, + "properties": { + "certAuthFilePath": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "experimentalChannel": { + "type": "boolean" + }, + "labelSelector": { + "type": "string" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "nativeLBByDefault": { + "type": "boolean" + }, + "statusAddress": { + "$ref": "#/$defs/gatewayStatusAddress" + }, + "throttleDuration": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "gatewayServiceRef": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "type": "object" + }, + "gatewayStatusAddress": { + "additionalProperties": false, + "properties": { + "hostname": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "service": { + "$ref": "#/$defs/gatewayServiceRef" + } + }, + "type": "object" + }, + "httpProvider": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "pollInterval": { + "type": "string" + }, + "pollTimeout": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + } + }, + "required": ["endpoint"], + "type": "object" + }, + "ingressEndpointIngress": { + "additionalProperties": false, + "properties": { + "hostname": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "publishedService": { + "type": "string" + } + }, + "type": "object" + }, + "ingressProvider": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "allowExternalNameServices": { + "type": "boolean" + }, + "certAuthFilePath": { + "type": "string" + }, + "disableClusterScopeResources": { + "type": "boolean" + }, + "disableIngressClassLookup": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "ingressClass": { + "type": "string" + }, + "ingressEndpoint": { + "$ref": "#/$defs/ingressEndpointIngress" + }, + "labelSelector": { + "type": "string" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "nativeLBByDefault": { + "type": "boolean" + }, + "throttleDuration": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "nomadEndpointConfig": { + "additionalProperties": false, + "properties": { + "address": { + "type": "string" + }, + "endpointWaitTime": { + "type": "string" + }, + "region": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "nomadProviderBuilder": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "$ref": "#/$defs/nomadEndpointConfig" + }, + "exposedByDefault": { + "type": "boolean" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "prefix": { + "type": "string" + }, + "refreshInterval": { + "type": "string" + }, + "stale": { + "type": "boolean" + }, + "throttleDuration": { + "type": "string" + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "pingHandler": { + "additionalProperties": false, + "properties": { + "entryPoint": { + "type": "string" + }, + "manualRouting": { + "type": "boolean" + }, + "terminatingStatusCode": { + "type": "integer" + } + }, + "type": "object" + }, + "pluginsDescriptor": { + "additionalProperties": false, + "properties": { + "moduleName": { + "type": "string" + }, + "settings": { + "$ref": "#/$defs/pluginsSettings" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "pluginsLocalDescriptor": { + "additionalProperties": false, + "properties": { + "moduleName": { + "type": "string" + }, + "settings": { + "$ref": "#/$defs/pluginsSettings" + } + }, + "type": "object" + }, + "pluginsSettings": { + "additionalProperties": false, + "properties": { + "envs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "mounts": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "redisProvider": { + "additionalProperties": false, + "properties": { + "db": { + "type": "integer" + }, + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "password": { + "type": "string" + }, + "rootKey": { + "type": "string" + }, + "sentinel": { + "$ref": "#/$defs/redisSentinel" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "redisSentinel": { + "additionalProperties": false, + "properties": { + "latencyStrategy": { + "type": "boolean" + }, + "masterName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "randomStrategy": { + "type": "boolean" + }, + "replicaStrategy": { + "type": "boolean" + }, + "useDisconnectedReplicas": { + "type": "boolean" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "restProvider": { + "additionalProperties": false, + "properties": { + "insecure": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticAPI": { + "additionalProperties": false, + "properties": { + "basePath": { + "type": "string" + }, + "dashboard": { + "type": "boolean" + }, + "debug": { + "type": "boolean" + }, + "disableDashboardAd": { + "type": "boolean" + }, + "insecure": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticCertificateResolver": { + "additionalProperties": false, + "properties": { + "acme": { + "$ref": "#/$defs/acmeConfiguration" + }, + "tailscale": { + "$ref": "#/$defs/CertificateResolverTailscaleStruct" + } + }, + "type": "object" + }, + "staticCore": { + "additionalProperties": false, + "properties": { + "defaultRuleSyntax": { + "type": "string" + } + }, + "type": "object" + }, + "staticEntryPoint": { + "additionalProperties": false, + "properties": { + "address": { + "type": "string" + }, + "allowACMEByPass": { + "type": "boolean" + }, + "asDefault": { + "type": "boolean" + }, + "forwardedHeaders": { + "$ref": "#/$defs/staticForwardedHeaders" + }, + "http": { + "$ref": "#/$defs/staticHTTPConfig" + }, + "http2": { + "$ref": "#/$defs/staticHTTP2Config" + }, + "http3": { + "$ref": "#/$defs/staticHTTP3Config" + }, + "observability": { + "$ref": "#/$defs/staticObservabilityConfig" + }, + "proxyProtocol": { + "$ref": "#/$defs/staticProxyProtocol" + }, + "reusePort": { + "type": "boolean" + }, + "transport": { + "$ref": "#/$defs/staticEntryPointsTransport" + }, + "udp": { + "$ref": "#/$defs/staticUDPConfig" + } + }, + "type": "object" + }, + "staticEntryPointsTransport": { + "additionalProperties": false, + "properties": { + "keepAliveMaxRequests": { + "type": "integer" + }, + "keepAliveMaxTime": { + "type": "string" + }, + "lifeCycle": { + "$ref": "#/$defs/staticLifeCycle" + }, + "respondingTimeouts": { + "$ref": "#/$defs/staticRespondingTimeouts" + } + }, + "type": "object" + }, + "staticExperimental": { + "additionalProperties": false, + "properties": { + "abortOnPluginFailure": { + "type": "boolean" + }, + "fastProxy": { + "$ref": "#/$defs/staticFastProxyConfig" + }, + "kubernetesGateway": { + "type": "boolean" + }, + "localPlugins": { + "additionalProperties": { + "$ref": "#/$defs/pluginsLocalDescriptor" + }, + "type": "object" + }, + "otlplogs": { + "type": "boolean" + }, + "plugins": { + "additionalProperties": { + "$ref": "#/$defs/pluginsDescriptor" + }, + "type": "object" + } + }, + "type": "object" + }, + "staticFastProxyConfig": { + "additionalProperties": false, + "properties": { + "debug": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticForwardedHeaders": { + "additionalProperties": false, + "properties": { + "connection": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "insecure": { + "type": "boolean" + }, + "trustedIPs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "staticForwardingTimeouts": { + "additionalProperties": false, + "properties": { + "dialTimeout": { + "type": "string" + }, + "idleConnTimeout": { + "type": "string" + }, + "responseHeaderTimeout": { + "type": "string" + } + }, + "type": "object" + }, + "staticGlobal": { + "additionalProperties": false, + "properties": { + "checkNewVersion": { + "type": "boolean" + }, + "sendAnonymousUsage": { + "type": "boolean" + }, + "updaterCallbacks": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" + }, + "staticHTTP2Config": { + "additionalProperties": false, + "properties": { + "maxConcurrentStreams": { + "type": "integer" + } + }, + "type": "object" + }, + "staticHTTP3Config": { + "additionalProperties": false, + "properties": { + "advertisedPort": { + "type": "integer" + } + }, + "type": "object" + }, + "staticHTTPConfig": { + "additionalProperties": false, + "properties": { + "encodeQuerySemicolons": { + "type": "boolean" + }, + "maxHeaderBytes": { + "type": "integer" + }, + "middlewares": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "redirections": { + "$ref": "#/$defs/staticRedirections" + }, + "tls": { + "$ref": "#/$defs/staticTLSConfig" + } + }, + "type": "object" + }, + "staticLifeCycle": { + "additionalProperties": false, + "properties": { + "graceTimeOut": { + "type": "string" + }, + "requestAcceptGraceTimeout": { + "type": "string" + } + }, + "type": "object" + }, + "staticObservabilityConfig": { + "additionalProperties": false, + "properties": { + "accessLogs": { + "type": "boolean" + }, + "metrics": { + "type": "boolean" + }, + "tracing": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticProviders": { + "additionalProperties": false, + "properties": { + "consul": { + "$ref": "#/$defs/consulProviderBuilder" + }, + "consulCatalog": { + "$ref": "#/$defs/consulcatalogProviderBuilder" + }, + "docker": { + "$ref": "#/$defs/dockerProvider" + }, + "ecs": { + "$ref": "#/$defs/ecsProvider" + }, + "etcd": { + "$ref": "#/$defs/etcdProvider" + }, + "file": { + "$ref": "#/$defs/fileProvider" + }, + "http": { + "$ref": "#/$defs/httpProvider" + }, + "kubernetesCRD": { + "$ref": "#/$defs/crdProvider" + }, + "kubernetesGateway": { + "$ref": "#/$defs/gatewayProvider" + }, + "kubernetesIngress": { + "$ref": "#/$defs/ingressProvider" + }, + "nomad": { + "$ref": "#/$defs/nomadProviderBuilder" + }, + "plugin": { + "additionalProperties": { + "additionalProperties": {}, + "type": "object" + }, + "type": "object" + }, + "providersThrottleDuration": { + "type": "string" + }, + "redis": { + "$ref": "#/$defs/redisProvider" + }, + "rest": { + "$ref": "#/$defs/restProvider" + }, + "swarm": { + "$ref": "#/$defs/dockerSwarmProvider" + }, + "zooKeeper": { + "$ref": "#/$defs/zkProvider" + } + }, + "type": "object" + }, + "staticProxyProtocol": { + "additionalProperties": false, + "properties": { + "insecure": { + "type": "boolean" + }, + "trustedIPs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "staticRedirectEntryPoint": { + "additionalProperties": false, + "properties": { + "permanent": { + "type": "boolean" + }, + "priority": { + "type": "integer" + }, + "scheme": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "type": "object" + }, + "staticRedirections": { + "additionalProperties": false, + "properties": { + "entryPoint": { + "$ref": "#/$defs/staticRedirectEntryPoint" + } + }, + "type": "object" + }, + "staticRespondingTimeouts": { + "additionalProperties": false, + "properties": { + "idleTimeout": { + "type": "string" + }, + "readTimeout": { + "type": "string" + }, + "writeTimeout": { + "type": "string" + } + }, + "type": "object" + }, + "staticServersTransport": { + "additionalProperties": false, + "properties": { + "forwardingTimeouts": { + "$ref": "#/$defs/staticForwardingTimeouts" + }, + "insecureSkipVerify": { + "type": "boolean" + }, + "maxIdleConnsPerHost": { + "type": "integer" + }, + "rootCAs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "spiffe": { + "$ref": "#/$defs/staticSpiffe" + } + }, + "type": "object" + }, + "staticSpiffe": { + "additionalProperties": false, + "properties": { + "ids": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "trustDomain": { + "type": "string" + } + }, + "type": "object" + }, + "staticSpiffeClientConfig": { + "additionalProperties": false, + "properties": { + "workloadAPIAddr": { + "type": "string" + } + }, + "type": "object" + }, + "staticTCPServersTransport": { + "additionalProperties": false, + "properties": { + "dialKeepAlive": { + "type": "string" + }, + "dialTimeout": { + "type": "string" + }, + "terminationDelay": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/staticTLSClientConfig" + } + }, + "type": "object" + }, + "staticTLSClientConfig": { + "additionalProperties": false, + "properties": { + "insecureSkipVerify": { + "type": "boolean" + }, + "rootCAs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "spiffe": { + "$ref": "#/$defs/staticSpiffe" + } + }, + "type": "object" + }, + "staticTLSConfig": { + "additionalProperties": false, + "properties": { + "certResolver": { + "type": "string" + }, + "domains": { + "items": { + "$ref": "#/$defs/typesDomain" + }, + "type": ["array", "null"] + }, + "options": { + "type": "string" + } + }, + "type": "object" + }, + "staticTracing": { + "additionalProperties": false, + "properties": { + "addInternals": { + "type": "boolean" + }, + "capturedRequestHeaders": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "capturedResponseHeaders": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "globalAttributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "otlp": { + "$ref": "#/$defs/typesOTelTracing" + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "safeQueryParams": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "sampleRate": { + "type": "number" + }, + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "staticUDPConfig": { + "additionalProperties": false, + "properties": { + "timeout": { + "type": "string" + } + }, + "type": "object" + }, + "typesAccessLog": { + "additionalProperties": false, + "properties": { + "addInternals": { + "type": "boolean" + }, + "bufferingSize": { + "type": "integer" + }, + "fields": { + "$ref": "#/$defs/typesAccessLogFields" + }, + "filePath": { + "type": "string" + }, + "filters": { + "$ref": "#/$defs/typesAccessLogFilters" + }, + "format": { + "type": "string" + }, + "otlp": { + "$ref": "#/$defs/typesOTelLog" + } + }, + "type": "object" + }, + "typesAccessLogFields": { + "additionalProperties": false, + "properties": { + "defaultMode": { + "type": "string" + }, + "headers": { + "$ref": "#/$defs/typesFieldHeaders" + }, + "names": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "typesAccessLogFilters": { + "additionalProperties": false, + "properties": { + "minDuration": { + "type": "string" + }, + "retryAttempts": { + "type": "boolean" + }, + "statusCodes": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "typesClientTLS": { + "additionalProperties": false, + "properties": { + "ca": { + "type": "string" + }, + "cert": { + "type": "string" + }, + "insecureSkipVerify": { + "type": "boolean" + }, + "key": { + "type": "string" + } + }, + "type": "object" + }, + "typesDatadog": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "address": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "pushInterval": { + "type": "string" + } + }, + "type": "object" + }, + "typesDomain": { + "additionalProperties": false, + "properties": { + "main": { + "type": "string" + }, + "sans": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "typesFieldHeaders": { + "additionalProperties": false, + "properties": { + "defaultMode": { + "type": "string" + }, + "names": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "typesHostResolverConfig": { + "additionalProperties": false, + "properties": { + "cnameFlattening": { + "type": "boolean" + }, + "resolvConfig": { + "type": "string" + }, + "resolvDepth": { + "type": "integer" + } + }, + "type": "object" + }, + "typesInfluxDB2": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "additionalLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "address": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "org": { + "type": "string" + }, + "pushInterval": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "typesMetrics": { + "additionalProperties": false, + "properties": { + "addInternals": { + "type": "boolean" + }, + "datadog": { + "$ref": "#/$defs/typesDatadog" + }, + "influxDB2": { + "$ref": "#/$defs/typesInfluxDB2" + }, + "otlp": { + "$ref": "#/$defs/typesOTLP" + }, + "prometheus": { + "$ref": "#/$defs/typesPrometheus" + }, + "statsD": { + "$ref": "#/$defs/typesStatsd" + } + }, + "type": "object" + }, + "typesOTLP": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "explicitBoundaries": { + "items": { + "type": "number" + }, + "type": ["array", "null"] + }, + "grpc": { + "$ref": "#/$defs/typesOTelGRPC" + }, + "http": { + "$ref": "#/$defs/typesOTelHTTP" + }, + "pushInterval": { + "type": "string" + }, + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "typesOTelGRPC": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "insecure": { + "type": "boolean" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + } + }, + "type": "object" + }, + "typesOTelHTTP": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + } + }, + "type": "object" + }, + "typesOTelLog": { + "additionalProperties": false, + "properties": { + "grpc": { + "$ref": "#/$defs/typesOTelGRPC" + }, + "http": { + "$ref": "#/$defs/typesOTelHTTP" + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "typesOTelTracing": { + "additionalProperties": false, + "properties": { + "grpc": { + "$ref": "#/$defs/typesOTelGRPC" + }, + "http": { + "$ref": "#/$defs/typesOTelHTTP" + } + }, + "type": "object" + }, + "typesPrometheus": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "buckets": { + "items": { + "type": "number" + }, + "type": ["array", "null"] + }, + "entryPoint": { + "type": "string" + }, + "headerLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "manualRouting": { + "type": "boolean" + } + }, + "type": "object" + }, + "typesStatsd": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "address": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "pushInterval": { + "type": "string" + } + }, + "type": "object" + }, + "typesTraefikLog": { + "additionalProperties": false, + "properties": { + "compress": { + "type": "boolean" + }, + "filePath": { + "type": "string" + }, + "format": { + "type": "string" + }, + "level": { + "type": "string" + }, + "maxAge": { + "type": "integer" + }, + "maxBackups": { + "type": "integer" + }, + "maxSize": { + "type": "integer" + }, + "noColor": { + "type": "boolean" + }, + "otlp": { + "$ref": "#/$defs/typesOTelLog" + } + }, + "type": "object" + }, + "zkProvider": { + "additionalProperties": false, + "properties": { + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "password": { + "type": "string" + }, + "rootKey": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + } + }, + "title": "Traefik v3 Static Configuration", + "properties": { + "accessLog": { + "$ref": "#/$defs/typesAccessLog" + }, + "api": { + "$ref": "#/$defs/staticAPI" + }, + "certificatesResolvers": { + "additionalProperties": { + "$ref": "#/$defs/staticCertificateResolver" + }, + "type": "object" + }, + "core": { + "$ref": "#/$defs/staticCore" + }, + "entryPoints": { + "additionalProperties": { + "$ref": "#/$defs/staticEntryPoint" + }, + "type": "object" + }, + "experimental": { + "$ref": "#/$defs/staticExperimental" + }, + "global": { + "$ref": "#/$defs/staticGlobal" + }, + "hostResolver": { + "$ref": "#/$defs/typesHostResolverConfig" + }, + "log": { + "$ref": "#/$defs/typesTraefikLog" + }, + "metrics": { + "$ref": "#/$defs/typesMetrics" + }, + "ping": { + "$ref": "#/$defs/pingHandler" + }, + "providers": { + "$ref": "#/$defs/staticProviders" + }, + "serversTransport": { + "$ref": "#/$defs/staticServersTransport" + }, + "spiffe": { + "$ref": "#/$defs/staticSpiffeClientConfig" + }, + "tcpServersTransport": { + "$ref": "#/$defs/staticTCPServersTransport" + }, + "tracing": { + "$ref": "#/$defs/staticTracing" + } + }, + "type": "object" +} From bebd988162a584bbfa169ccd85e45439dee69bd8 Mon Sep 17 00:00:00 2001 From: "Arthur K." Date: Wed, 11 Jun 2025 15:04:44 +0300 Subject: [PATCH 6/6] feat: custom label shorthands --- .../reference/static-configuration/cli-ref.md | 24 ++++++++++ .../reference/static-configuration/env-ref.md | 24 ++++++++++ .../reference/static-configuration/file.toml | 20 ++++++++ .../reference/static-configuration/file.yaml | 14 ++++++ pkg/provider/configuration.go | 11 +++-- pkg/provider/docker/config.go | 48 ++++++++++++++----- pkg/provider/docker/pdocker.go | 9 +++- pkg/provider/docker/shared.go | 10 ++++ 8 files changed, 144 insertions(+), 16 deletions(-) diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 2eaa9861b..193f0c65d 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -768,6 +768,18 @@ Expose containers by default. (Default: ```true```) `--providers.docker.httpclienttimeout`: Client timeout for HTTP connections. (Default: ```0```) +`--providers.docker.labelmap`: +Label shorthands. + +`--providers.docker.labelmap[n].from`: +Shorthand label. + +`--providers.docker.labelmap[n].to`: +Full label with templates. + +`--providers.docker.labelmap[n].value`: +Optional override; used instead of user input if set. + `--providers.docker.network`: Default Docker network used. @@ -1167,6 +1179,18 @@ Expose containers by default. (Default: ```true```) `--providers.swarm.httpclienttimeout`: Client timeout for HTTP connections. (Default: ```0```) +`--providers.swarm.labelmap`: +Label shorthands. + +`--providers.swarm.labelmap[n].from`: +Shorthand label. + +`--providers.swarm.labelmap[n].to`: +Full label with templates. + +`--providers.swarm.labelmap[n].value`: +Optional override; used instead of user input if set. + `--providers.swarm.network`: Default Docker network used. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 474fdc9ef..849d058ab 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -768,6 +768,18 @@ Expose containers by default. (Default: ```true```) `TRAEFIK_PROVIDERS_DOCKER_HTTPCLIENTTIMEOUT`: Client timeout for HTTP connections. (Default: ```0```) +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP`: +Label shorthands. + +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP_n_FROM`: +Shorthand label. + +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP_n_TO`: +Full label with templates. + +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP_n_VALUE`: +Optional override; used instead of user input if set. + `TRAEFIK_PROVIDERS_DOCKER_NETWORK`: Default Docker network used. @@ -1167,6 +1179,18 @@ Expose containers by default. (Default: ```true```) `TRAEFIK_PROVIDERS_SWARM_HTTPCLIENTTIMEOUT`: Client timeout for HTTP connections. (Default: ```0```) +`TRAEFIK_PROVIDERS_SWARM_LABELMAP`: +Label shorthands. + +`TRAEFIK_PROVIDERS_SWARM_LABELMAP_n_FROM`: +Shorthand label. + +`TRAEFIK_PROVIDERS_SWARM_LABELMAP_n_TO`: +Full label with templates. + +`TRAEFIK_PROVIDERS_SWARM_LABELMAP_n_VALUE`: +Optional override; used instead of user input if set. + `TRAEFIK_PROVIDERS_SWARM_NETWORK`: Default Docker network used. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 8e96fd52e..13fd6b2bc 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -98,6 +98,16 @@ password = "foobar" endpoint = "foobar" httpClientTimeout = "42s" + + [[providers.docker.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" + + [[providers.docker.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" [providers.docker.tls] ca = "foobar" cert = "foobar" @@ -116,6 +126,16 @@ endpoint = "foobar" httpClientTimeout = "42s" refreshSeconds = "42s" + + [[providers.swarm.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" + + [[providers.swarm.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" [providers.swarm.tls] ca = "foobar" cert = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 3c79d9089..4c4fdc109 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -109,6 +109,13 @@ providers: useBindPortIP: true watch: true defaultRule: foobar + labelMap: + - from: foobar + to: foobar + value: foobar + - from: foobar + to: foobar + value: foobar username: foobar password: foobar endpoint: foobar @@ -126,6 +133,13 @@ providers: useBindPortIP: true watch: true defaultRule: foobar + labelMap: + - from: foobar + to: foobar + value: foobar + - from: foobar + to: foobar + value: foobar username: foobar password: foobar endpoint: foobar diff --git a/pkg/provider/configuration.go b/pkg/provider/configuration.go index bbb393425..70dd14144 100644 --- a/pkg/provider/configuration.go +++ b/pkg/provider/configuration.go @@ -396,8 +396,8 @@ func AddStore(configuration *dynamic.TLSConfiguration, storeName string, store t return reflect.DeepEqual(configuration.Stores[storeName], store) } -// MakeDefaultRuleTemplate creates the default rule template. -func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*template.Template, error) { +// MakeAnyTemplate creates a template with any name +func MakeAnyTemplate(name string, value string, funcMap template.FuncMap) (*template.Template, error) { defaultFuncMap := sprig.TxtFuncMap() defaultFuncMap["normalize"] = Normalize @@ -405,7 +405,12 @@ func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*tem defaultFuncMap[k] = fn } - return template.New("defaultRule").Funcs(defaultFuncMap).Parse(defaultRule) + return template.New(name).Funcs(defaultFuncMap).Parse(value) +} + +// MakeDefaultRuleTemplate creates the default rule template. +func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*template.Template, error) { + return MakeAnyTemplate("defaultRule", defaultRule, funcMap) } // BuildTCPRouterConfiguration builds a router configuration. diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go index b3bbe2411..3a6e75bee 100644 --- a/pkg/provider/docker/config.go +++ b/pkg/provider/docker/config.go @@ -1,6 +1,7 @@ package docker import ( + "bytes" "context" "errors" "fmt" @@ -28,15 +29,50 @@ func NewDynConfBuilder(configuration Shared, apiClient client.APIClient, swarm b return &DynConfBuilder{Shared: configuration, apiClient: apiClient, swarm: swarm} } +func (p *DynConfBuilder) applyLabels(container *dockerData, model interface{}) { + for _, item := range p.LabelMap { + value, ok := container.Labels[item.From] + if !ok { // label doesn't exist + continue + } + + writer := &bytes.Buffer{} + if err := item.toTpl.Execute(writer, model); err != nil { + continue // should never happen? + } + + to := writer.String() + if item.Value != nil { + container.Labels[to] = *item.Value + } else { + container.Labels[to] = value + } + } +} + func (p *DynConfBuilder) build(ctx context.Context, containersInspected []dockerData) *dynamic.Configuration { configurations := make(map[string]*dynamic.Configuration) for _, container := range containersInspected { + serviceName := getServiceName(container) + + model := struct { + Name string + ContainerName string + Labels *map[string]string + }{ + Name: serviceName, + ContainerName: strings.TrimPrefix(container.Name, "/"), + Labels: &container.Labels, + } + containerName := getServiceName(container) + "-" + container.ID logger := log.Ctx(ctx).With().Str("container", containerName).Logger() ctxContainer := logger.WithContext(ctx) + p.applyLabels(&container, model) + if !p.keepContainer(ctxContainer, container) { continue } @@ -83,18 +119,6 @@ func (p *DynConfBuilder) build(ctx context.Context, containersInspected []docker continue } - serviceName := getServiceName(container) - - model := struct { - Name string - ContainerName string - Labels map[string]string - }{ - Name: serviceName, - ContainerName: strings.TrimPrefix(container.Name, "/"), - Labels: container.Labels, - } - provider.BuildRouterConfiguration(ctx, confFromLabel.HTTP, serviceName, p.defaultRuleTpl, model) configurations[containerName] = confFromLabel diff --git a/pkg/provider/docker/pdocker.go b/pkg/provider/docker/pdocker.go index 44ff3470d..8deb4f146 100644 --- a/pkg/provider/docker/pdocker.go +++ b/pkg/provider/docker/pdocker.go @@ -49,8 +49,15 @@ func (p *Provider) Init() error { if err != nil { return fmt.Errorf("error while parsing default rule: %w", err) } - p.defaultRuleTpl = defaultRuleTpl + + for _, item := range p.LabelMap { + toTpl, err := provider.MakeAnyTemplate(item.From, item.To, nil) + if err != nil { + return fmt.Errorf("error while parsing label %v: %w", item.To, err) + } + item.toTpl = toTpl + } return nil } diff --git a/pkg/provider/docker/shared.go b/pkg/provider/docker/shared.go index 97c8d519b..027c398fd 100644 --- a/pkg/provider/docker/shared.go +++ b/pkg/provider/docker/shared.go @@ -33,9 +33,19 @@ type Shared struct { Watch bool `description:"Watch Docker events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"` DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"` + LabelMap []*LabelMapItem `description:"Label shorthands." json:"labelMap,omitempty" toml:"labelMap,omitempty" yaml:"labelMap,omitempty"` + defaultRuleTpl *template.Template } +type LabelMapItem struct { + From string `description:"Shorthand label." json:"from,omitempty" toml:"from,omitempty" yaml:"from,omitempty"` + To string `description:"Full label with templates." json:"to,omitempty" toml:"to,omitempty" yaml:"to,omitempty"` + Value *string `description:"Optional override; used instead of user input if set." json:"value,omitempty" toml:"value,omitempty" yaml:"value,omitempty"` + toTpl *template.Template +} + + func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData { containerInspected, err := dockerClient.ContainerInspect(ctx, containerID) if err != nil {