Compare commits
	
		
			4 Commits
		
	
	
		
			v3.6.0-rc1
			...
			v2.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 1f81260694 | ||
|  | cbcd999257 | ||
|  | a0724fd1ca | ||
|  | 3fe9c9ab58 | 
| @@ -1,5 +1,3 @@ | ||||
| dist/ | ||||
| !dist/**/traefik | ||||
| !dist/traefik | ||||
| site/ | ||||
| vendor/ | ||||
| .idea/ | ||||
|   | ||||
							
								
								
									
										24
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,24 @@ | ||||
| provider/kubernetes/**  @containous/kubernetes | ||||
| provider/rancher/**     @containous/rancher | ||||
| provider/marathon/**    @containous/marathon | ||||
| provider/docker/**      @containous/docker | ||||
|  | ||||
| docs/user-guide/kubernetes.md       @containous/kubernetes | ||||
| docs/user-guide/marathon.md         @containous/marathon | ||||
| docs/user-guide/swarm.md            @containous/docker | ||||
| docs/user-guide/swarm-mode.md       @containous/docker | ||||
|  | ||||
| docs/configuration/backends/docker.md       @containous/docker | ||||
| docs/configuration/backends/kubernetes.md   @containous/kubernetes | ||||
| docs/configuration/backends/marathon.md     @containous/marathon | ||||
| docs/configuration/backends/rancher.md      @containous/rancher | ||||
|  | ||||
| examples/k8s/                   @containous/kubernetes | ||||
| examples/compose-k8s.yaml       @containous/kubernetes | ||||
| examples/k8s.namespace.yaml     @containous/kubernetes | ||||
| examples/compose-rancher.yml    @containous/rancher | ||||
| examples/compose-marathon.yml   @containous/marathon | ||||
|  | ||||
| vendor/github.com/gambol99/go-marathon  @containous/marathon | ||||
| vendor/github.com/rancher               @containous/rancher | ||||
| vendor/k8s.io/                          @containous/kubernetes | ||||
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,3 +0,0 @@ | ||||
| # These are supported funding model platforms | ||||
|  | ||||
| github: traefik | ||||
							
								
								
									
										4
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -8,7 +8,7 @@ DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS. | ||||
| 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/ | ||||
| - the Traefik community forum: https://community.containo.us/ | ||||
|  | ||||
| --> | ||||
|  | ||||
| @@ -17,7 +17,7 @@ Bug | ||||
| <!-- | ||||
|  | ||||
| The configurations between 1.X and 2.X are NOT compatible. | ||||
| Please have a look here https://doc.traefik.io/traefik/getting-started/configuration-overview/. | ||||
| Please have a look here https://docs.traefik.io/v2.0/getting-started/configuration-overview/. | ||||
|  | ||||
| --> | ||||
|  | ||||
|   | ||||
							
								
								
									
										82
									
								
								.github/ISSUE_TEMPLATE/Bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,82 @@ | ||||
| --- | ||||
| name: Bug report | ||||
| about: Create a report to help us improve | ||||
|  | ||||
| --- | ||||
| <!-- PLEASE FOLLOW THE ISSUE TEMPLATE TO HELP TRIAGE AND SUPPORT! --> | ||||
|  | ||||
| ### Do you want to request a *feature* or report a *bug*? | ||||
|  | ||||
| <!-- | ||||
| DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS. | ||||
|  | ||||
| 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.containo.us/ | ||||
|  | ||||
| --> | ||||
|  | ||||
| Bug | ||||
|  | ||||
| <!-- | ||||
|  | ||||
| The configurations between 1.X and 2.X are NOT compatible. | ||||
| Please have a look here https://docs.traefik.io/v2.0/getting-started/configuration-overview/. | ||||
|  | ||||
| --> | ||||
|  | ||||
| ### What did you do? | ||||
|  | ||||
| <!-- | ||||
|  | ||||
| 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 | ||||
|  | ||||
| --> | ||||
|  | ||||
| ### What did you expect to see? | ||||
|  | ||||
|  | ||||
|  | ||||
| ### What did you see instead? | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Output of `traefik version`: (_What version of Traefik are you using?_) | ||||
|  | ||||
| <!-- | ||||
| `latest` is not considered as a valid version. | ||||
|  | ||||
| For the Traefik Docker image: | ||||
|     docker run [IMAGE] version | ||||
|     ex: docker run traefik version | ||||
|  | ||||
| --> | ||||
|  | ||||
| ``` | ||||
| (paste your output here) | ||||
| ``` | ||||
|  | ||||
| ### What is your environment & configuration (arguments, toml, provider, platform, ...)? | ||||
|  | ||||
| ```toml | ||||
| # (paste your configuration here) | ||||
| ``` | ||||
|  | ||||
| <!-- | ||||
| Add more configuration information here. | ||||
| --> | ||||
|  | ||||
|  | ||||
| ### If applicable, please paste the log output in DEBUG level (`--log.level=DEBUG` switch) | ||||
|  | ||||
| ``` | ||||
| (paste your output here) | ||||
| ``` | ||||
							
								
								
									
										35
									
								
								.github/ISSUE_TEMPLATE/Feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,35 @@ | ||||
| --- | ||||
| name: Feature request | ||||
| about: Suggest an idea for this project | ||||
|  | ||||
| --- | ||||
| <!-- PLEASE FOLLOW THE ISSUE TEMPLATE TO HELP TRIAGE AND SUPPORT! --> | ||||
|  | ||||
| ### Do you want to request a *feature* or report a *bug*? | ||||
|  | ||||
| <!-- | ||||
| DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS. | ||||
|  | ||||
| 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.containo.us/ | ||||
|  | ||||
| --> | ||||
|  | ||||
| Feature | ||||
|  | ||||
| ### What did you expect to see? | ||||
|  | ||||
| <!-- | ||||
|  | ||||
| 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 | ||||
|  | ||||
| --> | ||||
							
								
								
									
										82
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -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 | ||||
							
								
								
									
										8
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -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. | ||||
							
								
								
									
										33
									
								
								.github/ISSUE_TEMPLATE/feature-request.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -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 | ||||
							
								
								
									
										17
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,18 +1,19 @@ | ||||
| <!-- | ||||
| PLEASE READ THIS MESSAGE. | ||||
|  | ||||
| Documentation: | ||||
| - for Traefik v2: use branch v2.11 (fixes only) | ||||
| - for Traefik v3: use branch v3.5 | ||||
| Documentation fixes or enhancements: | ||||
| - for Traefik v1: use branch v1.7 | ||||
| - for Traefik v2: use branch v2.0 | ||||
|  | ||||
| Bug: | ||||
| - for Traefik v2: use branch v2.11 (security fixes only) | ||||
| - for Traefik v3: use branch v3.5 | ||||
| Bug fixes: | ||||
| - for Traefik v1: use branch v1.7 | ||||
| - for Traefik v2: use branch v2.0 | ||||
|  | ||||
| Enhancements: | ||||
| - use branch master | ||||
| - for Traefik v1: we only accept bug fixes | ||||
| - for Traefik v2: use branch master | ||||
|  | ||||
| HOW TO WRITE A GOOD PULL REQUEST? https://doc.traefik.io/traefik/contributing/submitting-pull-requests/ | ||||
| HOW TO WRITE A GOOD PULL REQUEST? https://docs.traefik.io/contributing/submitting-pull-requests/ | ||||
|  | ||||
| --> | ||||
|  | ||||
|   | ||||
							
								
								
									
										81
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,81 +0,0 @@ | ||||
| name: Build Binaries | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - '*' | ||||
|     paths-ignore: | ||||
|       - 'docs/**' | ||||
|       - '**.md' | ||||
|       - 'script/gcg/**' | ||||
|  | ||||
| env: | ||||
|   GO_VERSION: '1.24' | ||||
|   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@v5 | ||||
|         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 | ||||
							
								
								
									
										25
									
								
								.github/workflows/check_doc.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -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@v5 | ||||
|         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 }} | ||||
							
								
								
									
										70
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -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@v5 | ||||
|  | ||||
|     - 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}}" | ||||
							
								
								
									
										53
									
								
								.github/workflows/documentation.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,53 +0,0 @@ | ||||
| name: Build and Publish Documentation | ||||
|  | ||||
| on: | ||||
|   workflow_dispatch: {} | ||||
|   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@v5 | ||||
|         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: | | ||||
|           STRUCTOR_LATEST_TAG=$(curl -s https://api.github.com/repos/traefik/traefik/releases/latest | jq -r '.tag_name') | ||||
|           $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 | ||||
|  | ||||
|       - 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 }} | ||||
							
								
								
									
										70
									
								
								.github/workflows/experimental.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,70 +0,0 @@ | ||||
| name: Build experimental image on branch | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - master | ||||
|       - v* | ||||
|  | ||||
| env: | ||||
|   GO_VERSION: '1.24' | ||||
|   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@v5 | ||||
|         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##*/} | ||||
							
								
								
									
										136
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,136 +0,0 @@ | ||||
| name: Release | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     tags: | ||||
|       - 'v*.*.*' | ||||
|  | ||||
| env: | ||||
|   GO_VERSION: '1.24' | ||||
|   CGO_ENABLED: 0 | ||||
|   VERSION: ${{ github.ref_name }} | ||||
|   TRAEFIKER_EMAIL: "traefiker@traefik.io" | ||||
|   CODENAME: ramequin | ||||
|  | ||||
| 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@v5 | ||||
|         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@v5 | ||||
|         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 .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} --latest=false | ||||
|            | ||||
|           ./script/deploy.sh | ||||
|  | ||||
							
								
								
									
										26
									
								
								.github/workflows/sync-docker-images.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -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 | ||||
							
								
								
									
										40
									
								
								.github/workflows/template-webui.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,40 +0,0 @@ | ||||
| name: Build Web UI | ||||
| on: | ||||
|   workflow_call: {} | ||||
| jobs: | ||||
|  | ||||
|   build-webui: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - name: Check out code | ||||
|         uses: actions/checkout@v5 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Enable corepack | ||||
|         run: corepack enable | ||||
|  | ||||
|       - 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 | ||||
| @@ -1,40 +0,0 @@ | ||||
| name: Test K8s Gateway API conformance | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - '*' | ||||
|     paths: | ||||
|       - '.github/workflows/test-gateway-api-conformance.yaml' | ||||
|       - 'pkg/provider/kubernetes/gateway/**' | ||||
|       - 'integration/fixtures/gateway-api-conformance/**' | ||||
|       - 'integration/gateway_api_conformance_test.go' | ||||
|  | ||||
| env: | ||||
|   GO_VERSION: '1.24' | ||||
|   CGO_ENABLED: 0 | ||||
|  | ||||
| jobs: | ||||
|  | ||||
|   test-gateway-api-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: Gateway API conformance test and report | ||||
|         run: | | ||||
|           make test-gateway-api-conformance | ||||
|           git diff --exit-code | ||||
							
								
								
									
										103
									
								
								.github/workflows/test-integration.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,103 +0,0 @@ | ||||
| name: Test Integration | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - '*' | ||||
|     paths-ignore: | ||||
|       - 'docs/**' | ||||
|       - '**.md' | ||||
|       - 'script/gcg/**' | ||||
|  | ||||
| env: | ||||
|   GO_VERSION: '1.24' | ||||
|   CGO_ENABLED: 0 | ||||
|  | ||||
| jobs: | ||||
|  | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - name: Check out code | ||||
|         uses: actions/checkout@v5 | ||||
|         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-linux-amd64 | ||||
|  | ||||
|       - name: Save go cache build | ||||
|         uses: actions/cache/save@v4 | ||||
|         with: | ||||
|           path: | | ||||
|             ~/.cache/go-build | ||||
|           key: ${{ runner.os }}-go-build-cache-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }} | ||||
|  | ||||
|       - name: Artifact traefik binary | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: traefik | ||||
|           path: ./dist/linux/amd64/traefik | ||||
|           retention-days: 1 | ||||
|  | ||||
|   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@v5 | ||||
|         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: Download traefik binary | ||||
|         uses: actions/download-artifact@v4 | ||||
|         with: | ||||
|           name: traefik | ||||
|           path: ./dist/linux/amd64/ | ||||
|  | ||||
|       - name: Make binary executable | ||||
|         run: chmod +x ./dist/linux/amd64/traefik | ||||
|  | ||||
|       - name: Restore go cache build | ||||
|         uses: actions/cache/restore@v4 | ||||
|         with: | ||||
|           path: | | ||||
|             ~/.cache/go-build | ||||
|           key: ${{ runner.os }}-go-build-cache-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }} | ||||
|  | ||||
|       - 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 | ||||
							
								
								
									
										50
									
								
								.github/workflows/test-knative-conformance.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,50 +0,0 @@ | ||||
| name: Test Knative conformance | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - '*' | ||||
|     paths: | ||||
|       - '.github/workflows/test-knative-conformance.yaml' | ||||
|       - 'pkg/provider/kubernetes/knative/**' | ||||
|       - 'integration/fixtures/knative/**' | ||||
|       - 'integration/knative_conformance_test.go' | ||||
|  | ||||
| env: | ||||
|   GO_VERSION: '1.24' | ||||
|   CGO_ENABLED: 0 | ||||
|  | ||||
| jobs: | ||||
|  | ||||
|   test-knative-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: Set up KO | ||||
|         uses: ko-build/setup-ko@v0.6 | ||||
|         env: | ||||
|           KO_DOCKER_REPO: ko.local | ||||
|  | ||||
|       - name: Upload Test Images | ||||
|         run: | | ||||
|           # Download the test image templates. | ||||
|           go mod vendor | ||||
|           ./integration/fixtures/knative/upload-test-images.sh | ||||
|  | ||||
|       - name: Avoid generating webui | ||||
|         run: | | ||||
|           touch webui/static/index.html | ||||
|  | ||||
|       - name: Knative conformance test | ||||
|         run: | | ||||
|           make test-knative-conformance | ||||
							
								
								
									
										88
									
								
								.github/workflows/test-unit.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,88 +0,0 @@ | ||||
| name: Test Unit | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - '*' | ||||
|     paths-ignore: | ||||
|       - 'docs/**' | ||||
|       - '**.md' | ||||
|       - 'script/gcg/**' | ||||
|  | ||||
| env: | ||||
|   GO_VERSION: '1.24' | ||||
|  | ||||
| jobs: | ||||
|   generate-packages: | ||||
|     name: List Go Packages | ||||
|     runs-on: ubuntu-latest | ||||
|     outputs: | ||||
|       matrix: ${{ steps.set-matrix.outputs.matrix }} | ||||
|     steps: | ||||
|       - name: Check out code | ||||
|         uses: actions/checkout@v5 | ||||
|         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: Generate matrix | ||||
|         id: set-matrix | ||||
|         run: | | ||||
|           matrix_output=$(go run ./internal/testsci/genmatrix.go) | ||||
|           echo "$matrix_output" | ||||
|           echo "$matrix_output" >> $GITHUB_OUTPUT | ||||
|  | ||||
|   test-unit: | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: generate-packages | ||||
|     strategy: | ||||
|       matrix: | ||||
|         package: ${{ fromJson(needs.generate-packages.outputs.matrix) }} | ||||
|  | ||||
|     steps: | ||||
|       - name: Check out code | ||||
|         uses: actions/checkout@v5 | ||||
|         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: Tests | ||||
|         run: | | ||||
|           go test -v -parallel 8 ${{ matrix.package.group }} | ||||
|  | ||||
|   test-ui-unit: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - name: Check out code | ||||
|         uses: actions/checkout@v5 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Enable corepack | ||||
|         run: corepack enable | ||||
|  | ||||
|       - 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 | ||||
|         working-directory: ./webui | ||||
|         env: | ||||
|           VITE_APP_BASE_API_URL: "/api" | ||||
|         run: | | ||||
|           yarn install | ||||
|           yarn test:unit:ci | ||||
							
								
								
									
										84
									
								
								.github/workflows/validate.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,84 +0,0 @@ | ||||
| name: Validate | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - '*' | ||||
|  | ||||
| env: | ||||
|   GO_VERSION: '1.24' | ||||
|   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@v5 | ||||
|         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@v5 | ||||
|         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: Validate | ||||
|         run: make validate-files | ||||
|  | ||||
|   validate-generate: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - name: Check out code | ||||
|         uses: actions/checkout@v5 | ||||
|         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 | ||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -7,6 +7,7 @@ | ||||
| /webui/.tmp/ | ||||
| /site/ | ||||
| /docs/site/ | ||||
| /static/ | ||||
| /autogen/ | ||||
| /traefik | ||||
| /traefik.toml | ||||
| @@ -15,8 +16,3 @@ | ||||
| *.exe | ||||
| cover.out | ||||
| vendor/ | ||||
| plugins-storage/ | ||||
| plugins-local/ | ||||
| traefik_changelog.md | ||||
| integration/tailscale.secret | ||||
| integration/gateway-api-conformance-reports/**/experimental-dev-default-report.yaml | ||||
|   | ||||
							
								
								
									
										97
									
								
								.golangci.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,97 @@ | ||||
| [run] | ||||
|   timeout = "10m" | ||||
|   skip-files = [] | ||||
|   skip-dirs = [ | ||||
|     "pkg/provider/kubernetes/crd/generated/", | ||||
|   ] | ||||
|  | ||||
| [linters-settings] | ||||
|  | ||||
|   [linters-settings.govet] | ||||
|     check-shadowing = false | ||||
|  | ||||
|   [linters-settings.golint] | ||||
|     min-confidence = 0.0 | ||||
|  | ||||
|   [linters-settings.gocyclo] | ||||
|     min-complexity = 14.0 | ||||
|  | ||||
|   [linters-settings.maligned] | ||||
|     suggest-new = true | ||||
|  | ||||
|   [linters-settings.goconst] | ||||
|     min-len = 3.0 | ||||
|     min-occurrences = 4.0 | ||||
|  | ||||
|   [linters-settings.misspell] | ||||
|     locale = "US" | ||||
|  | ||||
|   [linters-settings.funlen] | ||||
|     lines = 230 # default 60 | ||||
|     statements = 120 # default 40 | ||||
|  | ||||
| [linters] | ||||
|   enable-all = true | ||||
|   disable = [ | ||||
|     "gocyclo", # FIXME must be fixed | ||||
|     "gosec", | ||||
|     "dupl", | ||||
|     "maligned", | ||||
|     "lll", | ||||
|     "unparam", | ||||
|     "prealloc", | ||||
|     "scopelint", | ||||
|     "gochecknoinits", | ||||
|     "gochecknoglobals", | ||||
|     "godox", | ||||
|     "gocognit", | ||||
|     "bodyclose", # Too many false-positive and panics. | ||||
|     "wsl", # Too strict | ||||
|     "stylecheck", # skip because report issues related to some generated files. | ||||
|   ] | ||||
|  | ||||
| [issues] | ||||
|   exclude-use-default = false | ||||
|   max-per-linter = 0 | ||||
|   max-same-issues = 0 | ||||
|   exclude = [ | ||||
|     "SA1019: http.CloseNotifier is deprecated: the CloseNotifier interface predates Go's context package. New code should use Request.Context instead.", # FIXME must be fixed | ||||
|     "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*printf?|os\\.(Un)?Setenv). is not checked", | ||||
|     "should have a package comment, unless it's in another file for this package", | ||||
|   ] | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "(.+)_test.go" | ||||
|     linters = ["goconst", "funlen"] | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "integration/.+_test.go" | ||||
|     text = "Error return value of `cmd\\.Process\\.Kill` is not checked" | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "integration/(consul_catalog_test|constraint_test).go" | ||||
|     text = "Error return value of `(s.deregisterService|s.deregisterAgentService)` is not checked" | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "integration/grpc_test.go" | ||||
|     text = "Error return value of `closer` is not checked" | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "pkg/h2c/h2c.go" | ||||
|     text = "Error return value of `rw.Write` is not checked" | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "pkg/middlewares/recovery/recovery.go" | ||||
|     text = "`logger` can be `github.com/stretchr/testify/assert.TestingT`" | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "pkg/provider/docker/builder_test.go" | ||||
|     text = "(U1000: func )?`(.+)` is unused" | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "pkg/provider/kubernetes/builder_(endpoint|service)_test.go" | ||||
|     text = "(U1000: func )?`(.+)` is unused" | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "pkg/config/parser/.+_test.go" | ||||
|     text = "U1000: field `(foo|fuu)` is unused" | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "pkg/server/service/bufferpool.go" | ||||
|     text = "SA6002: argument should be pointer-like to avoid allocations" | ||||
|  [[issues.exclude-rules]] | ||||
|     path = "cmd/configuration.go" | ||||
|     text = "string `traefik` has (\\d) occurrences, make it a constant" | ||||
|  [[issues.exclude-rules]] # FIXME must be fixed | ||||
|     path = "cmd/context.go" | ||||
|     text = "S1000: should use a simple channel send/receive instead of `select` with a single case" | ||||
							
								
								
									
										334
									
								
								.golangci.yml
									
									
									
									
									
								
							
							
						
						| @@ -1,334 +0,0 @@ | ||||
| version: "2" | ||||
|  | ||||
| formatters: | ||||
|   enable: | ||||
|     - gci | ||||
|     - gofumpt | ||||
|   exclusions: | ||||
|     generated: lax | ||||
|     paths: | ||||
|       - pkg/provider/kubernetes/crd/generated/ | ||||
|  | ||||
| linters: | ||||
|   default: all | ||||
|   disable: | ||||
|     - bodyclose # too many false-positive | ||||
|     - containedctx # too many false-positive | ||||
|     - contextcheck # too many false-positive | ||||
|     - cyclop # duplicate of gocyclo | ||||
|     - dupl # Too strict | ||||
|     - err113 # Too strict | ||||
|     - exhaustive # Not relevant | ||||
|     - exhaustruct # Not relevant | ||||
|     - forcetypeassert # Too strict | ||||
|     - gochecknoglobals | ||||
|     - gochecknoinits | ||||
|     - gocognit # Too strict | ||||
|     - gocyclo # FIXME must be fixed | ||||
|     - gosec # Too strict | ||||
|     - gosmopolitan  # not relevant | ||||
|     - ireturn # Not relevant | ||||
|     - lll # Not relevant | ||||
|     - maintidx # kind of duplicate of gocyclo | ||||
|     - makezero # Not relevant | ||||
|     - mnd # Too strict | ||||
|     - nestif # Too many false-positive. | ||||
|     - nilnil # Not relevant | ||||
|     - nlreturn # Not relevant | ||||
|     - noctx # Too strict | ||||
|     - nonamedreturns # Too strict | ||||
|     - paralleltest # Not relevant | ||||
|     - prealloc # Too many false-positive. | ||||
|     - rowserrcheck # not relevant (SQL) | ||||
|     - sqlclosecheck # not relevant (SQL) | ||||
|     - tagliatelle # Too strict | ||||
|     - testpackage # Too strict | ||||
|     - tparallel # Not relevant | ||||
|     - varnamelen # Not relevant | ||||
|     - wrapcheck # Too strict | ||||
|     - wsl # Too strict | ||||
|  | ||||
|   settings: | ||||
|     depguard: | ||||
|       rules: | ||||
|         main: | ||||
|           deny: | ||||
|             - pkg: github.com/instana/testify | ||||
|               desc: not allowed | ||||
|             - pkg: github.com/pkg/errors | ||||
|               desc: Should be replaced by standard lib errors package | ||||
|     errcheck: | ||||
|       exclude-functions: | ||||
|         - fmt.Fprintln | ||||
|     forbidigo: | ||||
|       forbid: | ||||
|         - pattern: ^print(ln)?$ | ||||
|         - pattern: ^spew\.Print(f|ln)?$ | ||||
|         - pattern: ^spew\.Dump$ | ||||
|     funlen: | ||||
|       lines: -1 | ||||
|       statements: 120 | ||||
|     goconst: | ||||
|       min-len: 3 | ||||
|       min-occurrences: 4 | ||||
|     gocyclo: | ||||
|       min-complexity: 14 | ||||
|     godox: | ||||
|       keywords: | ||||
|         - FIXME | ||||
|     gomoddirectives: | ||||
|       toolchain-pattern: go1\.\d+\.\d+$ | ||||
|       tool-forbidden: true | ||||
|       go-version-pattern: ^1\.\d+(\.0)?$ | ||||
|       replace-allow-list: | ||||
|         - github.com/abbot/go-http-auth | ||||
|         - github.com/gorilla/mux | ||||
|         - github.com/mailgun/minheap | ||||
|         - github.com/mailgun/multibuf | ||||
|         - github.com/jaguilar/vt100 | ||||
|         - github.com/cucumber/godog | ||||
|     govet: | ||||
|       enable-all: true | ||||
|       disable: | ||||
|         - shadow | ||||
|         - fieldalignment | ||||
|     importas: | ||||
|       no-unaliased: true | ||||
|       alias: | ||||
|         - pkg: github.com/docker/compose/v2/pkg/api | ||||
|           alias: composeapi | ||||
|  | ||||
|         # Standard Kubernetes rewrites: | ||||
|         - pkg: k8s.io/api/core/v1 | ||||
|           alias: corev1 | ||||
|         - pkg: k8s.io/api/networking/v1 | ||||
|           alias: netv1 | ||||
|         - pkg: k8s.io/api/networking/v1beta1 | ||||
|           alias: netv1beta1 | ||||
|         - pkg: k8s.io/api/admission/v1 | ||||
|           alias: admv1 | ||||
|         - pkg: k8s.io/api/admission/v1beta1 | ||||
|           alias: admv1beta1 | ||||
|         - pkg: k8s.io/api/extensions/v1beta1 | ||||
|           alias: extv1beta1 | ||||
|         - pkg: k8s.io/apimachinery/pkg/apis/meta/v1 | ||||
|           alias: metav1 | ||||
|         - pkg: k8s.io/apimachinery/pkg/types | ||||
|           alias: ktypes | ||||
|         - pkg: k8s.io/apimachinery/pkg/api/errors | ||||
|           alias: kerror | ||||
|         - pkg: k8s.io/client-go/kubernetes | ||||
|           alias: kclientset | ||||
|         - pkg: k8s.io/client-go/informers | ||||
|           alias: kinformers | ||||
|         - pkg: k8s.io/client-go/testing | ||||
|           alias: ktesting | ||||
|         - pkg: k8s.io/apimachinery/pkg/runtime/schema | ||||
|           alias: kschema | ||||
|         - pkg: k8s.io/client-go/kubernetes/scheme | ||||
|           alias: kscheme | ||||
|         - pkg: k8s.io/apimachinery/pkg/version | ||||
|           alias: kversion | ||||
|         - pkg: k8s.io/client-go/kubernetes/fake | ||||
|           alias: kubefake | ||||
|         - pkg: k8s.io/client-go/discovery/fake | ||||
|           alias: discoveryfake | ||||
|  | ||||
|         # Kubernetes Gateway rewrites: | ||||
|         - pkg: sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned | ||||
|           alias: gateclientset | ||||
|         - pkg: sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions | ||||
|           alias: gateinformers | ||||
|         - pkg: sigs.k8s.io/gateway-api/apis/v1alpha2 | ||||
|           alias: gatev1alpha2 | ||||
|  | ||||
|         # Traefik Kubernetes rewrites: | ||||
|         - pkg: github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1 | ||||
|           alias: traefikv1alpha1 | ||||
|         - pkg: github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/clientset/versioned | ||||
|           alias: traefikclientset | ||||
|         - pkg: github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/informers/externalversions | ||||
|           alias: traefikinformers | ||||
|         - pkg: github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme | ||||
|           alias: traefikscheme | ||||
|         - pkg: github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake | ||||
|           alias: traefikcrdfake | ||||
|     misspell: | ||||
|       locale: US | ||||
|     revive: | ||||
|       rules: | ||||
|         - name: struct-tag | ||||
|         - name: blank-imports | ||||
|         - name: context-as-argument | ||||
|         - name: context-keys-type | ||||
|         - name: dot-imports | ||||
|         - name: error-return | ||||
|         - name: error-strings | ||||
|         - name: error-naming | ||||
|         - name: exported | ||||
|           disabled: true | ||||
|         - name: if-return | ||||
|         - name: increment-decrement | ||||
|         - name: var-naming | ||||
|         - name: var-declaration | ||||
|         - name: package-comments | ||||
|           disabled: true | ||||
|         - name: range | ||||
|         - name: receiver-naming | ||||
|         - name: time-naming | ||||
|         - name: unexported-return | ||||
|         - name: indent-error-flow | ||||
|         - name: errorf | ||||
|         - name: empty-block | ||||
|         - name: superfluous-else | ||||
|         - name: unused-parameter | ||||
|           disabled: true | ||||
|         - name: unreachable-code | ||||
|         - name: redefines-builtin-id | ||||
|     tagalign: | ||||
|       align: false | ||||
|       sort: true | ||||
|       order: | ||||
|         - description | ||||
|         - json | ||||
|         - toml | ||||
|         - yaml | ||||
|         - yml | ||||
|         - label | ||||
|         - label-slice-as-struct | ||||
|         - file | ||||
|         - kv | ||||
|         - export | ||||
|     testifylint: | ||||
|       disable: | ||||
|         - suite-dont-use-pkg | ||||
|         - require-error | ||||
|         - go-require | ||||
|     perfsprint: | ||||
|       err-error: true | ||||
|       errorf: true | ||||
|       sprintf1: true | ||||
|       strconcat: false | ||||
|     staticcheck: | ||||
|       checks: | ||||
|         - all | ||||
|         - '-SA1019' | ||||
|         - '-ST1000' | ||||
|         - '-ST1003' | ||||
|         - '-ST1016' | ||||
|         - '-ST1020' | ||||
|         - '-ST1021' | ||||
|         - '-ST1022' | ||||
|         - '-QF1001' | ||||
|         - '-QF1008' # TODO must be fixed | ||||
|  | ||||
|   exclusions: | ||||
|     generated: lax | ||||
|     presets: | ||||
|       - comments | ||||
|       - std-error-handling | ||||
|     rules: | ||||
|       - path: (.+)_test.go | ||||
|         linters: | ||||
|           - canonicalheader | ||||
|           - fatcontext | ||||
|           - funlen | ||||
|           - goconst | ||||
|           - godot | ||||
|       - path: (.+)_test.go | ||||
|         text: ' always receives ' | ||||
|         linters: | ||||
|           - unparam | ||||
|       - path: pkg/server/middleware/middlewares.go | ||||
|         text: Function 'buildConstructor' has too many statements | ||||
|         linters: | ||||
|           - funlen | ||||
|       - path: pkg/provider/kubernetes/ingress-nginx/kubernetes.go | ||||
|         text: Function 'loadConfiguration' has too many statements | ||||
|         linters: | ||||
|           - funlen | ||||
|       - path: pkg/tracing/haystack/logger.go | ||||
|         linters: | ||||
|           - goprintffuncname | ||||
|       - path: pkg/tracing/tracing.go | ||||
|         text: printf-like formatting function 'SetErrorWithEvent' should be named 'SetErrorWithEventf' | ||||
|         linters: | ||||
|           - goprintffuncname | ||||
|       - path: pkg/tls/tlsmanager_test.go | ||||
|         text: 'SA1019: config.ClientCAs.Subjects has been deprecated since Go 1.18' | ||||
|       - path: pkg/types/tls_test.go | ||||
|         text: 'SA1019: tlsConfig.RootCAs.Subjects has been deprecated since Go 1.18' | ||||
|       - path: pkg/provider/kubernetes/(crd|gateway)/client.go | ||||
|         linters: | ||||
|           - interfacebloat | ||||
|       - path: pkg/observability/metrics/metrics.go | ||||
|         linters: | ||||
|           - interfacebloat | ||||
|       - path: integration/healthcheck_test.go | ||||
|         text: Duplicate words \(wsp2,\) found | ||||
|         linters: | ||||
|           - dupword | ||||
|       - path: pkg/types/domain_test.go | ||||
|         text: Duplicate words \(sub\) found | ||||
|         linters: | ||||
|           - dupword | ||||
|       - path: pkg/provider/kubernetes/gateway/client_mock_test.go | ||||
|         text: 'unusedwrite: unused write to field' | ||||
|         linters: | ||||
|           - govet | ||||
|       - path: pkg/provider/acme/local_store.go | ||||
|         linters: | ||||
|           - musttag | ||||
|       - path: pkg/tls/certificate.go | ||||
|         text: the methods of "Certificates" use pointer receiver and non-pointer receiver. | ||||
|         linters: | ||||
|           - recvcheck | ||||
|       - path: pkg/config/static/static_config.go | ||||
|         source: 'errors.New\("Consul Catalog provider' | ||||
|         text: 'ST1005: error strings should not be capitalized' | ||||
|       - path: pkg/config/static/static_config.go | ||||
|         source: 'errors.New\("Consul provider' | ||||
|         text: 'ST1005: error strings should not be capitalized' | ||||
|       - path: pkg/config/static/static_config.go | ||||
|         source: 'errors.New\("Nomad provider' | ||||
|         text: 'ST1005: error strings should not be capitalized' | ||||
|       - path: (.+)\.go | ||||
|         text: 'struct-tag: unknown option ''inline'' in JSON tag' | ||||
|         linters: | ||||
|           - revive | ||||
|       - path: (.+)\.go | ||||
|         text: 'struct-tag: unknown option ''omitzero'' in TOML tag' | ||||
|         linters: | ||||
|           - revive | ||||
|       - path: (.+)\.go$ | ||||
|         text: 'SA1019: http.CloseNotifier has been deprecated' # FIXME must be fixed | ||||
|       - path: (.+)\.go$ | ||||
|         text: 'SA1019: cfg.(SSLRedirect|SSLTemporaryRedirect|SSLHost|SSLForceHost|FeaturePolicy) is deprecated' | ||||
|       - path: (.+)\.go$ | ||||
|         text: 'SA1019: c.Providers.(ConsulCatalog|Consul|Nomad).Namespace is deprecated' | ||||
|       - path: pkg/provider/kubernetes/crd/kubernetes.go | ||||
|         text: "Function 'loadConfigurationFromCRD' has too many statements" | ||||
|         linters: | ||||
|           - funlen | ||||
|       - path: pkg/plugins/middlewarewasm.go | ||||
|         text: 'the methods of "wasmMiddlewareBuilder" use pointer receiver and non-pointer receiver.' | ||||
|         linters: | ||||
|           - recvcheck | ||||
|       - path: pkg/server/service/bufferpool.go | ||||
|         text: 'SA6002: argument should be pointer-like to avoid allocations' | ||||
|       - path: pkg/proxy/httputil/bufferpool.go | ||||
|         text: 'SA6002: argument should be pointer-like to avoid allocations' | ||||
|       - path: pkg/udp/conn.go | ||||
|         text: 'SA6002: argument should be pointer-like to avoid allocations' | ||||
|       - path: integration/integration_test.go | ||||
|         text: 'var (gatewayAPIConformanceRunTest|traefikVersion) is unused' | ||||
|       - path: pkg/server/router/router.go | ||||
|         text: 'appendAssign: append result not assigned to the same slice' | ||||
|         linters: | ||||
|           - gocritic | ||||
|     paths: | ||||
|       - pkg/provider/kubernetes/crd/generated/ | ||||
|  | ||||
| issues: | ||||
|   max-issues-per-linter: 0 | ||||
|   max-same-issues: 0 | ||||
							
								
								
									
										58
									
								
								.goreleaser.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,58 @@ | ||||
| project_name: traefik | ||||
|  | ||||
| before: | ||||
|   hooks: | ||||
|     - go generate | ||||
|  | ||||
| builds: | ||||
|   - binary: traefik | ||||
|  | ||||
|     main: ./cmd/traefik/traefik.go | ||||
|     env: | ||||
|       - CGO_ENABLED=0 | ||||
|     ldflags: | ||||
|       - -s -w -X github.com/containous/traefik/v2/pkg/version.Version={{.Version}} -X github.com/containous/traefik/v2/pkg/version.Codename={{.Env.CODENAME}} -X github.com/containous/traefik/v2/pkg/version.BuildDate={{.Date}} | ||||
|  | ||||
|     goos: | ||||
|       - linux | ||||
|       - darwin | ||||
|       - windows | ||||
|       - freebsd | ||||
|       - openbsd | ||||
|     goarch: | ||||
|       - amd64 | ||||
|       - 386 | ||||
|       - arm | ||||
|       - arm64 | ||||
|       - ppc64le | ||||
|     goarm: | ||||
|       - 7 | ||||
|       - 6 | ||||
|       - 5 | ||||
|     ignore: | ||||
|       - goos: darwin | ||||
|         goarch: 386 | ||||
|       - goos: openbsd | ||||
|         goarch: arm | ||||
|       - goos: freebsd | ||||
|         goarch: arm | ||||
|  | ||||
| changelog: | ||||
|   skip: true | ||||
|  | ||||
| archives: | ||||
|   - id: traefik | ||||
|     name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' | ||||
|     format: tar.gz | ||||
|     format_overrides: | ||||
|       - goos: windows | ||||
|         format: zip | ||||
|     files: | ||||
|       - LICENSE.md | ||||
|       - CHANGELOG.md | ||||
|  | ||||
| checksum: | ||||
|   name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt" | ||||
|  | ||||
| release: | ||||
|   disable: true | ||||
| @@ -1,69 +0,0 @@ | ||||
| project_name: traefik | ||||
| version: 2 | ||||
|  | ||||
| [[if .GOARCH]] | ||||
| dist: "./dist/[[ .GOOS ]]-[[ .GOARCH ]]" | ||||
| [[else]] | ||||
| dist: "./dist/[[ .GOOS ]]" | ||||
| [[end]] | ||||
|  | ||||
| builds: | ||||
|   - binary: traefik | ||||
|  | ||||
|     main: ./cmd/traefik/ | ||||
|     env: | ||||
|       - CGO_ENABLED=0 | ||||
|     ldflags: | ||||
|       - -s -w -X github.com/traefik/traefik/v3/pkg/version.Version={{.Version}} -X github.com/traefik/traefik/v3/pkg/version.Codename={{.Env.CODENAME}} -X github.com/traefik/traefik/v3/pkg/version.BuildDate={{.Date}} | ||||
|     flags: | ||||
|       - -trimpath | ||||
|     goos: | ||||
|       - "[[ .GOOS ]]" | ||||
|     goarch: | ||||
|       [[if .GOARCH]] | ||||
|       - "[[ .GOARCH ]]" | ||||
|       [[else]] | ||||
|       - amd64 | ||||
|       - '386' | ||||
|       - arm | ||||
|       - arm64 | ||||
|       - ppc64le | ||||
|       - s390x | ||||
|       - riscv64 | ||||
|       [[end]] | ||||
|     goarm: | ||||
|       - '7' | ||||
|       - '6' | ||||
|     ignore: | ||||
|       - goos: darwin | ||||
|         goarch: '386' | ||||
|       - goos: openbsd | ||||
|         goarch: arm | ||||
|       - goos: openbsd | ||||
|         goarch: arm64 | ||||
|       - goos: freebsd | ||||
|         goarch: arm | ||||
|       - goos: freebsd | ||||
|         goarch: arm64 | ||||
|       - goos: windows | ||||
|         goarch: arm | ||||
|  | ||||
| changelog: | ||||
|   disable: true | ||||
|  | ||||
| archives: | ||||
|   - id: traefik | ||||
|     name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' | ||||
|     format: tar.gz | ||||
|     format_overrides: | ||||
|       - goos: windows | ||||
|         format: zip | ||||
|     files: | ||||
|       - LICENSE.md | ||||
|       - CHANGELOG.md | ||||
|  | ||||
| checksum: | ||||
|   name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt" | ||||
|  | ||||
| release: | ||||
|   disable: true | ||||
							
								
								
									
										4
									
								
								.semaphoreci/cleanup.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| #!/usr/bin/env bash | ||||
| set -e | ||||
|  | ||||
| sudo rm -rf static | ||||
							
								
								
									
										20
									
								
								.semaphoreci/golang.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,20 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| set -e | ||||
|  | ||||
| curl -O https://dl.google.com/go/go"${GO_VERSION}".linux-amd64.tar.gz | ||||
|  | ||||
| tar -xvf go"${GO_VERSION}".linux-amd64.tar.gz | ||||
| rm -rf go"${GO_VERSION}".linux-amd64.tar.gz | ||||
|  | ||||
| sudo mkdir -p /usr/local/golang/"${GO_VERSION}"/go | ||||
| sudo mv go /usr/local/golang/"${GO_VERSION}"/ | ||||
|  | ||||
| sudo rm /usr/local/bin/go | ||||
| sudo chmod +x /usr/local/golang/"${GO_VERSION}"/go/bin/go | ||||
| sudo ln -s /usr/local/golang/"${GO_VERSION}"/go/bin/go /usr/local/bin/go | ||||
|  | ||||
| export GOROOT="/usr/local/golang/${GO_VERSION}/go" | ||||
| export GOTOOLDIR="/usr/local/golang/${GO_VERSION}/go/pkg/tool/linux_amd64" | ||||
|  | ||||
| go version | ||||
							
								
								
									
										6
									
								
								.semaphoreci/job1.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | ||||
| #!/usr/bin/env bash | ||||
| set -e | ||||
|  | ||||
| if [ -n "$SHOULD_TEST" ]; then ci_retry make pull-images; fi | ||||
|  | ||||
| if [ -n "$SHOULD_TEST" ]; then ci_retry make test-integration; fi | ||||
							
								
								
									
										8
									
								
								.semaphoreci/job2.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| #!/usr/bin/env bash | ||||
| set -e | ||||
|  | ||||
| ci_retry make validate | ||||
|  | ||||
| if [ -n "$SHOULD_TEST" ]; then ci_retry make test-unit; fi | ||||
|  | ||||
| if [ -n "$SHOULD_TEST" ]; then make -j"${N_MAKE_JOBS}" crossbinary-default-parallel; fi | ||||
							
								
								
									
										35
									
								
								.semaphoreci/setup.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,35 @@ | ||||
| # For personnal CI | ||||
| # mv /home/runner/workspace/src/github.com/<username>/ /home/runner/workspace/src/github.com/containous/ | ||||
| # cd /home/runner/workspace/src/github.com/containous/traefik/ | ||||
| for s in apache2 cassandra elasticsearch memcached mysql mongod postgresql sphinxsearch rethinkdb rabbitmq-server redis-server; do sudo service $s stop; done | ||||
| sudo swapoff -a | ||||
| sudo dd if=/dev/zero of=/swapfile bs=1M count=3072 | ||||
| sudo mkswap /swapfile | ||||
| sudo swapon /swapfile | ||||
| sudo rm -rf /home/runner/.rbenv | ||||
| sudo rm -rf /usr/local/golang/{1.4.3,1.5.4,1.6.4,1.7.6,1.8.6,1.9.7,1.10.3,1.11} | ||||
| #export DOCKER_VERSION=18.06.3 | ||||
| source .semaphoreci/vars | ||||
| if [ -z "${PULL_REQUEST_NUMBER}" ]; then SHOULD_TEST="-*-"; else TEMP_STORAGE=$(curl --silent https://patch-diff.githubusercontent.com/raw/containous/traefik/pull/${PULL_REQUEST_NUMBER}.diff | patch --dry-run -p1 -R || true); fi | ||||
| echo ${SHOULD_TEST} | ||||
| if [ -n "$TEMP_STORAGE" ]; then SHOULD_TEST=$(echo "$TEMP_STORAGE" | grep -Ev '(.md|.yaml|.yml)' || :); fi | ||||
| echo ${TEMP_STORAGE} | ||||
| echo ${SHOULD_TEST} | ||||
| #if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq update; fi | ||||
| #if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*; fi | ||||
| if [ -n "$SHOULD_TEST" ]; then docker version; fi | ||||
| export GO_VERSION=1.12 | ||||
| if [ -f "./go.mod" ]; then GO_VERSION="$(grep '^go .*' go.mod | awk '{print $2}')"; export GO_VERSION; fi | ||||
| #if [ "${GO_VERSION}" == '1.13' ]; then export GO_VERSION=1.13rc2; fi | ||||
| echo "Selected Go version: ${GO_VERSION}" | ||||
|  | ||||
| if [ -f "./.semaphoreci/golang.sh" ]; then ./.semaphoreci/golang.sh; fi | ||||
| if [ -f "./.semaphoreci/golang.sh" ]; then export GOROOT="/usr/local/golang/${GO_VERSION}/go"; fi | ||||
| if [ -f "./.semaphoreci/golang.sh" ]; then export GOTOOLDIR="/usr/local/golang/${GO_VERSION}/go/pkg/tool/linux_amd64"; fi | ||||
| go version | ||||
|  | ||||
| if [ -f "./go.mod" ]; then export GO111MODULE=on; fi | ||||
| if [ -f "./go.mod" ]; then export GOPROXY=https://proxy.golang.org; fi | ||||
| if [ -f "./go.mod" ]; then go mod download; fi | ||||
|  | ||||
| df | ||||
							
								
								
									
										36
									
								
								.semaphoreci/vars
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | ||||
| #!/usr/bin/env bash | ||||
| set -e | ||||
|  | ||||
| export REPO='containous/traefik' | ||||
|  | ||||
| if VERSION=$(git describe --exact-match --abbrev=0 --tags); | ||||
| then | ||||
|   export VERSION | ||||
| else | ||||
|   export VERSION='' | ||||
| fi | ||||
|  | ||||
| export CODENAME=montdor | ||||
|  | ||||
| export N_MAKE_JOBS=2 | ||||
|  | ||||
|  | ||||
| function ci_retry { | ||||
|  | ||||
|     local NRETRY=3 | ||||
|     local NSLEEP=5 | ||||
|     local n=0 | ||||
|  | ||||
|     until [ $n -ge $NRETRY ] | ||||
|     do | ||||
|         "$@" && break | ||||
|         n=$((n+1)) | ||||
|         echo "${*} failed, attempt ${n}/${NRETRY}" | ||||
|         sleep $NSLEEP | ||||
|     done | ||||
|  | ||||
|     [ $n -lt $NRETRY ] | ||||
|  | ||||
| } | ||||
|  | ||||
| export -f ci_retry | ||||
							
								
								
									
										58
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,58 @@ | ||||
| sudo: required | ||||
| dist: trusty | ||||
|  | ||||
| git: | ||||
|   depth: false | ||||
|  | ||||
| services: | ||||
|   - docker | ||||
|  | ||||
| env: | ||||
|   global: | ||||
|     - REPO=$TRAVIS_REPO_SLUG | ||||
|     - VERSION=$TRAVIS_TAG | ||||
|     - CODENAME=montdor | ||||
|     - GO111MODULE=on | ||||
|  | ||||
| script: | ||||
| - echo "Skipping tests... (Tests are executed on SemaphoreCI)" | ||||
| - if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then make docs; fi | ||||
|  | ||||
| before_deploy: | ||||
|   - > | ||||
|     if ! [ "$BEFORE_DEPLOY_RUN" ]; then | ||||
|       export BEFORE_DEPLOY_RUN=1; | ||||
|       sudo -E apt-get -yq update; | ||||
|       sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*; | ||||
|       docker version; | ||||
|       make build-image; | ||||
|       if [ "$TRAVIS_TAG" ]; then | ||||
|         make release-packages; | ||||
|       fi; | ||||
|       curl -sfL https://raw.githubusercontent.com/containous/structor/master/godownloader.sh | bash -s -- -b "${GOPATH}/bin" ${STRUCTOR_VERSION} | ||||
|       structor -o containous -r traefik --dockerfile-url="https://raw.githubusercontent.com/containous/traefik/v1.7/docs.Dockerfile" --menu.js-url="https://raw.githubusercontent.com/containous/structor/master/traefik-menu.js.gotmpl" --rqts-url="https://raw.githubusercontent.com/containous/structor/master/requirements-override.txt" --force-edit-url --exp-branch=master --debug; | ||||
|     fi | ||||
|  | ||||
| deploy: | ||||
|   - provider: releases | ||||
|     api_key: ${GITHUB_TOKEN} | ||||
|     file: dist/traefik* | ||||
|     skip_cleanup: true | ||||
|     file_glob: true | ||||
|     on: | ||||
|       repo: containous/traefik | ||||
|       tags: true | ||||
|   - provider: script | ||||
|     script: sh script/deploy.sh | ||||
|     skip_cleanup: true | ||||
|     on: | ||||
|       repo: containous/traefik | ||||
|       tags: true | ||||
|   - provider: pages | ||||
|     edge: false | ||||
|     github_token: ${GITHUB_TOKEN} | ||||
|     local_dir: site | ||||
|     skip_cleanup: true | ||||
|     on: | ||||
|       repo: containous/traefik | ||||
|       all_branches: true | ||||
							
								
								
									
										
											BIN
										
									
								
								.travis/traefiker_rsa.enc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										11295
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| ## Our Pledge | ||||
|  | ||||
| In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. | ||||
| In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience,nationality, personal appearance, race, religion, or sexual identity and orientation. | ||||
|  | ||||
| ## Our Standards | ||||
|  | ||||
| @@ -30,38 +30,22 @@ Project maintainers have the right and responsibility to remove, edit, or reject | ||||
|  | ||||
| ## Scope | ||||
|  | ||||
| This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or our community. | ||||
|  | ||||
| Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. | ||||
| This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.  | ||||
| Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.  | ||||
| Representation of a project may be further defined and clarified by project maintainers. | ||||
|  | ||||
| ## Enforcement | ||||
|  | ||||
| Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@traefik.io | ||||
|  | ||||
| All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. | ||||
|  | ||||
| The project team is obligated to maintain confidentiality with regard to the reporter of an incident. | ||||
|  | ||||
| Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@containo.us | ||||
| All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.  | ||||
| The project team is obligated to maintain confidentiality with regard to the reporter of an incident.  | ||||
| Further details of specific enforcement policies may be posted separately. | ||||
|  | ||||
| Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. | ||||
|  | ||||
| When an inappropriate behavior is reported, maintainers will discuss on the Maintainer's Discord before marking the message as "abuse".  | ||||
| This conversation beforehand avoids one-sided decisions. | ||||
|  | ||||
| The first message will be edited and marked as abuse. | ||||
| The second edited message and marked as abuse results in a 7-day ban. | ||||
| The third edited message and marked as abuse results in a permanent ban. | ||||
|  | ||||
| The content of edited messages is: | ||||
| `Dear user, we want traefik to provide a welcoming and respectful environment. Your [comment/issue/PR] has been reported and marked as abuse according to our [Code of Conduct](./CODE_OF_CONDUCT.md). Thank you.` | ||||
|  | ||||
| The [report must be resolved](https://docs.github.com/en/communities/moderating-comments-and-conversations/managing-reported-content-in-your-organizations-repository#resolving-a-report) accordingly. | ||||
|  | ||||
| ## Attribution | ||||
|  | ||||
| This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] | ||||
|  | ||||
| [homepage]: http://contributor-covenant.org | ||||
| [version]: http://contributor-covenant.org/version/1/4/ | ||||
| [version]: http://contributor-covenant.org/version/1/4/ | ||||
| @@ -1,11 +1,4 @@ | ||||
| # Contributing | ||||
|  | ||||
| Here are some guidelines that should help to start contributing to the project. | ||||
|  | ||||
| - [Submitting pull Requests](https://doc.traefik.io/traefik/contributing/submitting-pull-requests/) | ||||
| - [Submitting issues](https://doc.traefik.io/traefik/contributing/submitting-issues/) | ||||
| - [Submitting security issues](https://doc.traefik.io/traefik/contributing/submitting-security-issues/) | ||||
| - [Advocating for Traefik](https://doc.traefik.io/traefik/contributing/advocating/) | ||||
| - [Triage Process](https://github.com/traefik/contributors-guide/blob/master/issue_triage.md) | ||||
|  | ||||
| If you are willing to become a maintainer of the project, please take a look at the [maintainers guidelines](docs/content/contributing/maintainers-guidelines.md). | ||||
| - https://docs.traefik.io/contributing/submitting-pull-requests/ | ||||
| - https://docs.traefik.io/contributing/submitting-issues/ | ||||
|   | ||||
							
								
								
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						| @@ -1,12 +1,6 @@ | ||||
| # syntax=docker/dockerfile:1.2 | ||||
| FROM alpine:3.22 | ||||
|  | ||||
| RUN apk add --no-cache --no-progress ca-certificates tzdata | ||||
|  | ||||
| ARG TARGETPLATFORM | ||||
| COPY ./dist/$TARGETPLATFORM/traefik / | ||||
|  | ||||
| FROM scratch | ||||
| COPY script/ca-certificates.crt /etc/ssl/certs/ | ||||
| COPY dist/traefik / | ||||
| EXPOSE 80 | ||||
| VOLUME ["/tmp"] | ||||
|  | ||||
| ENTRYPOINT ["/traefik"] | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs | ||||
| Copyright (c) 2016-2018 Containous SAS | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
|   | ||||
							
								
								
									
										268
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						| @@ -1,201 +1,151 @@ | ||||
| .PHONY: all docs docs-serve | ||||
|  | ||||
| SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/') | ||||
|  | ||||
| TAG_NAME := $(shell git describe --abbrev=0 --tags --exact-match) | ||||
| TAG_NAME := $(shell git tag -l --contains HEAD) | ||||
| SHA := $(shell git rev-parse HEAD) | ||||
| VERSION_GIT := $(if $(TAG_NAME),$(TAG_NAME),$(SHA)) | ||||
| VERSION := $(if $(VERSION),$(VERSION),$(VERSION_GIT)) | ||||
|  | ||||
| BIN_NAME := traefik | ||||
| CODENAME ?= cheddar | ||||
| BIND_DIR := "dist" | ||||
|  | ||||
| DATE := $(shell date -u '+%Y-%m-%d_%I:%M:%S%p') | ||||
| GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)) | ||||
| TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(subst /,-,$(GIT_BRANCH))) | ||||
|  | ||||
| # Default build target | ||||
| GOOS := $(shell go env GOOS) | ||||
| GOARCH := $(shell go env GOARCH) | ||||
| GOGC ?= | ||||
| REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]') | ||||
| TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik") | ||||
|  | ||||
| LINT_EXECUTABLES = misspell shellcheck | ||||
| INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -e "TEST_CONTAINER=1" -v "/var/run/docker.sock:/var/run/docker.sock") | ||||
| DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",) | ||||
|  | ||||
| DOCKER_BUILD_PLATFORMS ?= linux/amd64,linux/arm64 | ||||
| TRAEFIK_ENVS := \ | ||||
| 	-e OS_ARCH_ARG \ | ||||
| 	-e OS_PLATFORM_ARG \ | ||||
| 	-e TESTFLAGS \ | ||||
| 	-e VERBOSE \ | ||||
| 	-e VERSION \ | ||||
| 	-e CODENAME \ | ||||
| 	-e TESTDIRS \ | ||||
| 	-e CI \ | ||||
| 	-e CONTAINER=DOCKER		# Indicator for integration tests that we are running inside a container. | ||||
|  | ||||
| .PHONY: default | ||||
| #? default: Run `make generate` and `make binary` | ||||
| default: generate binary | ||||
| TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)" | ||||
| DOCKER_RUN_OPTS := $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)" | ||||
| DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(DOCKER_RUN_OPTS) | ||||
| DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) -i $(DOCKER_RUN_OPTS) | ||||
|  | ||||
| #? dist: Create the "dist" directory | ||||
| PRE_TARGET ?= build-dev-image | ||||
|  | ||||
| default: binary | ||||
|  | ||||
| ## Build Dev Docker image | ||||
| build-dev-image: dist | ||||
| 	docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile . | ||||
|  | ||||
| ## Build Dev Docker image without cache | ||||
| build-dev-image-no-cache: dist | ||||
| 	docker build --no-cache -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile . | ||||
|  | ||||
| ## Create the "dist" directory | ||||
| dist: | ||||
| 	mkdir -p dist | ||||
| 	mkdir dist | ||||
|  | ||||
| .PHONY: build-webui-image | ||||
| #? build-webui-image: Build WebUI Docker image | ||||
| ## Build WebUI Docker image | ||||
| build-webui-image: | ||||
| 	docker build -t traefik-webui -f webui/buildx.Dockerfile webui | ||||
| 	docker build -t traefik-webui -f webui/Dockerfile webui | ||||
|  | ||||
| .PHONY: clean-webui | ||||
| #? clean-webui: Clean WebUI static generated assets | ||||
| clean-webui: | ||||
| 	rm -r webui/static | ||||
| 	mkdir -p webui/static | ||||
| 	printf 'For more information see `webui/readme.md`' > webui/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md | ||||
| ## Generate WebUI | ||||
| generate-webui: build-webui-image | ||||
| 	if [ ! -d "static" ]; then \ | ||||
| 		mkdir -p static; \ | ||||
| 		docker run --rm -v "$$PWD/static":'/src/static' traefik-webui npm run build:nc; \ | ||||
| 		docker run --rm -v "$$PWD/static":'/src/static' traefik-webui chown -R $(shell id -u):$(shell id -g) ../static; \ | ||||
| 		echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \ | ||||
| 	fi | ||||
|  | ||||
| webui/static/index.html: | ||||
| 	$(MAKE) build-webui-image | ||||
| 	docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui yarn build:prod | ||||
| 	docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui chown -R $(shell id -u):$(shell id -g) ./static | ||||
| 	printf 'For more information see `webui/readme.md`' > webui/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md | ||||
| ## Build the linux binary | ||||
| binary: generate-webui $(PRE_TARGET) | ||||
| 	$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate binary | ||||
|  | ||||
| .PHONY: generate-webui | ||||
| #? generate-webui: Generate WebUI | ||||
| generate-webui: webui/static/index.html | ||||
| ## Build the binary for the standard plaforms (linux, darwin, windows) | ||||
| crossbinary-default: generate-webui build-dev-image | ||||
| 	$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-default | ||||
|  | ||||
| .PHONY: generate | ||||
| #? generate: Generate code (Dynamic and Static configuration documentation reference files) | ||||
| generate: | ||||
| 	go generate | ||||
| ## Build the binary for the standard plaforms (linux, darwin, windows) in parallel | ||||
| crossbinary-default-parallel: | ||||
| 	$(MAKE) generate-webui | ||||
| 	$(MAKE) build-dev-image crossbinary-default | ||||
|  | ||||
| .PHONY: binary | ||||
| #? binary: Build the binary | ||||
| binary: generate-webui dist | ||||
| 	@echo SHA: $(VERSION) $(CODENAME) $(DATE) | ||||
| 	CGO_ENABLED=0 GOGC=${GOGC} GOOS=${GOOS} GOARCH=${GOARCH} go build ${FLAGS[*]} -ldflags "-s -w \ | ||||
|     -X github.com/traefik/traefik/v3/pkg/version.Version=$(VERSION) \ | ||||
|     -X github.com/traefik/traefik/v3/pkg/version.Codename=$(CODENAME) \ | ||||
|     -X github.com/traefik/traefik/v3/pkg/version.BuildDate=$(DATE)" \ | ||||
|     -installsuffix nocgo -o "./dist/${GOOS}/${GOARCH}/$(BIN_NAME)" ./cmd/traefik | ||||
| ## Run the unit and integration tests | ||||
| test: build-dev-image | ||||
| 	$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration | ||||
|  | ||||
| binary-linux-arm64: export GOOS := linux | ||||
| binary-linux-arm64: export GOARCH := arm64 | ||||
| binary-linux-arm64: | ||||
| 	@$(MAKE) binary | ||||
| ## Run the unit tests | ||||
| test-unit: $(PRE_TARGET) | ||||
| 	$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate test-unit | ||||
|  | ||||
| binary-linux-amd64: export GOOS := linux | ||||
| binary-linux-amd64: export GOARCH := amd64 | ||||
| binary-linux-amd64: | ||||
| 	@$(MAKE) binary | ||||
|  | ||||
| binary-windows-amd64: export GOOS := windows | ||||
| binary-windows-amd64: export GOARCH := amd64 | ||||
| binary-windows-amd64: export BIN_NAME := traefik.exe | ||||
| binary-windows-amd64: | ||||
| 	@$(MAKE) binary | ||||
|  | ||||
| .PHONY: crossbinary-default | ||||
| #? crossbinary-default: Build the binary for the standard platforms (linux, darwin, windows) | ||||
| crossbinary-default: generate generate-webui | ||||
| 	$(CURDIR)/script/crossbinary-default.sh | ||||
|  | ||||
| .PHONY: test | ||||
| #? test: Run the unit and integration tests | ||||
| test: test-ui-unit test-unit test-integration | ||||
|  | ||||
| .PHONY: test-unit | ||||
| #? test-unit: Run the unit tests | ||||
| test-unit: | ||||
| 	GOOS=$(GOOS) GOARCH=$(GOARCH) go test -cover "-coverprofile=cover.out" -v $(TESTFLAGS) ./pkg/... ./cmd/... | ||||
|  | ||||
| .PHONY: test-integration | ||||
| #? test-integration: Run the integration tests | ||||
| test-integration: | ||||
| 	GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -test.timeout=20m -failfast -v $(TESTFLAGS) | ||||
|  | ||||
| .PHONY: test-gateway-api-conformance | ||||
| #? test-gateway-api-conformance: Run the Gateway API conformance tests | ||||
| test-gateway-api-conformance: build-image-dirty | ||||
| 	# In case of a new Minor/Major version, the traefikVersion needs to be updated. | ||||
| 	GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -tags gatewayAPIConformance -test.run GatewayAPIConformanceSuite -traefikVersion="v3.6" $(TESTFLAGS) | ||||
|  | ||||
| .PHONY: test-knative-conformance | ||||
| #? test-knative-conformance: Run the Knative conformance tests | ||||
| test-knative-conformance: build-image-dirty | ||||
| 	GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration/integration_test.go ./integration/knative_conformance_test.go -v -tags knativeConformance -test.run KnativeConformanceSuite | ||||
|  | ||||
| .PHONY: test-ui-unit | ||||
| #? test-ui-unit: Run the unit tests for the webui | ||||
| test-ui-unit: | ||||
| 	$(MAKE) build-webui-image | ||||
| 	docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui yarn --cwd webui install | ||||
| 	docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui yarn --cwd webui test:unit:ci | ||||
|  | ||||
| .PHONY: pull-images | ||||
| #? pull-images: Pull all Docker images to avoid timeout during integration tests | ||||
| ## Pull all images for integration tests | ||||
| pull-images: | ||||
| 	grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml \ | ||||
| 		| awk '{print $$2}' \ | ||||
| 		| sort \ | ||||
| 		| uniq \ | ||||
| 		| xargs -P 6 -n 1 docker pull | ||||
| 	grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml | awk '{print $$2}' | sort | uniq | xargs -P 6 -n 1 docker pull | ||||
|  | ||||
| .PHONY: lint | ||||
| #? lint: Run golangci-lint | ||||
| lint: | ||||
| 	golangci-lint run | ||||
| ## Run the integration tests | ||||
| test-integration: $(PRE_TARGET) | ||||
| 	$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK),TEST_CONTAINER=1) ./script/make.sh generate binary test-integration | ||||
| 	TEST_HOST=1 ./script/make.sh test-integration | ||||
|  | ||||
| .PHONY: validate-files | ||||
| #? validate-files: Validate code and docs | ||||
| validate-files: | ||||
| 	$(foreach exec,$(LINT_EXECUTABLES),\ | ||||
|             $(if $(shell which $(exec)),,$(error "No $(exec) in PATH"))) | ||||
| 	$(CURDIR)/script/validate-vendor.sh | ||||
| 	$(CURDIR)/script/validate-misspell.sh | ||||
| 	$(CURDIR)/script/validate-shell-script.sh | ||||
| ## Validate code and docs | ||||
| validate-files: $(PRE_TARGET) | ||||
| 	$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell | ||||
| 	bash $(CURDIR)/script/validate-shell-script.sh | ||||
|  | ||||
| .PHONY: validate | ||||
| #? validate: Validate code, docs, and vendor | ||||
| validate: lint validate-files | ||||
| ## Validate code, docs, and vendor | ||||
| validate: $(PRE_TARGET) | ||||
| 	$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell validate-vendor | ||||
| 	bash $(CURDIR)/script/validate-shell-script.sh | ||||
|  | ||||
| # Target for building images for multiple architectures. | ||||
| .PHONY: multi-arch-image-% | ||||
| multi-arch-image-%: binary-linux-amd64 binary-linux-arm64 | ||||
| 	docker buildx build $(DOCKER_BUILDX_ARGS) -t traefik/traefik:$* --platform=$(DOCKER_BUILD_PLATFORMS) -f Dockerfile . | ||||
| ## Clean up static directory and build a Docker Traefik image | ||||
| build-image: binary | ||||
| 	rm -rf static | ||||
| 	docker build -t $(TRAEFIK_IMAGE) . | ||||
|  | ||||
| ## Build a Docker Traefik image | ||||
| build-image-dirty: binary | ||||
| 	docker build -t $(TRAEFIK_IMAGE) . | ||||
|  | ||||
| .PHONY: build-image | ||||
| #? build-image: Clean up static directory and build a Docker Traefik image | ||||
| build-image: export DOCKER_BUILDX_ARGS := --load | ||||
| build-image: export DOCKER_BUILD_PLATFORMS := linux/$(GOARCH) | ||||
| build-image: clean-webui | ||||
| 	@$(MAKE) multi-arch-image-latest | ||||
| ## Start a shell inside the build env | ||||
| shell: build-dev-image | ||||
| 	$(DOCKER_RUN_TRAEFIK) /bin/bash | ||||
|  | ||||
| .PHONY: build-image-dirty | ||||
| #? build-image-dirty: Build a Docker Traefik image without re-building the webui when it's already built | ||||
| build-image-dirty: export DOCKER_BUILDX_ARGS := --load | ||||
| build-image-dirty: export DOCKER_BUILD_PLATFORMS := linux/$(GOARCH) | ||||
| build-image-dirty: | ||||
| 	@$(MAKE) multi-arch-image-latest | ||||
|  | ||||
| .PHONY: docs | ||||
| #? docs: Build documentation site | ||||
| ## Build documentation site | ||||
| docs: | ||||
| 	make -C ./docs docs | ||||
|  | ||||
| .PHONY: docs-serve | ||||
| #? docs-serve: Serve the documentation site locally | ||||
| ## Serve the documentation site localy | ||||
| docs-serve: | ||||
| 	make -C ./docs docs-serve | ||||
|  | ||||
| .PHONY: docs-pull-images | ||||
| #? docs-pull-images: Pull image for doc building | ||||
| docs-pull-images: | ||||
| 	make -C ./docs docs-pull-images | ||||
|  | ||||
| .PHONY: generate-crd | ||||
| #? generate-crd: Generate CRD clientset and CRD manifests | ||||
| ## Generate CRD clientset | ||||
| generate-crd: | ||||
| 	@$(CURDIR)/script/code-gen.sh | ||||
| 	./script/update-generated-crd-code.sh | ||||
|  | ||||
| .PHONY: generate-genconf | ||||
| #? generate-genconf: Generate code from dynamic configuration github.com/traefik/genconf | ||||
| generate-genconf: | ||||
| 	go run ./cmd/internal/gen/ | ||||
| ## Create packages for the release | ||||
| release-packages: generate-webui build-dev-image | ||||
| 	rm -rf dist | ||||
| 	$(DOCKER_RUN_TRAEFIK_NOTTY) goreleaser release --skip-publish --timeout="60m" | ||||
| 	$(DOCKER_RUN_TRAEFIK_NOTTY) tar cfz dist/traefik-${VERSION}.src.tar.gz \ | ||||
| 		--exclude-vcs \ | ||||
| 		--exclude .idea \ | ||||
| 		--exclude .travis \ | ||||
| 		--exclude .semaphoreci \ | ||||
| 		--exclude .github \ | ||||
| 		--exclude dist . | ||||
| 	$(DOCKER_RUN_TRAEFIK_NOTTY) chown -R $(shell id -u):$(shell id -g) dist/ | ||||
|  | ||||
| .PHONY: fmt | ||||
| #? fmt: Format the Code | ||||
| ## Format the Code | ||||
| fmt: | ||||
| 	gofmt -s -l -w $(SRCS) | ||||
|  | ||||
| .PHONY: help | ||||
| #? help: Get more info on make commands | ||||
| help: Makefile | ||||
| 	@echo " Choose a command run in traefik:" | ||||
| 	@sed -n 's/^#?//p' $< | column -t -s ':' |  sort | sed -e 's/^/ /' | ||||
| run-dev: | ||||
| 	go generate | ||||
| 	GO111MODULE=on go build ./cmd/traefik | ||||
| 	./traefik | ||||
|   | ||||
							
								
								
									
										87
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,20 +1,19 @@ | ||||
|  | ||||
| <p align="center"> | ||||
|     <picture> | ||||
|       <source media="(prefers-color-scheme: dark)" srcset="docs/content/assets/img/traefik.logo-dark.png"> | ||||
|       <source media="(prefers-color-scheme: light)" srcset="docs/content/assets/img/traefik.logo.png"> | ||||
|       <img alt="Traefik" title="Traefik" src="docs/content/assets/img/traefik.logo.png"> | ||||
|     </picture> | ||||
| <img src="docs/content/assets/img/traefik.logo.png" alt="Traefik" title="Traefik" /> | ||||
| </p> | ||||
|  | ||||
| [](https://doc.traefik.io/traefik) | ||||
| [](https://goreportcard.com/report/traefik/traefik) | ||||
| [](https://github.com/traefik/traefik/blob/master/LICENSE.md) | ||||
| [](https://community.traefik.io/) | ||||
| [](https://semaphoreci.com/containous/traefik) | ||||
| [](https://docs.traefik.io) | ||||
| [](http://goreportcard.com/report/containous/traefik) | ||||
| [](https://microbadger.com/images/traefik) | ||||
| [](https://github.com/containous/traefik/blob/master/LICENSE.md) | ||||
| [](https://community.containo.us/) | ||||
| [](https://twitter.com/intent/follow?screen_name=traefik) | ||||
|  | ||||
|  | ||||
| Traefik (pronounced _traffic_) is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy. | ||||
| Traefik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher v2](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically. | ||||
| Traefik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically. | ||||
| Pointing Traefik at your orchestrator should be the _only_ configuration step you need. | ||||
|  | ||||
| --- | ||||
| @@ -34,8 +33,7 @@ Pointing Traefik at your orchestrator should be the _only_ configuration step yo | ||||
|  | ||||
| --- | ||||
|  | ||||
| :warning: When migrating to a new major version of Traefik, please refer to the [migration guide](https://doc.traefik.io/traefik/migrate/v2-to-v3/) to ensure a smooth transition and to be aware of any breaking changes. | ||||
|  | ||||
| :warning: Please be aware that the old configurations for Traefik v1.x are NOT compatible with the v2.x config as of now. If you're running v2, please ensure you are using a [v2 configuration](https://docs.traefik.io/). | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| @@ -58,26 +56,28 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t | ||||
|  | ||||
| - Continuously updates its configuration (No restarts!) | ||||
| - Supports multiple load balancing algorithms | ||||
| - Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support) | ||||
| - Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org)  (wildcard certificates support) | ||||
| - Circuit breakers, retry | ||||
| - See the magic through its clean web UI | ||||
| - WebSocket, HTTP/2, gRPC ready | ||||
| - Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB 2.X) | ||||
| - Websocket, HTTP/2, GRPC ready | ||||
| - Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB) | ||||
| - Keeps access logs (JSON, CLF) | ||||
| - Fast | ||||
| - Exposes a Rest API | ||||
| - Packaged as a single binary file (made with :heart: with go) and available as an [official](https://hub.docker.com/r/_/traefik/) docker image | ||||
| - Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image | ||||
|  | ||||
|  | ||||
| ## Supported Backends | ||||
|  | ||||
| - [Docker](https://doc.traefik.io/traefik/providers/docker/) / [Swarm mode](https://doc.traefik.io/traefik/providers/docker/) | ||||
| - [Kubernetes](https://doc.traefik.io/traefik/providers/kubernetes-crd/) | ||||
| - [ECS](https://doc.traefik.io/traefik/providers/ecs/) | ||||
| - [File](https://doc.traefik.io/traefik/providers/file/) | ||||
| - [Docker](https://docs.traefik.io/providers/docker/) / [Swarm mode](https://docs.traefik.io/providers/docker/) | ||||
| - [Kubernetes](https://docs.traefik.io/providers/kubernetes-crd/) | ||||
| - [Marathon](https://docs.traefik.io/providers/marathon/) | ||||
| - [Rancher](https://docs.traefik.io/providers/rancher/) (Metadata) | ||||
| - [File](https://docs.traefik.io/providers/file/) | ||||
|  | ||||
| ## Quickstart | ||||
|  | ||||
| To get your hands on Traefik, you can use the [5-Minute Quickstart](https://doc.traefik.io/traefik/getting-started/quick-start/) in our documentation (you will need Docker). | ||||
| To get your hands on Traefik, you can use the [5-Minute Quickstart](https://docs.traefik.io/getting-started/quick-start/) in our documentation (you will need Docker). | ||||
|  | ||||
| ## Web UI | ||||
|  | ||||
| @@ -87,25 +87,28 @@ You can access the simple HTML frontend of Traefik. | ||||
|  | ||||
| ## Documentation | ||||
|  | ||||
| You can find the complete documentation of Traefik v3 at [https://doc.traefik.io/traefik/](https://doc.traefik.io/traefik/). | ||||
| You can find the complete documentation of Traefik v2 at [https://docs.traefik.io](https://docs.traefik.io). | ||||
|  | ||||
| If you are using Traefik v1, you can find the complete documentation at [https://docs.traefik.io/v1.7/](https://docs.traefik.io/v1.7/) | ||||
|  | ||||
| A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io). | ||||
|  | ||||
| ## Support | ||||
|  | ||||
| To get community support, you can: | ||||
| - join the Traefik community forum: [](https://community.containo.us/) | ||||
|  | ||||
| - join the Traefik community forum: [](https://community.traefik.io/) | ||||
|  | ||||
| If you need commercial support, please contact [Traefik.io](https://traefik.io) by mail: <mailto:support@traefik.io>. | ||||
| If you need commercial support, please contact [Containo.us](https://containo.us) by mail: <mailto:support@containo.us>. | ||||
|  | ||||
| ## Download | ||||
|  | ||||
| - Grab the latest binary from the [releases](https://github.com/traefik/traefik/releases) page and run it with the [sample configuration file](https://raw.githubusercontent.com/traefik/traefik/master/traefik.sample.toml): | ||||
| - Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml): | ||||
|  | ||||
| ```shell | ||||
| ./traefik --configFile=traefik.toml | ||||
| ``` | ||||
|  | ||||
| - Or use the official tiny Docker image and run it with the [sample configuration file](https://raw.githubusercontent.com/traefik/traefik/master/traefik.sample.toml): | ||||
| - Or use the official tiny Docker image and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml): | ||||
|  | ||||
| ```shell | ||||
| docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik | ||||
| @@ -114,18 +117,16 @@ docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.to | ||||
| - Or get the sources: | ||||
|  | ||||
| ```shell | ||||
| git clone https://github.com/traefik/traefik | ||||
| git clone https://github.com/containous/traefik | ||||
| ``` | ||||
|  | ||||
| ## Introductory Videos | ||||
|  | ||||
| You can find high level and deep dive videos on [videos.traefik.io](https://videos.traefik.io). | ||||
| You can find high level and deep dive videos on [videos.containo.us](https://videos.containo.us) | ||||
|  | ||||
| ## Maintainers | ||||
|  | ||||
| We are strongly promoting a philosophy of openness and sharing, and firmly standing against the elitist closed approach. Being part of the core team should be accessible to anyone who is motivated and want to be part of that journey! | ||||
| This [document](docs/content/contributing/maintainers-guidelines.md) describes how to be part of the [maintainers' team](docs/content/contributing/maintainers.md) as well as various responsibilities and guidelines for Traefik maintainers. | ||||
| You can also find more information on our process to review pull requests and manage issues [in this document](https://github.com/traefik/contributors-guide/blob/master/issue_triage.md). | ||||
| [Information about process and maintainers](docs/content/contributing/maintainers.md) | ||||
|  | ||||
| ## Contributing | ||||
|  | ||||
| @@ -136,24 +137,24 @@ By participating in this project, you agree to abide by its terms. | ||||
|  | ||||
| ## Release Cycle | ||||
|  | ||||
| - We usually release 3/4 new versions (e.g. 1.1.0, 1.2.0, 1.3.0) per year. | ||||
| - Release Candidates are available before the release (e.g. 1.1.0-rc1, 1.1.0-rc2, 1.1.0-rc3, 1.1.0-rc4, before 1.1.0). | ||||
| - Bug-fixes (e.g. 1.1.1, 1.1.2, 1.2.1, 1.2.3) are released as needed (no additional features are delivered in those versions, bug-fixes only). | ||||
| - We release a new version (e.g. 1.1.0, 1.2.0, 1.3.0) every other month. | ||||
| - Release Candidates are available before the release (e.g. 1.1.0-rc1, 1.1.0-rc2, 1.1.0-rc3, 1.1.0-rc4, before 1.1.0) | ||||
| - Bug-fixes (e.g. 1.1.1, 1.1.2, 1.2.1, 1.2.3) are released as needed (no additional features are delivered in those versions, bug-fixes only) | ||||
|  | ||||
| Each version is supported until the next one is released (e.g. 1.1.x will be supported until 1.2.0 is out). | ||||
| Each version is supported until the next one is released (e.g. 1.1.x will be supported until 1.2.0 is out) | ||||
|  | ||||
| We use [Semantic Versioning](https://semver.org/). | ||||
| We use [Semantic Versioning](http://semver.org/) | ||||
|  | ||||
| ## Mailing Lists | ||||
| ## Mailing lists | ||||
|  | ||||
| - General announcements, new releases: mail at news+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/news). | ||||
| - General announcements, new releases: mail at news+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/news) | ||||
| - Security announcements: mail at security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security). | ||||
|  | ||||
| ## Credits | ||||
|  | ||||
| Kudos to [Peka](https://www.instagram.com/pierroks/) for his awesome work on the gopher's logo!. | ||||
| Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo . | ||||
|  | ||||
| The gopher's logo of Traefik is licensed under the Creative Commons 3.0 Attributions license. | ||||
| Traefik's logo is licensed under the Creative Commons 3.0 Attributions license. | ||||
|  | ||||
| The gopher's logo of Traefik was inspired by the gopher stickers made by [Takuya Ueda](https://twitter.com/tenntenn). | ||||
| The original Go gopher was designed by [Renee French](https://reneefrench.blogspot.com/). | ||||
| Traefik's logo was inspired by the gopher stickers made by Takuya Ueda (https://twitter.com/tenntenn). | ||||
| The original Go gopher was designed by Renee French (http://reneefrench.blogspot.com/). | ||||
|   | ||||
							
								
								
									
										30
									
								
								SECURITY.md
									
									
									
									
									
								
							
							
						
						| @@ -1,30 +0,0 @@ | ||||
| # Security Policy | ||||
|  | ||||
| You can join our security mailing list to be aware of the latest announcements from our security team. | ||||
| You can subscribe by sending an email to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security). | ||||
|  | ||||
| Reported vulnerabilities can be found on [cve.mitre.org](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=traefik). | ||||
|  | ||||
| ## Supported Versions | ||||
|  | ||||
| - We usually release 3/4 new versions (e.g. 1.1.0, 1.2.0, 1.3.0) per year. | ||||
| - Release Candidates are available before the release (e.g. 1.1.0-rc1, 1.1.0-rc2, 1.1.0-rc3, 1.1.0-rc4, before 1.1.0). | ||||
| - Bug-fixes (e.g. 1.1.1, 1.1.2, 1.2.1, 1.2.3) are released as needed (no additional features are delivered in those versions, bug-fixes only). | ||||
|  | ||||
| Each version is supported until the next one is released (e.g. 1.1.x will be supported until 1.2.0 is out). | ||||
|  | ||||
| We use [Semantic Versioning](https://semver.org/). | ||||
|  | ||||
| | Version   | Supported          | | ||||
| |-----------|--------------------| | ||||
| | `2.2.x`   | :white_check_mark: | | ||||
| | `< 2.2.x` | :x:                | | ||||
| | `1.7.x`   | :white_check_mark: | | ||||
| | `< 1.7.x` | :x:                | | ||||
|  | ||||
| ## Reporting a Vulnerability | ||||
|  | ||||
| We want to keep Traefik safe for everyone. | ||||
| If you've discovered a security vulnerability in Traefik, | ||||
| we appreciate your help in disclosing it to us in a responsible manner, | ||||
| by creating a [security advisory](https://github.com/traefik/traefik/security/advisories). | ||||
							
								
								
									
										37
									
								
								build.Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,37 @@ | ||||
| FROM golang:1.13-alpine | ||||
|  | ||||
| RUN apk --update upgrade \ | ||||
|     && apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \ | ||||
|     && update-ca-certificates \ | ||||
|     && rm -rf /var/cache/apk/* | ||||
|  | ||||
| # Which docker version to test on | ||||
| ARG DOCKER_VERSION=18.09.7 | ||||
|  | ||||
| # Download docker | ||||
| RUN mkdir -p /usr/local/bin \ | ||||
|     && curl -fL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz \ | ||||
|     | tar -xzC /usr/local/bin --transform 's#^.+/##x' | ||||
|  | ||||
| # Download go-bindata binary to bin folder in $GOPATH | ||||
| RUN mkdir -p /usr/local/bin \ | ||||
|     && curl -fsSL -o /usr/local/bin/go-bindata https://github.com/containous/go-bindata/releases/download/v1.0.0/go-bindata \ | ||||
|     && chmod +x /usr/local/bin/go-bindata | ||||
|  | ||||
| # Download golangci-lint binary to bin folder in $GOPATH | ||||
| RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.20.0 | ||||
|  | ||||
| # Download golangci-lint and misspell binary to bin folder in $GOPATH | ||||
| RUN GO111MODULE=off go get github.com/client9/misspell/cmd/misspell | ||||
|  | ||||
| # Download goreleaser binary to bin folder in $GOPATH | ||||
| RUN curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh | ||||
|  | ||||
| WORKDIR /go/src/github.com/containous/traefik | ||||
|  | ||||
| # Download go modules | ||||
| COPY go.mod . | ||||
| COPY go.sum . | ||||
| RUN GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download | ||||
|  | ||||
| COPY . /go/src/github.com/containous/traefik | ||||
| @@ -3,8 +3,8 @@ package cmd | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	ptypes "github.com/traefik/paerser/types" | ||||
| 	"github.com/traefik/traefik/v3/pkg/config/static" | ||||
| 	"github.com/containous/traefik/v2/pkg/config/static" | ||||
| 	"github.com/containous/traefik/v2/pkg/types" | ||||
| ) | ||||
|  | ||||
| // TraefikCmdConfiguration wraps the static configuration and extra parameters. | ||||
| @@ -23,15 +23,11 @@ func NewTraefikConfiguration() *TraefikCmdConfiguration { | ||||
| 			}, | ||||
| 			EntryPoints: make(static.EntryPoints), | ||||
| 			Providers: &static.Providers{ | ||||
| 				ProvidersThrottleDuration: ptypes.Duration(2 * time.Second), | ||||
| 				ProvidersThrottleDuration: types.Duration(2 * time.Second), | ||||
| 			}, | ||||
| 			ServersTransport: &static.ServersTransport{ | ||||
| 				MaxIdleConnsPerHost: 200, | ||||
| 			}, | ||||
| 			TCPServersTransport: &static.TCPServersTransport{ | ||||
| 				DialTimeout:   ptypes.Duration(30 * time.Second), | ||||
| 				DialKeepAlive: ptypes.Duration(15 * time.Second), | ||||
| 			}, | ||||
| 		}, | ||||
| 		ConfigFile: "", | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										22
									
								
								cmd/context.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,22 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"syscall" | ||||
| ) | ||||
|  | ||||
| // ContextWithSignal creates a context canceled when SIGINT or SIGTERM are notified | ||||
| func ContextWithSignal(ctx context.Context) context.Context { | ||||
| 	newCtx, cancel := context.WithCancel(ctx) | ||||
| 	signals := make(chan os.Signal) | ||||
| 	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) | ||||
| 	go func() { | ||||
| 		select { | ||||
| 		case <-signals: | ||||
| 			cancel() | ||||
| 		} | ||||
| 	}() | ||||
| 	return newCtx | ||||
| } | ||||
| @@ -7,8 +7,8 @@ import ( | ||||
| 	"os" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/traefik/paerser/cli" | ||||
| 	"github.com/traefik/traefik/v3/pkg/config/static" | ||||
| 	"github.com/containous/traefik/v2/pkg/cli" | ||||
| 	"github.com/containous/traefik/v2/pkg/config/static" | ||||
| ) | ||||
|  | ||||
| // NewCmd builds a new HealthCheck command. | ||||
| @@ -45,7 +45,7 @@ func runCmd(traefikConfiguration *static.Configuration) func(_ []string) error { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Do try to do a healthcheck. | ||||
| // Do try to do a healthcheck | ||||
| func Do(staticConfiguration static.Configuration) (*http.Response, error) { | ||||
| 	if staticConfiguration.Ping == nil { | ||||
| 		return nil, errors.New("please enable `ping` to use health check") | ||||
| @@ -64,7 +64,7 @@ func Do(staticConfiguration static.Configuration) (*http.Response, error) { | ||||
| 	client := &http.Client{Timeout: 5 * time.Second} | ||||
| 	protocol := "http" | ||||
|  | ||||
| 	// TODO Handle TLS on ping etc... | ||||
| 	// FIXME Handle TLS on ping etc... | ||||
| 	// if pingEntryPoint.TLS != nil { | ||||
| 	// 	protocol = "https" | ||||
| 	// 	tr := &http.Transport{ | ||||
| @@ -75,5 +75,5 @@ func Do(staticConfiguration static.Configuration) (*http.Response, error) { | ||||
|  | ||||
| 	path := "/" | ||||
|  | ||||
| 	return client.Head(protocol + "://" + pingEntryPoint.GetAddress() + path + "ping") | ||||
| 	return client.Head(protocol + "://" + pingEntryPoint.Address + path + "ping") | ||||
| } | ||||
|   | ||||
| @@ -1,332 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"go/format" | ||||
| 	"go/importer" | ||||
| 	"go/token" | ||||
| 	"go/types" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"reflect" | ||||
| 	"slices" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
|  | ||||
| 	"golang.org/x/tools/imports" | ||||
| ) | ||||
|  | ||||
| // File a kind of AST element that represents a file. | ||||
| type File struct { | ||||
| 	Package  string | ||||
| 	Imports  []string | ||||
| 	Elements []Element | ||||
| } | ||||
|  | ||||
| // Element is a simplified version of a symbol. | ||||
| type Element struct { | ||||
| 	Name  string | ||||
| 	Value string | ||||
| } | ||||
|  | ||||
| // Centrifuge a centrifuge. | ||||
| // Generate Go Structures from Go structures. | ||||
| type Centrifuge struct { | ||||
| 	IncludedImports []string | ||||
| 	ExcludedTypes   []string | ||||
| 	ExcludedFiles   []string | ||||
|  | ||||
| 	TypeCleaner    func(types.Type, string) string | ||||
| 	PackageCleaner func(string) string | ||||
|  | ||||
| 	rootPkg string | ||||
| 	fileSet *token.FileSet | ||||
| 	pkg     *types.Package | ||||
| } | ||||
|  | ||||
| // NewCentrifuge creates a new Centrifuge. | ||||
| func NewCentrifuge(rootPkg string) (*Centrifuge, error) { | ||||
| 	fileSet := token.NewFileSet() | ||||
|  | ||||
| 	pkg, err := importer.ForCompiler(fileSet, "source", nil).Import(rootPkg) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &Centrifuge{ | ||||
| 		fileSet: fileSet, | ||||
| 		pkg:     pkg, | ||||
| 		rootPkg: rootPkg, | ||||
|  | ||||
| 		TypeCleaner: func(typ types.Type, _ string) string { | ||||
| 			return typ.String() | ||||
| 		}, | ||||
| 		PackageCleaner: func(s string) string { | ||||
| 			return s | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Run runs the code extraction and the code generation. | ||||
| func (c Centrifuge) Run(dest string, pkgName string) error { | ||||
| 	files := c.run(c.pkg.Scope(), c.rootPkg, pkgName) | ||||
|  | ||||
| 	err := fileWriter{baseDir: dest}.Write(files) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, p := range c.pkg.Imports() { | ||||
| 		if slices.Contains(c.IncludedImports, p.Path()) { | ||||
| 			fls := c.run(p.Scope(), p.Path(), p.Name()) | ||||
|  | ||||
| 			err = fileWriter{baseDir: filepath.Join(dest, p.Name())}.Write(fls) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c Centrifuge) run(sc *types.Scope, rootPkg string, pkgName string) map[string]*File { | ||||
| 	files := map[string]*File{} | ||||
|  | ||||
| 	for _, name := range sc.Names() { | ||||
| 		if slices.Contains(c.ExcludedTypes, name) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		o := sc.Lookup(name) | ||||
| 		if !o.Exported() { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		filename := filepath.Base(c.fileSet.File(o.Pos()).Name()) | ||||
| 		if slices.Contains(c.ExcludedFiles, path.Join(rootPkg, filename)) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		fl, ok := files[filename] | ||||
| 		if !ok { | ||||
| 			files[filename] = &File{Package: pkgName} | ||||
| 			fl = files[filename] | ||||
| 		} | ||||
|  | ||||
| 		elt := Element{ | ||||
| 			Name: name, | ||||
| 		} | ||||
|  | ||||
| 		switch ob := o.(type) { | ||||
| 		case *types.TypeName: | ||||
|  | ||||
| 			switch obj := ob.Type().(*types.Named).Underlying().(type) { | ||||
| 			case *types.Struct: | ||||
| 				elt.Value = c.writeStruct(name, obj, rootPkg, fl) | ||||
|  | ||||
| 			case *types.Map: | ||||
| 				elt.Value = fmt.Sprintf("type %s map[%s]%s\n", name, obj.Key().String(), c.TypeCleaner(obj.Elem(), rootPkg)) | ||||
|  | ||||
| 			case *types.Slice: | ||||
| 				elt.Value = fmt.Sprintf("type %s []%v\n", name, c.TypeCleaner(obj.Elem(), rootPkg)) | ||||
|  | ||||
| 			case *types.Basic: | ||||
| 				elt.Value = fmt.Sprintf("type %s %v\n", name, obj.Name()) | ||||
|  | ||||
| 			default: | ||||
| 				log.Printf("OTHER TYPE::: %s %T\n", name, o.Type().(*types.Named).Underlying()) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 		default: | ||||
| 			log.Printf("OTHER::: %s %T\n", name, o) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if len(elt.Value) > 0 { | ||||
| 			fl.Elements = append(fl.Elements, elt) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return files | ||||
| } | ||||
|  | ||||
| func (c Centrifuge) writeStruct(name string, obj *types.Struct, rootPkg string, elt *File) string { | ||||
| 	b := strings.Builder{} | ||||
| 	b.WriteString(fmt.Sprintf("type %s struct {\n", name)) | ||||
|  | ||||
| 	for i := range obj.NumFields() { | ||||
| 		field := obj.Field(i) | ||||
|  | ||||
| 		if !field.Exported() { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		fPkg := c.PackageCleaner(extractPackage(field.Type())) | ||||
| 		if fPkg != "" && fPkg != rootPkg { | ||||
| 			elt.Imports = append(elt.Imports, fPkg) | ||||
| 		} | ||||
|  | ||||
| 		fType := c.TypeCleaner(field.Type(), rootPkg) | ||||
|  | ||||
| 		if field.Embedded() { | ||||
| 			b.WriteString(fmt.Sprintf("\t%s\n", fType)) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		values, ok := lookupTagValue(obj.Tag(i), "json") | ||||
| 		if len(values) > 0 && values[0] == "-" { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		b.WriteString(fmt.Sprintf("\t%s %s", field.Name(), fType)) | ||||
|  | ||||
| 		if ok { | ||||
| 			b.WriteString(fmt.Sprintf(" `json:\"%s\"`", strings.Join(values, ","))) | ||||
| 		} | ||||
|  | ||||
| 		b.WriteString("\n") | ||||
| 	} | ||||
|  | ||||
| 	b.WriteString("}\n") | ||||
|  | ||||
| 	return b.String() | ||||
| } | ||||
|  | ||||
| func lookupTagValue(raw, key string) ([]string, bool) { | ||||
| 	value, ok := reflect.StructTag(raw).Lookup(key) | ||||
| 	if !ok { | ||||
| 		return nil, ok | ||||
| 	} | ||||
|  | ||||
| 	values := strings.Split(value, ",") | ||||
|  | ||||
| 	if len(values) < 1 { | ||||
| 		return nil, true | ||||
| 	} | ||||
|  | ||||
| 	return values, true | ||||
| } | ||||
|  | ||||
| func extractPackage(t types.Type) string { | ||||
| 	switch tu := t.(type) { | ||||
| 	case *types.Named: | ||||
| 		return tu.Obj().Pkg().Path() | ||||
|  | ||||
| 	case *types.Slice: | ||||
| 		if v, ok := tu.Elem().(*types.Named); ok { | ||||
| 			return v.Obj().Pkg().Path() | ||||
| 		} | ||||
| 		return "" | ||||
|  | ||||
| 	case *types.Map: | ||||
| 		if v, ok := tu.Elem().(*types.Named); ok { | ||||
| 			return v.Obj().Pkg().Path() | ||||
| 		} | ||||
| 		return "" | ||||
|  | ||||
| 	case *types.Pointer: | ||||
| 		return extractPackage(tu.Elem()) | ||||
|  | ||||
| 	default: | ||||
| 		return "" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type fileWriter struct { | ||||
| 	baseDir string | ||||
| } | ||||
|  | ||||
| func (f fileWriter) Write(files map[string]*File) error { | ||||
| 	err := os.MkdirAll(f.baseDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for name, file := range files { | ||||
| 		err = f.writeFile(name, file) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f fileWriter) writeFile(name string, desc *File) error { | ||||
| 	if len(desc.Elements) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	filename := filepath.Join(f.baseDir, name) | ||||
|  | ||||
| 	file, err := os.Create(filename) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to create file: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { _ = file.Close() }() | ||||
|  | ||||
| 	b := bytes.NewBufferString("package ") | ||||
| 	b.WriteString(desc.Package) | ||||
| 	b.WriteString("\n") | ||||
| 	b.WriteString("// Code generated by centrifuge. DO NOT EDIT.\n") | ||||
|  | ||||
| 	b.WriteString("\n") | ||||
| 	f.writeImports(b, desc.Imports) | ||||
| 	b.WriteString("\n") | ||||
|  | ||||
| 	for _, elt := range desc.Elements { | ||||
| 		b.WriteString(elt.Value) | ||||
| 		b.WriteString("\n") | ||||
| 	} | ||||
|  | ||||
| 	// gofmt | ||||
| 	source, err := format.Source(b.Bytes()) | ||||
| 	if err != nil { | ||||
| 		log.Println(b.String()) | ||||
| 		return fmt.Errorf("failed to format sources: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// goimports | ||||
| 	process, err := imports.Process(filename, source, nil) | ||||
| 	if err != nil { | ||||
| 		log.Println(string(source)) | ||||
| 		return fmt.Errorf("failed to format imports: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = file.Write(process) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f fileWriter) writeImports(b io.StringWriter, imports []string) { | ||||
| 	if len(imports) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	uniq := map[string]struct{}{} | ||||
|  | ||||
| 	sort.Strings(imports) | ||||
|  | ||||
| 	_, _ = b.WriteString("import (\n") | ||||
| 	for _, s := range imports { | ||||
| 		if _, exist := uniq[s]; exist { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		uniq[s] = struct{}{} | ||||
|  | ||||
| 		_, _ = b.WriteString(fmt.Sprintf(`	"%s"`+"\n", s)) | ||||
| 	} | ||||
|  | ||||
| 	_, _ = b.WriteString(")\n") | ||||
| } | ||||
| @@ -1,124 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"go/build" | ||||
| 	"go/types" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const rootPkg = "github.com/traefik/traefik/v3/pkg/config/dynamic" | ||||
|  | ||||
| const ( | ||||
| 	destModuleName = "github.com/traefik/genconf" | ||||
| 	destPkg        = "dynamic" | ||||
| ) | ||||
|  | ||||
| const marsh = `package %s | ||||
|  | ||||
| import "encoding/json" | ||||
|  | ||||
| type JSONPayload struct { | ||||
| 	*Configuration | ||||
| } | ||||
|  | ||||
| func (c JSONPayload) MarshalJSON() ([]byte, error) { | ||||
| 	if c.Configuration == nil { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	return json.Marshal(c.Configuration) | ||||
| } | ||||
| ` | ||||
|  | ||||
| // main generate Go Structures from Go structures. | ||||
| // Allows to create an external module (destModuleName) used by the plugin's providers | ||||
| // that contains Go structs of the dynamic configuration and nothing else. | ||||
| // These Go structs do not have any non-exported fields and do not rely on any external dependencies. | ||||
| func main() { | ||||
| 	dest := filepath.Join(path.Join(build.Default.GOPATH, "src"), destModuleName, destPkg) | ||||
|  | ||||
| 	log.Println("Output:", dest) | ||||
|  | ||||
| 	err := run(dest) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func run(dest string) error { | ||||
| 	centrifuge, err := NewCentrifuge(rootPkg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	centrifuge.IncludedImports = []string{ | ||||
| 		"github.com/traefik/traefik/v3/pkg/tls", | ||||
| 		"github.com/traefik/traefik/v3/pkg/types", | ||||
| 	} | ||||
|  | ||||
| 	centrifuge.ExcludedTypes = []string{ | ||||
| 		// tls | ||||
| 		"CertificateStore", "Manager", | ||||
| 		// dynamic | ||||
| 		"Message", "Configurations", | ||||
| 		// types | ||||
| 		"HTTPCodeRanges", "HostResolverConfig", | ||||
| 	} | ||||
|  | ||||
| 	centrifuge.ExcludedFiles = []string{ | ||||
| 		"github.com/traefik/traefik/v3/pkg/types/logs.go", | ||||
| 		"github.com/traefik/traefik/v3/pkg/types/metrics.go", | ||||
| 	} | ||||
|  | ||||
| 	centrifuge.TypeCleaner = cleanType | ||||
| 	centrifuge.PackageCleaner = cleanPackage | ||||
|  | ||||
| 	err = centrifuge.Run(dest, destPkg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return os.WriteFile(filepath.Join(dest, "marshaler.go"), []byte(fmt.Sprintf(marsh, destPkg)), 0o666) | ||||
| } | ||||
|  | ||||
| func cleanType(typ types.Type, base string) string { | ||||
| 	if typ.String() == "github.com/traefik/traefik/v3/pkg/types.FileOrContent" { | ||||
| 		return "string" | ||||
| 	} | ||||
|  | ||||
| 	if typ.String() == "[]github.com/traefik/traefik/v3/pkg/types.FileOrContent" { | ||||
| 		return "[]string" | ||||
| 	} | ||||
|  | ||||
| 	if typ.String() == "github.com/traefik/paerser/types.Duration" { | ||||
| 		return "string" | ||||
| 	} | ||||
|  | ||||
| 	if strings.Contains(typ.String(), base) { | ||||
| 		return strings.ReplaceAll(typ.String(), base+".", "") | ||||
| 	} | ||||
|  | ||||
| 	if strings.Contains(typ.String(), "github.com/traefik/traefik/v3/pkg/") { | ||||
| 		return strings.ReplaceAll(typ.String(), "github.com/traefik/traefik/v3/pkg/", "") | ||||
| 	} | ||||
|  | ||||
| 	return typ.String() | ||||
| } | ||||
|  | ||||
| func cleanPackage(src string) string { | ||||
| 	switch src { | ||||
| 	case "github.com/traefik/paerser/types": | ||||
| 		return "" | ||||
| 	case "github.com/traefik/traefik/v3/pkg/tls": | ||||
| 		return path.Join(destModuleName, destPkg, "tls") | ||||
| 	case "github.com/traefik/traefik/v3/pkg/types": | ||||
| 		return path.Join(destModuleName, destPkg, "types") | ||||
| 	default: | ||||
| 		return src | ||||
| 	} | ||||
| } | ||||
| @@ -1,110 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	stdlog "log" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/rs/zerolog" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/traefik/traefik/v3/pkg/config/static" | ||||
| 	"github.com/traefik/traefik/v3/pkg/observability/logs" | ||||
| 	"gopkg.in/natefinch/lumberjack.v2" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	// hide the first logs before the setup of the logger. | ||||
| 	zerolog.SetGlobalLevel(zerolog.ErrorLevel) | ||||
| } | ||||
|  | ||||
| func setupLogger(ctx context.Context, staticConfiguration *static.Configuration) error { | ||||
| 	// Validate that the experimental flag is set up at this point, | ||||
| 	// rather than validating the static configuration before the setupLogger call. | ||||
| 	// This ensures that validation messages are not logged using an un-configured logger. | ||||
| 	if staticConfiguration.Log != nil && staticConfiguration.Log.OTLP != nil && | ||||
| 		(staticConfiguration.Experimental == nil || !staticConfiguration.Experimental.OTLPLogs) { | ||||
| 		return errors.New("the experimental OTLPLogs feature must be enabled to use OTLP logging") | ||||
| 	} | ||||
|  | ||||
| 	// configure log format | ||||
| 	w := getLogWriter(staticConfiguration) | ||||
|  | ||||
| 	// configure log level | ||||
| 	logLevel := getLogLevel(staticConfiguration) | ||||
| 	zerolog.SetGlobalLevel(logLevel) | ||||
|  | ||||
| 	// create logger | ||||
| 	logger := zerolog.New(w).With().Timestamp() | ||||
| 	if logLevel <= zerolog.DebugLevel { | ||||
| 		logger = logger.Caller() | ||||
| 	} | ||||
|  | ||||
| 	log.Logger = logger.Logger().Level(logLevel) | ||||
|  | ||||
| 	if staticConfiguration.Log != nil && staticConfiguration.Log.OTLP != nil { | ||||
| 		var err error | ||||
| 		log.Logger, err = logs.SetupOTelLogger(ctx, log.Logger, staticConfiguration.Log.OTLP) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("setting up OpenTelemetry logger: %w", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	zerolog.DefaultContextLogger = &log.Logger | ||||
|  | ||||
| 	// Global logrus replacement (related to lib like go-rancher-metadata, docker, etc.) | ||||
| 	logrus.StandardLogger().Out = logs.NoLevel(log.Logger, zerolog.DebugLevel) | ||||
|  | ||||
| 	// configure default standard log. | ||||
| 	stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags) | ||||
| 	stdlog.SetOutput(logs.NoLevel(log.Logger, zerolog.DebugLevel)) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func getLogWriter(staticConfiguration *static.Configuration) io.Writer { | ||||
| 	var w io.Writer = os.Stdout | ||||
| 	if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 { | ||||
| 		_, _ = os.OpenFile(staticConfiguration.Log.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) | ||||
| 		w = &lumberjack.Logger{ | ||||
| 			Filename:   staticConfiguration.Log.FilePath, | ||||
| 			MaxSize:    staticConfiguration.Log.MaxSize, | ||||
| 			MaxBackups: staticConfiguration.Log.MaxBackups, | ||||
| 			MaxAge:     staticConfiguration.Log.MaxAge, | ||||
| 			Compress:   true, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if staticConfiguration.Log == nil || staticConfiguration.Log.Format != "json" { | ||||
| 		w = zerolog.ConsoleWriter{ | ||||
| 			Out:        w, | ||||
| 			TimeFormat: time.RFC3339, | ||||
| 			NoColor:    staticConfiguration.Log != nil && (staticConfiguration.Log.NoColor || len(staticConfiguration.Log.FilePath) > 0), | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| func getLogLevel(staticConfiguration *static.Configuration) zerolog.Level { | ||||
| 	levelStr := "error" | ||||
| 	if staticConfiguration.Log != nil && staticConfiguration.Log.Level != "" { | ||||
| 		levelStr = strings.ToLower(staticConfiguration.Log.Level) | ||||
| 	} | ||||
|  | ||||
| 	logLevel, err := zerolog.ParseLevel(strings.ToLower(levelStr)) | ||||
| 	if err != nil { | ||||
| 		log.Error().Err(err). | ||||
| 			Str("logLevel", levelStr). | ||||
| 			Msg("Unspecified or invalid log level, setting the level to default (ERROR)...") | ||||
|  | ||||
| 		logLevel = zerolog.ErrorLevel | ||||
| 	} | ||||
|  | ||||
| 	return logLevel | ||||
| } | ||||
| @@ -1,102 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"path/filepath" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/hashicorp/go-retryablehttp" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/traefik/traefik/v3/pkg/config/static" | ||||
| 	"github.com/traefik/traefik/v3/pkg/observability/logs" | ||||
| 	"github.com/traefik/traefik/v3/pkg/plugins" | ||||
| ) | ||||
|  | ||||
| const outputDir = "./plugins-storage/" | ||||
|  | ||||
| func createPluginBuilder(staticConfiguration *static.Configuration) (*plugins.Builder, error) { | ||||
| 	manager, plgs, localPlgs, err := initPlugins(staticConfiguration) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return plugins.NewBuilder(manager, plgs, localPlgs) | ||||
| } | ||||
|  | ||||
| func initPlugins(staticCfg *static.Configuration) (*plugins.Manager, map[string]plugins.Descriptor, map[string]plugins.LocalDescriptor, error) { | ||||
| 	err := checkUniquePluginNames(staticCfg.Experimental) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	var manager *plugins.Manager | ||||
| 	plgs := map[string]plugins.Descriptor{} | ||||
|  | ||||
| 	if hasPlugins(staticCfg) { | ||||
| 		httpClient := retryablehttp.NewClient() | ||||
| 		httpClient.Logger = logs.NewRetryableHTTPLogger(log.Logger) | ||||
| 		httpClient.HTTPClient = &http.Client{Timeout: 10 * time.Second} | ||||
| 		httpClient.RetryMax = 3 | ||||
|  | ||||
| 		// Create separate downloader for HTTP operations | ||||
| 		archivesPath := filepath.Join(outputDir, "archives") | ||||
| 		downloader, err := plugins.NewRegistryDownloader(plugins.RegistryDownloaderOptions{ | ||||
| 			HTTPClient:   httpClient.HTTPClient, | ||||
| 			ArchivesPath: archivesPath, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, nil, fmt.Errorf("unable to create plugin downloader: %w", err) | ||||
| 		} | ||||
|  | ||||
| 		opts := plugins.ManagerOptions{ | ||||
| 			Output: outputDir, | ||||
| 		} | ||||
| 		manager, err = plugins.NewManager(downloader, opts) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, nil, fmt.Errorf("unable to create plugins manager: %w", err) | ||||
| 		} | ||||
|  | ||||
| 		err = plugins.SetupRemotePlugins(manager, staticCfg.Experimental.Plugins) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, nil, fmt.Errorf("unable to set up plugins environment: %w", err) | ||||
| 		} | ||||
|  | ||||
| 		plgs = staticCfg.Experimental.Plugins | ||||
| 	} | ||||
|  | ||||
| 	localPlgs := map[string]plugins.LocalDescriptor{} | ||||
|  | ||||
| 	if hasLocalPlugins(staticCfg) { | ||||
| 		err := plugins.SetupLocalPlugins(staticCfg.Experimental.LocalPlugins) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, nil, err | ||||
| 		} | ||||
|  | ||||
| 		localPlgs = staticCfg.Experimental.LocalPlugins | ||||
| 	} | ||||
|  | ||||
| 	return manager, plgs, localPlgs, nil | ||||
| } | ||||
|  | ||||
| func checkUniquePluginNames(e *static.Experimental) error { | ||||
| 	if e == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	for s := range e.LocalPlugins { | ||||
| 		if _, ok := e.Plugins[s]; ok { | ||||
| 			return fmt.Errorf("the plugin's name %q must be unique", s) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func hasPlugins(staticCfg *static.Configuration) bool { | ||||
| 	return staticCfg.Experimental != nil && len(staticCfg.Experimental.Plugins) > 0 | ||||
| } | ||||
|  | ||||
| func hasLocalPlugins(staticCfg *static.Configuration) bool { | ||||
| 	return staticCfg.Experimental != nil && len(staticCfg.Experimental.LocalPlugins) > 0 | ||||
| } | ||||
| @@ -2,60 +2,42 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/x509" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	stdlog "log" | ||||
| 	"maps" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"slices" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coreos/go-systemd/v22/daemon" | ||||
| 	"github.com/go-acme/lego/v4/challenge" | ||||
| 	gokitmetrics "github.com/go-kit/kit/metrics" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/containous/traefik/v2/autogen/genstatic" | ||||
| 	"github.com/containous/traefik/v2/cmd" | ||||
| 	"github.com/containous/traefik/v2/cmd/healthcheck" | ||||
| 	cmdVersion "github.com/containous/traefik/v2/cmd/version" | ||||
| 	"github.com/containous/traefik/v2/pkg/cli" | ||||
| 	"github.com/containous/traefik/v2/pkg/collector" | ||||
| 	"github.com/containous/traefik/v2/pkg/config/dynamic" | ||||
| 	"github.com/containous/traefik/v2/pkg/config/static" | ||||
| 	"github.com/containous/traefik/v2/pkg/log" | ||||
| 	"github.com/containous/traefik/v2/pkg/provider/acme" | ||||
| 	"github.com/containous/traefik/v2/pkg/provider/aggregator" | ||||
| 	"github.com/containous/traefik/v2/pkg/safe" | ||||
| 	"github.com/containous/traefik/v2/pkg/server" | ||||
| 	"github.com/containous/traefik/v2/pkg/server/router" | ||||
| 	traefiktls "github.com/containous/traefik/v2/pkg/tls" | ||||
| 	"github.com/containous/traefik/v2/pkg/version" | ||||
| 	"github.com/coreos/go-systemd/daemon" | ||||
| 	assetfs "github.com/elazarl/go-bindata-assetfs" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/spiffe/go-spiffe/v2/workloadapi" | ||||
| 	"github.com/traefik/paerser/cli" | ||||
| 	"github.com/traefik/traefik/v3/cmd" | ||||
| 	"github.com/traefik/traefik/v3/cmd/healthcheck" | ||||
| 	cmdVersion "github.com/traefik/traefik/v3/cmd/version" | ||||
| 	tcli "github.com/traefik/traefik/v3/pkg/cli" | ||||
| 	"github.com/traefik/traefik/v3/pkg/collector" | ||||
| 	"github.com/traefik/traefik/v3/pkg/config/dynamic" | ||||
| 	"github.com/traefik/traefik/v3/pkg/config/runtime" | ||||
| 	"github.com/traefik/traefik/v3/pkg/config/static" | ||||
| 	"github.com/traefik/traefik/v3/pkg/middlewares/accesslog" | ||||
| 	"github.com/traefik/traefik/v3/pkg/observability/logs" | ||||
| 	"github.com/traefik/traefik/v3/pkg/observability/metrics" | ||||
| 	"github.com/traefik/traefik/v3/pkg/observability/tracing" | ||||
| 	otypes "github.com/traefik/traefik/v3/pkg/observability/types" | ||||
| 	"github.com/traefik/traefik/v3/pkg/provider/acme" | ||||
| 	"github.com/traefik/traefik/v3/pkg/provider/aggregator" | ||||
| 	"github.com/traefik/traefik/v3/pkg/provider/tailscale" | ||||
| 	"github.com/traefik/traefik/v3/pkg/provider/traefik" | ||||
| 	"github.com/traefik/traefik/v3/pkg/proxy" | ||||
| 	"github.com/traefik/traefik/v3/pkg/proxy/httputil" | ||||
| 	"github.com/traefik/traefik/v3/pkg/redactor" | ||||
| 	"github.com/traefik/traefik/v3/pkg/safe" | ||||
| 	"github.com/traefik/traefik/v3/pkg/server" | ||||
| 	"github.com/traefik/traefik/v3/pkg/server/middleware" | ||||
| 	"github.com/traefik/traefik/v3/pkg/server/service" | ||||
| 	"github.com/traefik/traefik/v3/pkg/tcp" | ||||
| 	traefiktls "github.com/traefik/traefik/v3/pkg/tls" | ||||
| 	"github.com/traefik/traefik/v3/pkg/version" | ||||
| 	"github.com/vulcand/oxy/roundrobin" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// traefik config inits | ||||
| 	tConfig := cmd.NewTraefikConfiguration() | ||||
|  | ||||
| 	loaders := []cli.ResourceLoader{&tcli.DeprecationLoader{}, &tcli.FileLoader{}, &tcli.FlagLoader{}, &tcli.EnvLoader{}} | ||||
| 	loaders := []cli.ResourceLoader{&cli.FileLoader{}, &cli.FlagLoader{}, &cli.EnvLoader{}} | ||||
|  | ||||
| 	cmdTraefik := &cli.Command{ | ||||
| 		Name: "traefik", | ||||
| @@ -82,36 +64,39 @@ Complete documentation is available at https://traefik.io`, | ||||
|  | ||||
| 	err = cli.Execute(cmdTraefik) | ||||
| 	if err != nil { | ||||
| 		log.Error().Err(err).Msg("Command error") | ||||
| 		logrus.Exit(1) | ||||
| 		stdlog.Println(err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	logrus.Exit(0) | ||||
| 	os.Exit(0) | ||||
| } | ||||
|  | ||||
| func runCmd(staticConfiguration *static.Configuration) error { | ||||
| 	ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	if err := setupLogger(ctx, staticConfiguration); err != nil { | ||||
| 		return fmt.Errorf("setting up logger: %w", err) | ||||
| 	} | ||||
| 	configureLogging(staticConfiguration) | ||||
|  | ||||
| 	http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment | ||||
|  | ||||
| 	if err := roundrobin.SetDefaultWeight(0); err != nil { | ||||
| 		log.WithoutContext().Errorf("Could not set roundrobin default weight: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	staticConfiguration.SetEffectiveConfiguration() | ||||
| 	if err := staticConfiguration.ValidateConfiguration(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	log.Info().Str("version", version.Version). | ||||
| 		Msgf("Traefik version %s built on %s", version.Version, version.BuildDate) | ||||
| 	log.WithoutContext().Infof("Traefik version %s built on %s", version.Version, version.BuildDate) | ||||
|  | ||||
| 	redactedStaticConfiguration, err := redactor.RemoveCredentials(staticConfiguration) | ||||
| 	jsonConf, err := json.Marshal(staticConfiguration) | ||||
| 	if err != nil { | ||||
| 		log.Error().Err(err).Msg("Could not redact static configuration") | ||||
| 		log.WithoutContext().Errorf("Could not marshal static configuration: %v", err) | ||||
| 		log.WithoutContext().Debugf("Static configuration loaded [struct] %#v", staticConfiguration) | ||||
| 	} else { | ||||
| 		log.Debug().RawJSON("staticConfiguration", []byte(redactedStaticConfiguration)).Msg("Static configuration loaded [json]") | ||||
| 		log.WithoutContext().Debugf("Static configuration loaded %s", string(jsonConf)) | ||||
| 	} | ||||
|  | ||||
| 	if staticConfiguration.API != nil && staticConfiguration.API.Dashboard { | ||||
| 		staticConfiguration.API.DashboardAssets = &assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"} | ||||
| 	} | ||||
|  | ||||
| 	if staticConfiguration.Global.CheckNewVersion { | ||||
| @@ -120,11 +105,45 @@ func runCmd(staticConfiguration *static.Configuration) error { | ||||
|  | ||||
| 	stats(staticConfiguration) | ||||
|  | ||||
| 	svr, err := setupServer(staticConfiguration) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers) | ||||
|  | ||||
| 	tlsManager := traefiktls.NewManager() | ||||
|  | ||||
| 	acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager) | ||||
|  | ||||
| 	serverEntryPointsTCP := make(server.TCPEntryPoints) | ||||
| 	for entryPointName, config := range staticConfiguration.EntryPoints { | ||||
| 		ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName)) | ||||
| 		serverEntryPointsTCP[entryPointName], err = server.NewTCPEntryPoint(ctx, config) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err) | ||||
| 		} | ||||
| 		serverEntryPointsTCP[entryPointName].RouteAppenderFactory = router.NewRouteAppenderFactory(*staticConfiguration, entryPointName, acmeProviders) | ||||
| 	} | ||||
|  | ||||
| 	svr := server.NewServer(*staticConfiguration, providerAggregator, serverEntryPointsTCP, tlsManager) | ||||
|  | ||||
| 	resolverNames := map[string]struct{}{} | ||||
|  | ||||
| 	for _, p := range acmeProviders { | ||||
| 		resolverNames[p.ResolverName] = struct{}{} | ||||
| 		svr.AddListener(p.ListenConfiguration) | ||||
| 	} | ||||
|  | ||||
| 	svr.AddListener(func(config dynamic.Configuration) { | ||||
| 		for rtName, rt := range config.HTTP.Routers { | ||||
| 			if rt.TLS == nil || rt.TLS.CertResolver == "" { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			if _, ok := resolverNames[rt.TLS.CertResolver]; !ok { | ||||
| 				log.WithoutContext().Errorf("the router %s uses a non-existent resolver: %s", rtName, rt.TLS.CertResolver) | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	ctx := cmd.ContextWithSignal(context.Background()) | ||||
|  | ||||
| 	if staticConfiguration.Ping != nil { | ||||
| 		staticConfiguration.Ping.WithContext(ctx) | ||||
| 	} | ||||
| @@ -134,484 +153,126 @@ func runCmd(staticConfiguration *static.Configuration) error { | ||||
|  | ||||
| 	sent, err := daemon.SdNotify(false, "READY=1") | ||||
| 	if !sent && err != nil { | ||||
| 		log.Error().Err(err).Msg("Failed to notify") | ||||
| 		log.WithoutContext().Errorf("Failed to notify: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	t, err := daemon.SdWatchdogEnabled(false) | ||||
| 	if err != nil { | ||||
| 		log.Error().Err(err).Msg("Could not enable Watchdog") | ||||
| 		log.WithoutContext().Errorf("Could not enable Watchdog: %v", err) | ||||
| 	} else if t != 0 { | ||||
| 		// Send a ping each half time given | ||||
| 		t /= 2 | ||||
| 		log.Info().Msgf("Watchdog activated with timer duration %s", t) | ||||
| 		log.WithoutContext().Infof("Watchdog activated with timer duration %s", t) | ||||
| 		safe.Go(func() { | ||||
| 			tick := time.Tick(t) | ||||
| 			for range tick { | ||||
| 				resp, errHealthCheck := healthcheck.Do(*staticConfiguration) | ||||
| 				if resp != nil { | ||||
| 					_ = resp.Body.Close() | ||||
| 					resp.Body.Close() | ||||
| 				} | ||||
|  | ||||
| 				if staticConfiguration.Ping == nil || errHealthCheck == nil { | ||||
| 					if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok { | ||||
| 						log.Error().Msg("Fail to tick watchdog") | ||||
| 						log.WithoutContext().Error("Fail to tick watchdog") | ||||
| 					} | ||||
| 				} else { | ||||
| 					log.Error().Err(errHealthCheck).Send() | ||||
| 					log.WithoutContext().Error(errHealthCheck) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	svr.Wait() | ||||
| 	log.Info().Msg("Shutting down") | ||||
| 	log.WithoutContext().Info("Shutting down") | ||||
| 	logrus.Exit(0) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func setupServer(staticConfiguration *static.Configuration) (*server.Server, error) { | ||||
| 	providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers) | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	routinesPool := safe.NewPool(ctx) | ||||
|  | ||||
| 	// adds internal provider | ||||
| 	err := providerAggregator.AddProvider(traefik.New(*staticConfiguration)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// ACME | ||||
|  | ||||
| 	tlsManager := traefiktls.NewManager(staticConfiguration.OCSP) | ||||
| 	routinesPool.GoCtx(tlsManager.Run) | ||||
|  | ||||
| 	httpChallengeProvider := acme.NewChallengeHTTP() | ||||
|  | ||||
| 	tlsChallengeProvider := acme.NewChallengeTLSALPN() | ||||
| 	err = providerAggregator.AddProvider(tlsChallengeProvider) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	acmeProviders := initACMEProvider(staticConfiguration, providerAggregator, tlsManager, httpChallengeProvider, tlsChallengeProvider, routinesPool) | ||||
|  | ||||
| 	// Tailscale | ||||
|  | ||||
| 	tsProviders := initTailscaleProviders(staticConfiguration, providerAggregator) | ||||
|  | ||||
| 	// Observability | ||||
|  | ||||
| 	metricRegistries := registerMetricClients(staticConfiguration.Metrics) | ||||
| 	var semConvMetricRegistry *metrics.SemConvMetricsRegistry | ||||
| 	if staticConfiguration.Metrics != nil && staticConfiguration.Metrics.OTLP != nil { | ||||
| 		semConvMetricRegistry, err = metrics.NewSemConvMetricRegistry(ctx, staticConfiguration.Metrics.OTLP) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("unable to create SemConv metric registry: %w", err) | ||||
| 		} | ||||
| 	} | ||||
| 	metricsRegistry := metrics.NewMultiRegistry(metricRegistries) | ||||
| 	accessLog := setupAccessLog(ctx, staticConfiguration.AccessLog) | ||||
| 	tracer, tracerCloser := setupTracing(ctx, staticConfiguration.Tracing) | ||||
| 	observabilityMgr := middleware.NewObservabilityMgr(*staticConfiguration, metricsRegistry, semConvMetricRegistry, accessLog, tracer, tracerCloser) | ||||
|  | ||||
| 	// Entrypoints | ||||
|  | ||||
| 	serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints, staticConfiguration.HostResolver, metricsRegistry) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	serverEntryPointsUDP, err := server.NewUDPEntryPoints(staticConfiguration.EntryPoints) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if staticConfiguration.API != nil { | ||||
| 		version.DisableDashboardAd = staticConfiguration.API.DisableDashboardAd | ||||
| 	} | ||||
|  | ||||
| 	// Plugins | ||||
| 	pluginLogger := log.Ctx(ctx).With().Logger() | ||||
| 	hasPlugins := staticConfiguration.Experimental != nil && (staticConfiguration.Experimental.Plugins != nil || staticConfiguration.Experimental.LocalPlugins != nil) | ||||
| 	if hasPlugins { | ||||
| 		pluginsList := slices.Collect(maps.Keys(staticConfiguration.Experimental.Plugins)) | ||||
| 		pluginsList = append(pluginsList, slices.Collect(maps.Keys(staticConfiguration.Experimental.LocalPlugins))...) | ||||
|  | ||||
| 		pluginLogger = pluginLogger.With().Strs("plugins", pluginsList).Logger() | ||||
| 		pluginLogger.Info().Msg("Loading plugins...") | ||||
| 	} | ||||
|  | ||||
| 	pluginBuilder, err := createPluginBuilder(staticConfiguration) | ||||
| 	if err != nil && staticConfiguration.Experimental != nil && staticConfiguration.Experimental.AbortOnPluginFailure { | ||||
| 		return nil, fmt.Errorf("plugin: failed to create plugin builder: %w", err) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		pluginLogger.Err(err).Msg("Plugins are disabled because an error has occurred.") | ||||
| 	} else if hasPlugins { | ||||
| 		pluginLogger.Info().Msg("Plugins loaded.") | ||||
| 	} | ||||
|  | ||||
| 	// Providers plugins | ||||
|  | ||||
| 	for name, conf := range staticConfiguration.Providers.Plugin { | ||||
| 		if pluginBuilder == nil { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		p, err := pluginBuilder.BuildProvider(name, conf) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("plugin: failed to build provider: %w", err) | ||||
| 		} | ||||
|  | ||||
| 		err = providerAggregator.AddProvider(p) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("plugin: failed to add provider: %w", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Service manager factory | ||||
|  | ||||
| 	var spiffeX509Source *workloadapi.X509Source | ||||
| 	if staticConfiguration.Spiffe != nil && staticConfiguration.Spiffe.WorkloadAPIAddr != "" { | ||||
| 		log.Info().Str("workloadAPIAddr", staticConfiguration.Spiffe.WorkloadAPIAddr). | ||||
| 			Msg("Waiting on SPIFFE SVID delivery") | ||||
|  | ||||
| 		spiffeX509Source, err = workloadapi.NewX509Source( | ||||
| 			ctx, | ||||
| 			workloadapi.WithClientOptions( | ||||
| 				workloadapi.WithAddr( | ||||
| 					staticConfiguration.Spiffe.WorkloadAPIAddr, | ||||
| 				), | ||||
| 			), | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("unable to create SPIFFE x509 source: %w", err) | ||||
| 		} | ||||
| 		log.Info().Msg("Successfully obtained SPIFFE SVID.") | ||||
| 	} | ||||
|  | ||||
| 	transportManager := service.NewTransportManager(spiffeX509Source) | ||||
|  | ||||
| 	var proxyBuilder service.ProxyBuilder = httputil.NewProxyBuilder(transportManager, semConvMetricRegistry) | ||||
| 	if staticConfiguration.Experimental != nil && staticConfiguration.Experimental.FastProxy != nil { | ||||
| 		proxyBuilder = proxy.NewSmartBuilder(transportManager, proxyBuilder, *staticConfiguration.Experimental.FastProxy) | ||||
| 	} | ||||
|  | ||||
| 	dialerManager := tcp.NewDialerManager(spiffeX509Source) | ||||
| 	acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider) | ||||
| 	managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, observabilityMgr, transportManager, proxyBuilder, acmeHTTPHandler) | ||||
|  | ||||
| 	// Router factory | ||||
|  | ||||
| 	routerFactory, err := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, observabilityMgr, pluginBuilder, dialerManager) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("creating router factory: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Watcher | ||||
|  | ||||
| 	watcher := server.NewConfigurationWatcher( | ||||
| 		routinesPool, | ||||
| 		providerAggregator, | ||||
| 		getDefaultsEntrypoints(staticConfiguration), | ||||
| 		"internal", | ||||
| 	) | ||||
|  | ||||
| 	// TLS | ||||
| 	watcher.AddListener(func(conf dynamic.Configuration) { | ||||
| 		ctx := context.Background() | ||||
| 		tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates) | ||||
|  | ||||
| 		gauge := metricsRegistry.TLSCertsNotAfterTimestampGauge() | ||||
| 		for _, certificate := range tlsManager.GetServerCertificates() { | ||||
| 			appendCertMetric(gauge, certificate) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	// Metrics | ||||
| 	watcher.AddListener(func(_ dynamic.Configuration) { | ||||
| 		metricsRegistry.ConfigReloadsCounter().Add(1) | ||||
| 		metricsRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix())) | ||||
| 	}) | ||||
|  | ||||
| 	// Server Transports | ||||
| 	watcher.AddListener(func(conf dynamic.Configuration) { | ||||
| 		transportManager.Update(conf.HTTP.ServersTransports) | ||||
| 		proxyBuilder.Update(conf.HTTP.ServersTransports) | ||||
| 		dialerManager.Update(conf.TCP.ServersTransports) | ||||
| 	}) | ||||
|  | ||||
| 	// Switch router | ||||
| 	watcher.AddListener(switchRouter(routerFactory, serverEntryPointsTCP, serverEntryPointsUDP)) | ||||
|  | ||||
| 	// Metrics | ||||
| 	if metricsRegistry.IsEpEnabled() || metricsRegistry.IsRouterEnabled() || metricsRegistry.IsSvcEnabled() { | ||||
| 		var eps []string | ||||
| 		for key := range serverEntryPointsTCP { | ||||
| 			eps = append(eps, key) | ||||
| 		} | ||||
| 		watcher.AddListener(func(conf dynamic.Configuration) { | ||||
| 			metrics.OnConfigurationUpdate(conf, eps) | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// TLS challenge | ||||
| 	watcher.AddListener(tlsChallengeProvider.ListenConfiguration) | ||||
|  | ||||
| 	// Certificate Resolvers | ||||
|  | ||||
| 	resolverNames := map[string]struct{}{} | ||||
|  | ||||
| 	// ACME | ||||
| 	for _, p := range acmeProviders { | ||||
| 		resolverNames[p.ResolverName] = struct{}{} | ||||
| 		watcher.AddListener(p.ListenConfiguration) | ||||
| 	} | ||||
|  | ||||
| 	// Tailscale | ||||
| 	for _, p := range tsProviders { | ||||
| 		resolverNames[p.ResolverName] = struct{}{} | ||||
| 		watcher.AddListener(p.HandleConfigUpdate) | ||||
| 	} | ||||
|  | ||||
| 	// Certificate resolver logs | ||||
| 	watcher.AddListener(func(config dynamic.Configuration) { | ||||
| 		for rtName, rt := range config.HTTP.Routers { | ||||
| 			if rt.TLS == nil || rt.TLS.CertResolver == "" { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			if _, ok := resolverNames[rt.TLS.CertResolver]; !ok { | ||||
| 				log.Error().Err(err).Str(logs.RouterName, rtName).Str("certificateResolver", rt.TLS.CertResolver). | ||||
| 					Msg("Router uses a nonexistent certificate resolver") | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, observabilityMgr), nil | ||||
| } | ||||
|  | ||||
| func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvider http.Handler) http.Handler { | ||||
| 	var acmeHTTPHandler http.Handler | ||||
| 	for _, p := range acmeProviders { | ||||
| 		if p != nil && p.HTTPChallenge != nil { | ||||
| 			acmeHTTPHandler = httpChallengeProvider | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return acmeHTTPHandler | ||||
| } | ||||
|  | ||||
| func getDefaultsEntrypoints(staticConfiguration *static.Configuration) []string { | ||||
| 	var defaultEntryPoints []string | ||||
|  | ||||
| 	// Determines if at least one EntryPoint is configured to be used by default. | ||||
| 	var hasDefinedDefaults bool | ||||
| 	for _, ep := range staticConfiguration.EntryPoints { | ||||
| 		if ep.AsDefault { | ||||
| 			hasDefinedDefaults = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for name, cfg := range staticConfiguration.EntryPoints { | ||||
| 		// By default all entrypoints are considered. | ||||
| 		// If at least one is flagged, then only flagged entrypoints are included. | ||||
| 		if hasDefinedDefaults && !cfg.AsDefault { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		protocol, err := cfg.GetProtocol() | ||||
| 		if err != nil { | ||||
| 			// Should never happen because Traefik should not start if protocol is invalid. | ||||
| 			log.Error().Err(err).Msg("Invalid protocol") | ||||
| 		} | ||||
|  | ||||
| 		if protocol != "udp" && name != static.DefaultInternalEntryPointName { | ||||
| 			defaultEntryPoints = append(defaultEntryPoints, name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	slices.Sort(defaultEntryPoints) | ||||
| 	return defaultEntryPoints | ||||
| } | ||||
|  | ||||
| func switchRouter(routerFactory *server.RouterFactory, serverEntryPointsTCP server.TCPEntryPoints, serverEntryPointsUDP server.UDPEntryPoints) func(conf dynamic.Configuration) { | ||||
| 	return func(conf dynamic.Configuration) { | ||||
| 		rtConf := runtime.NewConfig(conf) | ||||
|  | ||||
| 		routers, udpRouters := routerFactory.CreateRouters(rtConf) | ||||
|  | ||||
| 		serverEntryPointsTCP.Switch(routers) | ||||
| 		serverEntryPointsUDP.Switch(udpRouters) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // initACMEProvider creates and registers acme.Provider instances corresponding to the configured ACME certificate resolvers. | ||||
| func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager, httpChallengeProvider, tlsChallengeProvider challenge.Provider, routinesPool *safe.Pool) []*acme.Provider { | ||||
| // initACMEProvider creates an acme provider from the ACME part of globalConfiguration | ||||
| func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager) []*acme.Provider { | ||||
| 	challengeStore := acme.NewLocalChallengeStore() | ||||
| 	localStores := map[string]*acme.LocalStore{} | ||||
|  | ||||
| 	var resolvers []*acme.Provider | ||||
| 	for name, resolver := range c.CertificatesResolvers { | ||||
| 		if resolver.ACME == nil { | ||||
| 			continue | ||||
| 		if resolver.ACME != nil { | ||||
| 			if localStores[resolver.ACME.Storage] == nil { | ||||
| 				localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage) | ||||
| 			} | ||||
|  | ||||
| 			p := &acme.Provider{ | ||||
| 				Configuration:  resolver.ACME, | ||||
| 				Store:          localStores[resolver.ACME.Storage], | ||||
| 				ChallengeStore: challengeStore, | ||||
| 				ResolverName:   name, | ||||
| 			} | ||||
|  | ||||
| 			if err := providerAggregator.AddProvider(p); err != nil { | ||||
| 				log.WithoutContext().Errorf("Unable to add ACME provider to the providers list: %v", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			p.SetTLSManager(tlsManager) | ||||
| 			if p.TLSChallenge != nil { | ||||
| 				tlsManager.TLSAlpnGetter = p.GetTLSALPNCertificate | ||||
| 			} | ||||
| 			p.SetConfigListenerChan(make(chan dynamic.Configuration)) | ||||
| 			resolvers = append(resolvers, p) | ||||
| 		} | ||||
|  | ||||
| 		if localStores[resolver.ACME.Storage] == nil { | ||||
| 			localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage, routinesPool) | ||||
| 		} | ||||
|  | ||||
| 		p := &acme.Provider{ | ||||
| 			Configuration:         resolver.ACME, | ||||
| 			Store:                 localStores[resolver.ACME.Storage], | ||||
| 			ResolverName:          name, | ||||
| 			HTTPChallengeProvider: httpChallengeProvider, | ||||
| 			TLSChallengeProvider:  tlsChallengeProvider, | ||||
| 		} | ||||
|  | ||||
| 		if err := providerAggregator.AddProvider(p); err != nil { | ||||
| 			log.Error().Err(err).Str("resolver", name).Msg("The ACME resolve is skipped from the resolvers list") | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		p.SetTLSManager(tlsManager) | ||||
|  | ||||
| 		p.SetConfigListenerChan(make(chan dynamic.Configuration)) | ||||
|  | ||||
| 		resolvers = append(resolvers, p) | ||||
| 	} | ||||
|  | ||||
| 	return resolvers | ||||
| } | ||||
|  | ||||
| // initTailscaleProviders creates and registers tailscale.Provider instances corresponding to the configured Tailscale certificate resolvers. | ||||
| func initTailscaleProviders(cfg *static.Configuration, providerAggregator *aggregator.ProviderAggregator) []*tailscale.Provider { | ||||
| 	var providers []*tailscale.Provider | ||||
| 	for name, resolver := range cfg.CertificatesResolvers { | ||||
| 		if resolver.Tailscale == nil { | ||||
| 			continue | ||||
| 		} | ||||
| func configureLogging(staticConfiguration *static.Configuration) { | ||||
| 	// configure default log flags | ||||
| 	stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags) | ||||
|  | ||||
| 		tsProvider := &tailscale.Provider{ResolverName: name} | ||||
|  | ||||
| 		if err := providerAggregator.AddProvider(tsProvider); err != nil { | ||||
| 			log.Error().Err(err).Str(logs.ProviderName, name).Msg("Unable to create Tailscale provider") | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		providers = append(providers, tsProvider) | ||||
| 	// configure log level | ||||
| 	// an explicitly defined log level always has precedence. if none is | ||||
| 	// given and debug mode is disabled, the default is ERROR, and DEBUG | ||||
| 	// otherwise. | ||||
| 	levelStr := "error" | ||||
| 	if staticConfiguration.Log != nil && staticConfiguration.Log.Level != "" { | ||||
| 		levelStr = strings.ToLower(staticConfiguration.Log.Level) | ||||
| 	} | ||||
|  | ||||
| 	return providers | ||||
| } | ||||
|  | ||||
| func registerMetricClients(metricsConfig *otypes.Metrics) []metrics.Registry { | ||||
| 	if metricsConfig == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	var registries []metrics.Registry | ||||
|  | ||||
| 	if metricsConfig.Prometheus != nil { | ||||
| 		logger := log.With().Str(logs.MetricsProviderName, "prometheus").Logger() | ||||
|  | ||||
| 		prometheusRegister := metrics.RegisterPrometheus(logger.WithContext(context.Background()), metricsConfig.Prometheus) | ||||
| 		if prometheusRegister != nil { | ||||
| 			registries = append(registries, prometheusRegister) | ||||
| 			logger.Debug().Msg("Configured Prometheus metrics") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if metricsConfig.Datadog != nil { | ||||
| 		logger := log.With().Str(logs.MetricsProviderName, "datadog").Logger() | ||||
|  | ||||
| 		registries = append(registries, metrics.RegisterDatadog(logger.WithContext(context.Background()), metricsConfig.Datadog)) | ||||
| 		logger.Debug(). | ||||
| 			Str("address", metricsConfig.Datadog.Address). | ||||
| 			Str("pushInterval", metricsConfig.Datadog.PushInterval.String()). | ||||
| 			Msgf("Configured Datadog metrics") | ||||
| 	} | ||||
|  | ||||
| 	if metricsConfig.StatsD != nil { | ||||
| 		logger := log.With().Str(logs.MetricsProviderName, "statsd").Logger() | ||||
|  | ||||
| 		registries = append(registries, metrics.RegisterStatsd(logger.WithContext(context.Background()), metricsConfig.StatsD)) | ||||
| 		logger.Debug(). | ||||
| 			Str("address", metricsConfig.StatsD.Address). | ||||
| 			Str("pushInterval", metricsConfig.StatsD.PushInterval.String()). | ||||
| 			Msg("Configured StatsD metrics") | ||||
| 	} | ||||
|  | ||||
| 	if metricsConfig.InfluxDB2 != nil { | ||||
| 		logger := log.With().Str(logs.MetricsProviderName, "influxdb2").Logger() | ||||
|  | ||||
| 		influxDB2Register := metrics.RegisterInfluxDB2(logger.WithContext(context.Background()), metricsConfig.InfluxDB2) | ||||
| 		if influxDB2Register != nil { | ||||
| 			registries = append(registries, influxDB2Register) | ||||
| 			logger.Debug(). | ||||
| 				Str("address", metricsConfig.InfluxDB2.Address). | ||||
| 				Str("bucket", metricsConfig.InfluxDB2.Bucket). | ||||
| 				Str("organization", metricsConfig.InfluxDB2.Org). | ||||
| 				Str("pushInterval", metricsConfig.InfluxDB2.PushInterval.String()). | ||||
| 				Msg("Configured InfluxDB v2 metrics") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if metricsConfig.OTLP != nil { | ||||
| 		logger := log.With().Str(logs.MetricsProviderName, "openTelemetry").Logger() | ||||
|  | ||||
| 		openTelemetryRegistry := metrics.RegisterOpenTelemetry(logger.WithContext(context.Background()), metricsConfig.OTLP) | ||||
| 		if openTelemetryRegistry != nil { | ||||
| 			registries = append(registries, openTelemetryRegistry) | ||||
| 			logger.Debug(). | ||||
| 				Str("pushInterval", metricsConfig.OTLP.PushInterval.String()). | ||||
| 				Msg("Configured OpenTelemetry metrics") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return registries | ||||
| } | ||||
|  | ||||
| func appendCertMetric(gauge gokitmetrics.Gauge, certificate *x509.Certificate) { | ||||
| 	slices.Sort(certificate.DNSNames) | ||||
|  | ||||
| 	labels := []string{ | ||||
| 		"cn", certificate.Subject.CommonName, | ||||
| 		"serial", certificate.SerialNumber.String(), | ||||
| 		"sans", strings.Join(certificate.DNSNames, ","), | ||||
| 	} | ||||
|  | ||||
| 	notAfter := float64(certificate.NotAfter.Unix()) | ||||
|  | ||||
| 	gauge.With(labels...).Set(notAfter) | ||||
| } | ||||
|  | ||||
| func setupAccessLog(ctx context.Context, conf *otypes.AccessLog) *accesslog.Handler { | ||||
| 	if conf == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	accessLoggerMiddleware, err := accesslog.NewHandler(ctx, conf) | ||||
| 	level, err := logrus.ParseLevel(levelStr) | ||||
| 	if err != nil { | ||||
| 		log.Warn().Err(err).Msg("Unable to create access logger") | ||||
| 		return nil | ||||
| 		log.WithoutContext().Errorf("Error getting level: %v", err) | ||||
| 	} | ||||
| 	log.SetLevel(level) | ||||
|  | ||||
| 	var logFile string | ||||
| 	if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 { | ||||
| 		logFile = staticConfiguration.Log.FilePath | ||||
| 	} | ||||
|  | ||||
| 	return accessLoggerMiddleware | ||||
| } | ||||
|  | ||||
| func setupTracing(ctx context.Context, conf *static.Tracing) (*tracing.Tracer, io.Closer) { | ||||
| 	if conf == nil { | ||||
| 		return nil, nil | ||||
| 	// configure log format | ||||
| 	var formatter logrus.Formatter | ||||
| 	if staticConfiguration.Log != nil && staticConfiguration.Log.Format == "json" { | ||||
| 		formatter = &logrus.JSONFormatter{} | ||||
| 	} else { | ||||
| 		disableColors := len(logFile) > 0 | ||||
| 		formatter = &logrus.TextFormatter{DisableColors: disableColors, FullTimestamp: true, DisableSorting: true} | ||||
| 	} | ||||
| 	log.SetFormatter(formatter) | ||||
|  | ||||
| 	tracer, closer, err := tracing.NewTracing(ctx, conf) | ||||
| 	if err != nil { | ||||
| 		log.Warn().Err(err).Msg("Unable to create tracer") | ||||
| 		return nil, nil | ||||
| 	if len(logFile) > 0 { | ||||
| 		dir := filepath.Dir(logFile) | ||||
|  | ||||
| 		if err := os.MkdirAll(dir, 0755); err != nil { | ||||
| 			log.WithoutContext().Errorf("Failed to create log path %s: %s", dir, err) | ||||
| 		} | ||||
|  | ||||
| 		err = log.OpenFile(logFile) | ||||
| 		logrus.RegisterExitHandler(func() { | ||||
| 			if err := log.CloseFile(); err != nil { | ||||
| 				log.WithoutContext().Errorf("Error while closing log: %v", err) | ||||
| 			} | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			log.WithoutContext().Errorf("Error while opening log file %s: %v", logFile, err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return tracer, closer | ||||
| } | ||||
|  | ||||
| func checkNewVersion() { | ||||
| @@ -624,19 +285,19 @@ func checkNewVersion() { | ||||
| } | ||||
|  | ||||
| func stats(staticConfiguration *static.Configuration) { | ||||
| 	logger := log.With().Logger() | ||||
| 	logger := log.WithoutContext() | ||||
|  | ||||
| 	if staticConfiguration.Global.SendAnonymousUsage { | ||||
| 		logger.Info().Msg(`Stats collection is enabled.`) | ||||
| 		logger.Info().Msg(`Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.`) | ||||
| 		logger.Info().Msg(`Help us improve Traefik by leaving this feature on :)`) | ||||
| 		logger.Info().Msg(`More details on: https://doc.traefik.io/traefik/contributing/data-collection/`) | ||||
| 		logger.Info(`Stats collection is enabled.`) | ||||
| 		logger.Info(`Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.`) | ||||
| 		logger.Info(`Help us improve Traefik by leaving this feature on :)`) | ||||
| 		logger.Info(`More details on: https://docs.traefik.io/v2.0/contributing/data-collection/`) | ||||
| 		collect(staticConfiguration) | ||||
| 	} else { | ||||
| 		logger.Info().Msg(` | ||||
| 		logger.Info(` | ||||
| Stats collection is disabled. | ||||
| Help us improve Traefik by turning this feature on :) | ||||
| More details on: https://doc.traefik.io/traefik/contributing/data-collection/ | ||||
| More details on: https://docs.traefik.io/v2.0/contributing/data-collection/ | ||||
| `) | ||||
| 	} | ||||
| } | ||||
| @@ -646,7 +307,7 @@ func collect(staticConfiguration *static.Configuration) { | ||||
| 	safe.Go(func() { | ||||
| 		for time.Sleep(10 * time.Minute); ; <-ticker { | ||||
| 			if err := collector.Collect(staticConfiguration); err != nil { | ||||
| 				log.Debug().Err(err).Send() | ||||
| 				log.WithoutContext().Debug(err) | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
|   | ||||
| @@ -1,186 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"crypto/x509" | ||||
| 	"encoding/pem" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-kit/kit/metrics" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 	"github.com/traefik/traefik/v3/pkg/config/static" | ||||
| ) | ||||
|  | ||||
| // FooCert is a PEM-encoded TLS cert. | ||||
| // generated from src/crypto/tls: | ||||
| // go run generate_cert.go  --rsa-bits 1024 --host foo.org,foo.com  --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h | ||||
| const fooCert = `-----BEGIN CERTIFICATE----- | ||||
| MIICHzCCAYigAwIBAgIQXQFLeYRwc5X21t457t2xADANBgkqhkiG9w0BAQsFADAS | ||||
| MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw | ||||
| MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB | ||||
| iQKBgQDCjn67GSs/khuGC4GNN+tVo1S+/eSHwr/hWzhfMqO7nYiXkFzmxi+u14CU | ||||
| Pda6WOeps7T2/oQEFMxKKg7zYOqkLSbjbE0ZfosopaTvEsZm/AZHAAvoOrAsIJOn | ||||
| SEiwy8h0tLA4z1SNR6rmIVQWyqBZEPAhBTQM1z7tFp48FakCFwIDAQABo3QwcjAO | ||||
| BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw | ||||
| AwEB/zAdBgNVHQ4EFgQUDHG3ASzeUezElup9zbPpBn/vjogwGwYDVR0RBBQwEoIH | ||||
| Zm9vLm9yZ4IHZm9vLmNvbTANBgkqhkiG9w0BAQsFAAOBgQBT+VLMbB9u27tBX8Aw | ||||
| ZrGY3rbNdBGhXVTksrjiF+6ZtDpD3iI56GH9zLxnqvXkgn3u0+Ard5TqF/xmdwVw | ||||
| NY0V/aWYfcL2G2auBCQrPvM03ozRnVUwVfP23eUzX2ORNHCYhd2ObQx4krrhs7cJ | ||||
| SWxtKwFlstoXY3K2g9oRD9UxdQ== | ||||
| -----END CERTIFICATE-----` | ||||
|  | ||||
| // BarCert is a PEM-encoded TLS cert. | ||||
| // generated from src/crypto/tls: | ||||
| // go run generate_cert.go  --rsa-bits 1024 --host bar.org,bar.com  --ca --start-date "Jan 1 00:00:00 1970" --duration=10000h | ||||
| const barCert = `-----BEGIN CERTIFICATE----- | ||||
| MIICHTCCAYagAwIBAgIQcuIcNEXzBHPoxna5S6wG4jANBgkqhkiG9w0BAQsFADAS | ||||
| MRAwDgYDVQQKEwdBY21lIENvMB4XDTcwMDEwMTAwMDAwMFoXDTcxMDIyMTE2MDAw | ||||
| MFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC | ||||
| gYEAqtcrP+KA7D6NjyztGNIPMup9KiBMJ8QL+preog/YHR7SQLO3kGFhpS3WKMab | ||||
| SzMypC3ZX1PZjBP5ZzwaV3PFbuwlCkPlyxR2lOWmullgI7mjY0TBeYLDIclIzGRp | ||||
| mpSDDSpkW1ay2iJDSpXjlhmwZr84hrCU7BRTQJo91fdsRTsCAwEAAaN0MHIwDgYD | ||||
| VR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMB | ||||
| Af8wHQYDVR0OBBYEFK8jnzFQvBAgWtfzOyXY4VSkwrTXMBsGA1UdEQQUMBKCB2Jh | ||||
| ci5vcmeCB2Jhci5jb20wDQYJKoZIhvcNAQELBQADgYEAJz0ifAExisC/ZSRhWuHz | ||||
| 7qs1i6Nd4+YgEVR8dR71MChP+AMxucY1/ajVjb9xlLys3GPE90TWSdVppabEVjZY | ||||
| Oq11nPKc50ItTt8dMku6t0JHBmzoGdkN0V4zJCBqdQJxhop8JpYJ0S9CW0eT93h3 | ||||
| ipYQSsmIINGtMXJ8VkP/MlM= | ||||
| -----END CERTIFICATE-----` | ||||
|  | ||||
| type gaugeMock struct { | ||||
| 	metrics map[string]float64 | ||||
| 	labels  string | ||||
| } | ||||
|  | ||||
| func (g gaugeMock) With(labelValues ...string) metrics.Gauge { | ||||
| 	g.labels = strings.Join(labelValues, ",") | ||||
| 	return g | ||||
| } | ||||
|  | ||||
| func (g gaugeMock) Set(value float64) { | ||||
| 	g.metrics[g.labels] = value | ||||
| } | ||||
|  | ||||
| func (g gaugeMock) Add(delta float64) { | ||||
| 	panic("implement me") | ||||
| } | ||||
|  | ||||
| func TestAppendCertMetric(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc     string | ||||
| 		certs    []string | ||||
| 		expected map[string]float64 | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc:     "No certs", | ||||
| 			certs:    []string{}, | ||||
| 			expected: map[string]float64{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:  "One cert", | ||||
| 			certs: []string{fooCert}, | ||||
| 			expected: map[string]float64{ | ||||
| 				"cn,,serial,123624926713171615935660664614975025408,sans,foo.com,foo.org": 3.6e+09, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:  "Two certs", | ||||
| 			certs: []string{fooCert, barCert}, | ||||
| 			expected: map[string]float64{ | ||||
| 				"cn,,serial,123624926713171615935660664614975025408,sans,foo.com,foo.org": 3.6e+09, | ||||
| 				"cn,,serial,152706022658490889223053211416725817058,sans,bar.com,bar.org": 3.6e+07, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			t.Parallel() | ||||
|  | ||||
| 			gauge := &gaugeMock{ | ||||
| 				metrics: map[string]float64{}, | ||||
| 			} | ||||
|  | ||||
| 			for _, cert := range test.certs { | ||||
| 				block, _ := pem.Decode([]byte(cert)) | ||||
| 				parsedCert, err := x509.ParseCertificate(block.Bytes) | ||||
| 				require.NoError(t, err) | ||||
|  | ||||
| 				appendCertMetric(gauge, parsedCert) | ||||
| 			} | ||||
|  | ||||
| 			assert.Equal(t, test.expected, gauge.metrics) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGetDefaultsEntrypoints(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc        string | ||||
| 		entrypoints static.EntryPoints | ||||
| 		expected    []string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc: "Skips special names", | ||||
| 			entrypoints: map[string]*static.EntryPoint{ | ||||
| 				"web": { | ||||
| 					Address: ":80", | ||||
| 				}, | ||||
| 				"traefik": { | ||||
| 					Address: ":8080", | ||||
| 				}, | ||||
| 			}, | ||||
| 			expected: []string{"web"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "Two EntryPoints not attachable", | ||||
| 			entrypoints: map[string]*static.EntryPoint{ | ||||
| 				"web": { | ||||
| 					Address: ":80", | ||||
| 				}, | ||||
| 				"websecure": { | ||||
| 					Address: ":443", | ||||
| 				}, | ||||
| 			}, | ||||
| 			expected: []string{"web", "websecure"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "Two EntryPoints only one attachable", | ||||
| 			entrypoints: map[string]*static.EntryPoint{ | ||||
| 				"web": { | ||||
| 					Address: ":80", | ||||
| 				}, | ||||
| 				"websecure": { | ||||
| 					Address:   ":443", | ||||
| 					AsDefault: true, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expected: []string{"websecure"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "Two attachable EntryPoints", | ||||
| 			entrypoints: map[string]*static.EntryPoint{ | ||||
| 				"web": { | ||||
| 					Address:   ":80", | ||||
| 					AsDefault: true, | ||||
| 				}, | ||||
| 				"websecure": { | ||||
| 					Address:   ":443", | ||||
| 					AsDefault: true, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expected: []string{"web", "websecure"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			actual := getDefaultsEntrypoints(&static.Configuration{ | ||||
| 				EntryPoints: test.entrypoints, | ||||
| 			}) | ||||
|  | ||||
| 			assert.ElementsMatch(t, test.expected, actual) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -7,8 +7,8 @@ import ( | ||||
| 	"runtime" | ||||
| 	"text/template" | ||||
|  | ||||
| 	"github.com/traefik/paerser/cli" | ||||
| 	"github.com/traefik/traefik/v3/pkg/version" | ||||
| 	"github.com/containous/traefik/v2/pkg/cli" | ||||
| 	"github.com/containous/traefik/v2/pkg/version" | ||||
| ) | ||||
|  | ||||
| var versionTemplate = `Version:      {{.Version}} | ||||
| @@ -17,7 +17,7 @@ Go version:   {{.GoVersion}} | ||||
| Built:        {{.BuildTime}} | ||||
| OS/Arch:      {{.Os}}/{{.Arch}}` | ||||
|  | ||||
| // NewCmd builds a new Version command. | ||||
| // NewCmd builds a new Version command | ||||
| func NewCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:          "version", | ||||
| @@ -33,7 +33,7 @@ func NewCmd() *cli.Command { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetPrint write Printable version. | ||||
| // GetPrint write Printable version | ||||
| func GetPrint(wr io.Writer) error { | ||||
| 	tmpl, err := template.New("").Parse(versionTemplate) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| [Unit] | ||||
| Description=Traefik | ||||
| Documentation=https://doc.traefik.io/traefik/ | ||||
| Documentation=https://docs.traefik.io | ||||
| #After=network-online.target | ||||
| #AssertFileIsExecutable=/usr/bin/traefik | ||||
| #AssertPathExists=/etc/traefik/traefik.toml | ||||
|   | ||||
| @@ -4,7 +4,6 @@ | ||||
|     "MD009": false, | ||||
|     "MD013": false, | ||||
|     "MD024": false, | ||||
|     "MD025": false, | ||||
|     "MD026": false, | ||||
|     "MD033": false, | ||||
|     "MD034": false, | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
|  | ||||
| ####### | ||||
| # This Makefile contains all targets related to the documentation | ||||
| ####### | ||||
| @@ -11,55 +12,41 @@ TRAEFIK_DOCS_CHECK_IMAGE ?= $(TRAEFIK_DOCS_BUILD_IMAGE)-check | ||||
| SITE_DIR := $(CURDIR)/site | ||||
|  | ||||
| DOCKER_RUN_DOC_PORT := 8000 | ||||
| DOCKER_RUN_DOC_MOUNTS := -v $(CURDIR):/mkdocs | ||||
| DOCKER_RUN_DOC_MOUNTS := -v $(CURDIR):/mkdocs  | ||||
| DOCKER_RUN_DOC_OPTS := --rm $(DOCKER_RUN_DOC_MOUNTS) -p $(DOCKER_RUN_DOC_PORT):8000 | ||||
|  | ||||
| # Default: generates the documentation into $(SITE_DIR) | ||||
| .PHONY: docs | ||||
| docs: docs-clean docs-image docs-lint docs-build docs-verify | ||||
|  | ||||
| # Writer Mode: build and serve docs on http://localhost:8000 with livereload | ||||
| .PHONY: docs-serve | ||||
| docs-serve: docs-image | ||||
| 	docker run  $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) mkdocs serve | ||||
|  | ||||
| ## Pull image for doc building | ||||
| .PHONY: docs-pull-images | ||||
| docs-pull-images: | ||||
| 	grep --no-filename -E '^FROM' ./*.Dockerfile \ | ||||
| 		| awk '{print $$2}' \ | ||||
| 		| sort \ | ||||
| 		| uniq \ | ||||
| 		| xargs -P 6 -n 1 docker pull | ||||
|  | ||||
| # Utilities Targets for each step | ||||
| .PHONY: docs-image | ||||
| docs-image: | ||||
| 	docker build -t $(TRAEFIK_DOCS_BUILD_IMAGE) -f docs.Dockerfile ./ | ||||
|  | ||||
| .PHONY: docs-build | ||||
| docs-build: docs-image | ||||
| 	docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) sh -c "mkdocs build \ | ||||
| 		&& chown -R $(shell id -u):$(shell id -g) ./site" | ||||
|  | ||||
| .PHONY: docs-verify | ||||
| docs-verify: docs-build | ||||
| ifneq ("$(DOCS_VERIFY_SKIP)", "true") | ||||
| 	docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./ | ||||
| 	docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /verify.sh | ||||
| else | ||||
| 	echo "DOCS_VERIFY_SKIP is true: no verification done." | ||||
| endif | ||||
| 	@if [ "$(DOCS_VERIFY_SKIP)" != "true" ]; then \ | ||||
| 		docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./; \ | ||||
| 		docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /verify.sh; \ | ||||
| 	else \ | ||||
| 		echo "DOCS_VERIFY_SKIP is true: no verification done."; \ | ||||
| 	fi | ||||
|  | ||||
| .PHONY: docs-lint | ||||
| docs-lint: | ||||
| ifneq ("$(DOCS_LINT_SKIP)", "true") | ||||
| 	docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./ | ||||
| 	docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /lint.sh | ||||
| else | ||||
| 	echo "DOCS_LINT_SKIP is true: no linting done." | ||||
| endif | ||||
| 	@if [ "$(DOCS_LINT_SKIP)" != "true" ]; then \ | ||||
| 		docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./ && \ | ||||
| 		docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /lint.sh; \ | ||||
| 	else \ | ||||
| 		echo "DOCS_LINT_SKIP is true: no linting done."; \ | ||||
| 	fi | ||||
|  | ||||
| .PHONY: docs-clean | ||||
| docs-clean: | ||||
| 	rm -rf $(SITE_DIR) | ||||
|  | ||||
| .PHONY: all docs-verify docs docs-clean docs-build docs-lint | ||||
|   | ||||
| @@ -1,19 +1,14 @@ | ||||
| FROM alpine:3.22 | ||||
| FROM alpine:3.15 as alpine | ||||
|  | ||||
| RUN apk --no-cache --no-progress add \ | ||||
|     build-base \ | ||||
|     gcompat \ | ||||
|     libcurl \ | ||||
|     libxml2-dev \ | ||||
|     libxslt-dev \ | ||||
|     ruby \ | ||||
|     ruby-bigdecimal \ | ||||
|     ruby-dev \ | ||||
|     ruby-etc \ | ||||
|     ruby-ffi \ | ||||
|     zlib-dev | ||||
|  | ||||
| RUN gem install nokogiri --version 1.18.6 --no-document -- --use-system-libraries | ||||
| RUN gem install html-proofer --version 5.0.10 --no-document -- --use-system-libraries | ||||
|     ruby-json \ | ||||
|     ruby-nokogiri | ||||
| RUN gem install html-proofer --version 3.19.3 --no-document -- --use-system-libraries | ||||
|  | ||||
| # After Ruby, some NodeJS YAY! | ||||
| RUN apk --no-cache --no-progress add \ | ||||
| @@ -21,9 +16,12 @@ RUN apk --no-cache --no-progress add \ | ||||
|     nodejs \ | ||||
|     npm | ||||
|  | ||||
| # To handle 'not get uid/gid' | ||||
| RUN npm config set unsafe-perm true | ||||
|  | ||||
| RUN npm install --global \ | ||||
|     markdownlint@0.29.0 \ | ||||
|     markdownlint-cli@0.35.0 | ||||
|     markdownlint@0.17.2 \ | ||||
|     markdownlint-cli@0.19.0 | ||||
|  | ||||
| # Finally the shell tools we need for later | ||||
| # tini helps to terminate properly all the parallelized tasks when sending CTRL-C | ||||
|   | ||||
							
								
								
									
										1
									
								
								docs/content/CNAME
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| docs.traefik.io | ||||
| @@ -1,58 +0,0 @@ | ||||
| /* Traefik Hub Menu icon base styles */ | ||||
| .menu-icon { | ||||
|   height: 18px; | ||||
|   width: 18px; | ||||
|   vertical-align: middle; | ||||
|   margin-left: 6px; | ||||
|   transition: all 0.2s ease; | ||||
|   filter: drop-shadow(0 1px 1px rgba(0,0,0,0.1)); | ||||
|   display: inline; | ||||
|   white-space: nowrap; | ||||
| } | ||||
|  | ||||
| /* Ensure parent container keeps items inline */ | ||||
| .nav-link-with-icon { | ||||
|   white-space: nowrap !important; | ||||
|   display: inline-flex !important; | ||||
|   align-items: center !important; | ||||
| } | ||||
|  | ||||
| /* Hover effects */ | ||||
| .menu-icon:hover { | ||||
|   transform: scale(1.05); | ||||
|   opacity: 0.8; | ||||
| } | ||||
|  | ||||
| /* Tablet responsive */ | ||||
| @media (max-width: 1024px) { | ||||
|   .menu-icon { | ||||
|     height: 14px; | ||||
|     width: 14px; | ||||
|     margin-left: 4px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* Mobile responsive */ | ||||
| @media (max-width: 768px) { | ||||
|   .menu-icon { | ||||
|     height: 12px; | ||||
|     width: 12px; | ||||
|     margin-left: 3px; | ||||
|     vertical-align: middle; | ||||
|   } | ||||
|    | ||||
|   /* Keep mobile navigation items inline */ | ||||
|   .nav-link-with-icon { | ||||
|     display: inline-flex !important; | ||||
|     align-items: center !important; | ||||
|     width: auto !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* High DPI displays */ | ||||
| @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { | ||||
|   .menu-icon { | ||||
|     image-rendering: -webkit-optimize-contrast; | ||||
|     image-rendering: crisp-edges; | ||||
|   } | ||||
| } | ||||
| Before Width: | Height: | Size: 520 KiB | 
| Before Width: | Height: | Size: 610 KiB | 
| Before Width: | Height: | Size: 80 KiB | 
| Before Width: | Height: | Size: 878 KiB | 
| Before Width: | Height: | Size: 791 KiB | 
| Before Width: | Height: | Size: 603 KiB | 
| Before Width: | Height: | Size: 966 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/addprefix.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 42 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/authforward.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 92 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/basicauth.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 71 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/buffering.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 70 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/chain.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 73 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/circuitbreaker.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 64 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/compress.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 62 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/digestauth.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 70 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/errorpages.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 120 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/headers.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 67 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/inflightreq.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/ipwhitelist.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 58 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/middleware/overview.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 307 KiB | 
| Before Width: | Height: | Size: 15 KiB | 
| Before Width: | Height: | Size: 13 KiB | 
| Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 289 KiB | 
| Before Width: | Height: | Size: 170 KiB | 
| Before Width: | Height: | Size: 482 KiB | 
| Before Width: | Height: | Size: 731 KiB | 
| Before Width: | Height: | Size: 715 KiB | 
| Before Width: | Height: | Size: 733 KiB | 
| Before Width: | Height: | Size: 358 KiB | 
| Before Width: | Height: | Size: 1010 KiB After Width: | Height: | Size: 452 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/content/assets/img/traefik.icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
| Before Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 7.6 KiB | 
| Before Width: | Height: | Size: 20 KiB | 
| Before Width: | Height: | Size: 241 KiB After Width: | Height: | Size: 125 KiB | 
							
								
								
									
										96
									
								
								docs/content/assets/styles/atom-one-light.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,96 @@ | ||||
| /* | ||||
|  | ||||
| Atom One Light by Daniel Gamage | ||||
| Original One Light Syntax theme from https://github.com/atom/one-light-syntax | ||||
|  | ||||
| base:    #fafafa | ||||
| mono-1:  #383a42 | ||||
| mono-2:  #686b77 | ||||
| mono-3:  #a0a1a7 | ||||
| hue-1:   #0184bb | ||||
| hue-2:   #4078f2 | ||||
| hue-3:   #a626a4 | ||||
| hue-4:   #50a14f | ||||
| hue-5:   #e45649 | ||||
| hue-5-2: #c91243 | ||||
| hue-6:   #986801 | ||||
| hue-6-2: #c18401 | ||||
|  | ||||
| */ | ||||
|  | ||||
| .hljs { | ||||
|   display: block; | ||||
|   overflow-x: auto; | ||||
|   padding: 0.5em; | ||||
|   color: #383a42; | ||||
|   background: #fafafa; | ||||
| } | ||||
|  | ||||
| .hljs-comment, | ||||
| .hljs-quote { | ||||
|   color: #a0a1a7; | ||||
|   font-style: italic; | ||||
| } | ||||
|  | ||||
| .hljs-doctag, | ||||
| .hljs-keyword, | ||||
| .hljs-formula { | ||||
|   color: #a626a4; | ||||
| } | ||||
|  | ||||
| .hljs-section, | ||||
| .hljs-name, | ||||
| .hljs-selector-tag, | ||||
| .hljs-deletion, | ||||
| .hljs-subst { | ||||
|   color: #e45649; | ||||
| } | ||||
|  | ||||
| .hljs-literal { | ||||
|   color: #0184bb; | ||||
| } | ||||
|  | ||||
| .hljs-string, | ||||
| .hljs-regexp, | ||||
| .hljs-addition, | ||||
| .hljs-attribute, | ||||
| .hljs-meta-string { | ||||
|   color: #50a14f; | ||||
| } | ||||
|  | ||||
| .hljs-built_in, | ||||
| .hljs-class .hljs-title { | ||||
|   color: #c18401; | ||||
| } | ||||
|  | ||||
| .hljs-attr, | ||||
| .hljs-variable, | ||||
| .hljs-template-variable, | ||||
| .hljs-type, | ||||
| .hljs-selector-class, | ||||
| .hljs-selector-attr, | ||||
| .hljs-selector-pseudo, | ||||
| .hljs-number { | ||||
|   color: #986801; | ||||
| } | ||||
|  | ||||
| .hljs-symbol, | ||||
| .hljs-bullet, | ||||
| .hljs-link, | ||||
| .hljs-meta, | ||||
| .hljs-selector-id, | ||||
| .hljs-title { | ||||
|   color: #4078f2; | ||||
| } | ||||
|  | ||||
| .hljs-emphasis { | ||||
|   font-style: italic; | ||||
| } | ||||
|  | ||||
| .hljs-strong { | ||||
|   font-weight: bold; | ||||
| } | ||||
|  | ||||
| .hljs-link { | ||||
|   text-decoration: underline; | ||||
| } | ||||
							
								
								
									
										63
									
								
								docs/content/assets/styles/extra.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,63 @@ | ||||
| @import url('https://fonts.googleapis.com/css?family=Noto+Sans|Noto+Serif'); | ||||
|  | ||||
| .md-logo img { | ||||
|     background-color: white; | ||||
|     border-radius: 50%; | ||||
|     width: 30px; | ||||
|     height: 30px; | ||||
| } | ||||
|  | ||||
| /* Fix for Chrome */ | ||||
| .md-typeset__table td code { | ||||
|     word-break: unset; | ||||
| } | ||||
|  | ||||
| .md-typeset__table tr :nth-child(1) { | ||||
|     word-wrap: break-word; | ||||
|     max-width: 30em; | ||||
| } | ||||
|  | ||||
| body { | ||||
|     font-family: 'Noto Sans', sans-serif; | ||||
| } | ||||
|  | ||||
| h1 { | ||||
|     font-weight: bold !important; | ||||
|     color: rgba(0,0,0,.9) !important; | ||||
| } | ||||
|  | ||||
| h2 { | ||||
|     font-weight: bold !important; | ||||
| } | ||||
|  | ||||
| h3 { | ||||
|     font-weight: bold !important; | ||||
| } | ||||
|  | ||||
| .md-typeset h5 { | ||||
|     text-transform: none; | ||||
| } | ||||
|  | ||||
| figcaption { | ||||
|     text-align: center; | ||||
|     font-size: 0.8em; | ||||
|     font-style: italic; | ||||
|     color: #8D909F; | ||||
| } | ||||
|  | ||||
| p.subtitle { | ||||
|     color: rgba(0,0,0,.54); | ||||
|     padding-top: 0; | ||||
|     margin-top: -2em; | ||||
|     font-weight: bold; | ||||
|     font-size: 1.25em; | ||||
| } | ||||
|  | ||||
| .markdown-body .task-list-item { | ||||
|     list-style-type: none !important; | ||||
| } | ||||
|  | ||||
| .markdown-body .task-list-item input[type="checkbox"] { | ||||
|     margin: 0 4px 0.25em -20px; | ||||
|     vertical-align: middle; | ||||
| } | ||||