├── .github
├── renovate.json
└── workflows
│ ├── codeql-analysis.yml
│ ├── golangci-lint.yml
│ └── push.yml
├── .gitignore
├── .goreleaser.yaml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── cmd
└── syncflaer
│ └── main.go
├── configs
└── config.yml
├── deployments
└── kubernetes
│ ├── configmap.yaml
│ ├── cronjob.yaml
│ ├── namespace.yaml
│ ├── prometheusrule.yaml
│ ├── rbac.yaml
│ └── secret.yaml
├── go.mod
├── go.sum
└── internal
├── additionalRecords.go
├── cloudflare.go
├── config.go
├── flags.go
├── ip.go
├── kube
├── client.go
└── ingresses.go
├── slack.go
└── traefik.go
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:recommended"
4 | ],
5 | "labels": [
6 | "dependencies"
7 | ],
8 | "postUpdateOptions": [
9 | "gomodTidy"
10 | ],
11 | "packageRules": [
12 | {
13 | "matchPackageNames": [
14 | "k8s.io/client-go"
15 | ],
16 | "allowedVersions": "< 1.0.0"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '22 20 * * 5'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | language: [ 'go' ]
32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33 | # Learn more:
34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
35 |
36 | steps:
37 | - name: Checkout repository
38 | uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
39 |
40 | # Initializes the CodeQL tools for scanning.
41 | - name: Initialize CodeQL
42 | uses: github/codeql-action/init@v3
43 | with:
44 | languages: ${{ matrix.language }}
45 | # If you wish to specify custom queries, you can do so here or in a config file.
46 | # By default, queries listed here will override any specified in a config file.
47 | # Prefix the list here with "+" to use these queries and those in the config file.
48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
49 |
50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
51 | # If this step fails, then you should remove it and run the build manually (see below)
52 | - name: Autobuild
53 | uses: github/codeql-action/autobuild@v3
54 |
55 | # ℹ️ Command-line programs to run using the OS shell.
56 | # 📚 https://git.io/JvXDl
57 |
58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
59 | # and modify them (or add more) to build your code if your project
60 | # uses a compiled language
61 |
62 | #- run: |
63 | # make bootstrap
64 | # make release
65 |
66 | - name: Perform CodeQL Analysis
67 | uses: github/codeql-action/analyze@v3
68 |
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: golangci-lint
3 | on:
4 | push:
5 | tags:
6 | - v*
7 | branches:
8 | - master
9 | pull_request:
10 | permissions:
11 | contents: read
12 | jobs:
13 | golangci:
14 | name: lint
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
18 | - name: Determine Go version from go.mod
19 | run: echo "GO_VERSION=$(grep "go 1." go.mod | cut -d " " -f 2)" >> $GITHUB_ENV
20 | - name: Set up Go
21 | uses: actions/setup-go@v5
22 | with:
23 | go-version: ${{ env.GO_VERSION }}
24 | - name: golangci-lint
25 | uses: golangci/golangci-lint-action@v3
26 | with:
27 | args: --issues-exit-code=0 --timeout=3m ./...
28 |
--------------------------------------------------------------------------------
/.github/workflows/push.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: build
3 |
4 | on:
5 | push:
6 | tags:
7 | - 'v*'
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
15 | with:
16 | fetch-depth: 0
17 | - name: Determine Go version from go.mod
18 | run: echo "GO_VERSION=$(grep "go 1." go.mod | cut -d " " -f 2)" >> $GITHUB_ENV
19 | - name: Set up Go
20 | uses: actions/setup-go@v5
21 | with:
22 | go-version: ${{ env.GO_VERSION }}
23 | - name: Set up QEMU
24 | uses: docker/setup-qemu-action@v3
25 | - name: Set up Docker Buildx
26 | uses: docker/setup-buildx-action@v3
27 | - name: Cache Go modules
28 | uses: actions/cache@v4
29 | with:
30 | path: ~/go/pkg/mod
31 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
32 | restore-keys: |
33 | ${{ runner.os }}-go-
34 | - name: Login to DockerHub
35 | uses: docker/login-action@v3
36 | with:
37 | username: ${{ secrets.DOCKERHUB_USERNAME }}
38 | password: ${{ secrets.DOCKERHUB_TOKEN }}
39 | - name: Log in to GitHub Container registry
40 | uses: docker/login-action@v3
41 | with:
42 | registry: ghcr.io
43 | username: ${{ github.actor }}
44 | password: ${{ secrets.GITHUB_TOKEN }}
45 | - name: Run GoReleaser
46 | uses: goreleaser/goreleaser-action@v4
47 | if: success() && startsWith(github.ref, 'refs/tags/')
48 | with:
49 | version: latest
50 | args: release --rm-dist
51 | env:
52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53 | TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vscode/
3 | config.yml
4 | !configs/config.yml
5 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | project_name: SyncFlaer
3 | before:
4 | hooks:
5 | - go mod tidy
6 | builds:
7 | - main: ./cmd/syncflaer
8 | env:
9 | - CGO_ENABLED=0
10 | goos:
11 | - linux
12 | - darwin
13 | - windows
14 | goarch:
15 | - amd64
16 | - arm64
17 | - arm
18 | dockers:
19 | - image_templates:
20 | - ghcr.io/containeroo/syncflaer:{{ .Tag }}-amd64
21 | - containeroo/syncflaer:{{ .Tag }}-amd64
22 | use: buildx
23 | dockerfile: Dockerfile
24 | extra_files:
25 | - go.mod
26 | - go.sum
27 | - cmd
28 | - internal
29 | build_flag_templates:
30 | - --pull
31 | - --platform=linux/amd64
32 | - --label=org.opencontainers.image.title={{ .ProjectName }}
33 | - --label=org.opencontainers.image.description={{ .ProjectName }}
34 | - --label=org.opencontainers.image.url=https://github.com/containeroo/syncflaer
35 | - --label=org.opencontainers.image.source=https://github.com/containeroo/syncflaer
36 | - --label=org.opencontainers.image.version={{ .Version }}
37 | - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
38 | - --label=org.opencontainers.image.revision={{ .FullCommit }}
39 | - --label=org.opencontainers.image.licenses="GNU General Public License v3.0"
40 | - image_templates:
41 | - ghcr.io/containeroo/syncflaer:{{ .Tag }}-arm64
42 | - containeroo/syncflaer:{{ .Tag }}-arm64
43 | use: buildx
44 | dockerfile: Dockerfile
45 | extra_files:
46 | - go.mod
47 | - go.sum
48 | - cmd
49 | - internal
50 | goarch: arm64
51 | build_flag_templates:
52 | - --pull
53 | - --platform=linux/arm64
54 | - --label=org.opencontainers.image.title={{ .ProjectName }}
55 | - --label=org.opencontainers.image.description={{ .ProjectName }}
56 | - --label=org.opencontainers.image.url=https://github.com/containeroo/syncflaer
57 | - --label=org.opencontainers.image.source=https://github.com/containeroo/syncflaer
58 | - --label=org.opencontainers.image.version={{ .Version }}
59 | - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
60 | - --label=org.opencontainers.image.revision={{ .FullCommit }}
61 | - --label=org.opencontainers.image.licenses="GNU General Public License v3.0"
62 | docker_manifests:
63 | - name_template: containeroo/syncflaer:{{ .Tag }}
64 | image_templates:
65 | - containeroo/syncflaer:{{ .Tag }}-amd64
66 | - containeroo/syncflaer:{{ .Tag }}-arm64
67 | - name_template: ghcr.io/containeroo/syncflaer:{{ .Tag }}
68 | image_templates:
69 | - ghcr.io/containeroo/syncflaer:{{ .Tag }}-amd64
70 | - ghcr.io/containeroo/syncflaer:{{ .Tag }}-arm64
71 | - name_template: containeroo/syncflaer:latest
72 | image_templates:
73 | - containeroo/syncflaer:{{ .Tag }}-amd64
74 | - containeroo/syncflaer:{{ .Tag }}-arm64
75 | - name_template: ghcr.io/containeroo/syncflaer:latest
76 | image_templates:
77 | - ghcr.io/containeroo/syncflaer:{{ .Tag }}-amd64
78 | - ghcr.io/containeroo/syncflaer:{{ .Tag }}-arm64
79 | brews:
80 | - name: syncflaer
81 | tap:
82 | owner: containeroo
83 | name: homebrew-tap
84 | token: "{{ .Env.TAP_GITHUB_TOKEN }}"
85 | folder: Formula
86 | homepage: https://containeroo.ch
87 | description: Synchronize Traefik host rules and/or Kubernetes Ingresses with Cloudflare
88 | license: GNU General Public License v3.0
89 | test: |
90 | system "#{bin}/syncflaer --version"
91 | dependencies:
92 | - name: go
93 | type: optional
94 | install: |-
95 | bin.install "syncflaer"
96 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [v5.5.3](https://github.com/containeroo/SyncFlaer/tree/v5.5.3) (2023-01-02)
4 |
5 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.5.2...v5.5.3)
6 |
7 | **Bug fixes:**
8 |
9 | - Improve log message for duplicated DNS records
10 | - Fix version output
11 |
12 | ## [v5.5.2](https://github.com/containeroo/SyncFlaer/tree/v5.5.2) (2023-01-01)
13 |
14 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.5.1...v5.5.2)
15 |
16 | **Bug fixes:**
17 |
18 | - Remove trailing dot from `additionalRecords` (#158)
19 |
20 | **Dependencies:**
21 |
22 | - Bump several dependencies
23 |
24 | ## [v5.5.1](https://github.com/containeroo/SyncFlaer/tree/v5.5.1) (2022-10-31)
25 |
26 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.5.0...v5.5.1)
27 |
28 | **Bug fixes:**
29 |
30 | - GitHub API errors are now debug messages (#141)
31 | - Remove `myip.is` from default ip providers list since it now returns a 404 (#142)
32 |
33 | **Dependencies:**
34 |
35 | - Bump several dependencies
36 |
37 | ## [v5.5.0](https://github.com/containeroo/SyncFlaer/tree/v5.5.0) (2022-05-02)
38 |
39 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.4.2...v5.5.0)
40 |
41 | **New features:**
42 |
43 | - Added update checker. Can be disabled by setting `skipUpdateCheck` to `true`.
44 |
45 | **Dependencies:**
46 |
47 | - Bump several dependencies
48 |
49 | ## [v5.4.2](https://github.com/containeroo/SyncFlaer/tree/v5.4.2) (2022-03-22)
50 |
51 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.4.1...v5.4.2)
52 |
53 | **Dependencies:**
54 |
55 | - Bump several dependencies
56 |
57 | ## [v5.4.1](https://github.com/containeroo/SyncFlaer/tree/v5.4.1) (2022-02-15)
58 |
59 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.4.0...v5.4.1)
60 |
61 | **Bug fixes:**
62 |
63 | - Improve log messages
64 |
65 | ## [v5.4.0](https://github.com/containeroo/SyncFlaer/tree/v5.4.0) (2022-02-15)
66 |
67 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.3.0...v5.4.0)
68 |
69 | **New features:**
70 |
71 | - Add support for overriding defaults in Traefik rules (#102)
72 |
73 | **Bug fixes:**
74 |
75 | - Improve Traefik rule parsing (#103)
76 |
77 | ## [v5.3.0](https://github.com/containeroo/SyncFlaer/tree/v5.3.0) (2022-01-21)
78 |
79 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.2.0...v5.3.0)
80 |
81 | **New features:**
82 |
83 | - Add support for Kubernetes ingress objects (#80)
84 |
85 | ## [v5.2.0](https://github.com/containeroo/SyncFlaer/tree/v5.2.0) (2021-12-24)
86 |
87 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.1.6...v5.2.0)
88 |
89 | **New features:**
90 |
91 | - add ability to disable management of the root record (#89)
92 |
93 | **New default settings:**
94 |
95 | - the `proxied` field in additionalRecords and Cloudflare defaults is no longer required and defaults to `true` if omitted
96 |
97 | ## [v5.1.6](https://github.com/containeroo/SyncFlaer/tree/v5.1.6) (2021-12-14)
98 |
99 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.1.5...v5.1.6)
100 |
101 | **Bug fixes:**
102 |
103 | - fix substring matching when two domains are similar (#86)
104 |
105 | ## [v5.1.5](https://github.com/containeroo/SyncFlaer/tree/v5.1.5) (2021-11-08)
106 |
107 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.1.4...v5.1.5)
108 |
109 | **Bug fixes:**
110 |
111 | - fix an issue that caused SyncFlaer to falsely update a DNS record if it is defined twice (#79)
112 |
113 | ## [v5.1.4](https://github.com/containeroo/SyncFlaer/tree/v5.1.4) (2021-10-29)
114 |
115 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.1.3...v5.1.4)
116 |
117 | **Bug fixes:**
118 |
119 | - sync version output with actual release version
120 |
121 | ## [v5.1.3](https://github.com/containeroo/SyncFlaer/tree/v5.1.3) (2021-10-25)
122 |
123 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.1.2...v5.1.3)
124 |
125 | **Bug fixes:**
126 |
127 | - fix an issue that caused an error if a Traefik route was equal to the root domain (e.g. `example.com`)
128 |
129 | **Dependencies:**
130 |
131 | - Update module github.com/slack-go/slack to v0.9.5 (#74)
132 | - Update module github.com/cloudflare/cloudflare-go to v0.26.0 (#76)
133 |
134 | ## [v5.1.2](https://github.com/containeroo/SyncFlaer/tree/v5.1.2) (2021-09-06)
135 |
136 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.1.1...v5.1.2)
137 |
138 | This release bumps go to v1.17
139 |
140 | **Dependencies:**
141 |
142 | - Update module github.com/slack-go/slack to v0.9.4 (#67)
143 | - Update module github.com/cloudflare/cloudflare-go to v0.21.0 (#71)
144 |
145 | ## [v5.1.1](https://github.com/containeroo/SyncFlaer/tree/v5.1.1) (2021-07-23)
146 |
147 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.1.0...v5.1.1)
148 |
149 | **New features:**
150 |
151 | - Docker images for SyncFlaer are now also available on ghcr
152 |
153 | **Dependencies:**
154 |
155 | - Update module github.com/slack-go/slack to v0.9.2 (#62)
156 | - Update module github.com/cloudflare/cloudflare-go to v0.19.0 (#63)
157 | - Update module github.com/slack-go/slack to v0.9.3 (#64)
158 |
159 | ## [v5.1.0](https://github.com/containeroo/SyncFlaer/tree/v5.1.0) (2021-06-28)
160 |
161 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.0.2...v5.1.0)
162 |
163 | **Improvements:**
164 |
165 | - add `https://api.ipify.org` as a new additional default ip provider (#60)
166 |
167 | ## [v5.0.2](https://github.com/containeroo/SyncFlaer/tree/v5.0.2) (2021-06-21)
168 |
169 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.0.1...v5.0.2)
170 |
171 | **Improvements:**
172 |
173 | - mask environment variable values in debug log
174 |
175 | **Dependencies:**
176 |
177 | - Update module github.com/cloudflare/cloudflare-go to v0.18.0 (#59)
178 |
179 | ## [v5.0.1](https://github.com/containeroo/SyncFlaer/tree/v5.0.1) (2021-06-17)
180 |
181 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v5.0.0...v5.0.1)
182 |
183 | **Improvements:**
184 |
185 | - add debug logging for environment variable processing
186 |
187 | ## [v5.0.0](https://github.com/containeroo/SyncFlaer/tree/v5.0.0) (2021-06-17)
188 |
189 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v4.1.1...v5.0.0)
190 |
191 | **Caution!** The configuration has been changed since v4.1.1! Please refer to the readme file for more information.
192 |
193 | **Improvements:**
194 |
195 | - add ability to dynamically configure environment variables for Cloudflare API token, Slack token URL and Traefik HTTP basic auth password using the `env:` prefix in config file
196 |
197 | ## [v4.1.1](https://github.com/containeroo/SyncFlaer/tree/v4.1.1) (2021-06-13)
198 |
199 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v4.1.0...v4.1.1)
200 |
201 | **Improvements:**
202 |
203 | - add warning when debug mode is enabled
204 | - improve logging
205 |
206 | ## [v4.1.0](https://github.com/containeroo/SyncFlaer/tree/v4.1.0) (2021-06-11)
207 |
208 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v4.0.1...v4.1.0)
209 |
210 | **New features:**
211 |
212 | - add ability to set custom request headers on Traefik request
213 |
214 | ## [v4.0.1](https://github.com/containeroo/SyncFlaer/tree/v4.0.1) (2021-05-11)
215 |
216 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v4.0.0...v4.0.1)
217 |
218 | **Bug fixes:**
219 |
220 | - fix Traefik rule matching (#48)
221 |
222 | **Dependencies:**
223 |
224 | - Update module github.com/slack-go/slack to v0.9.0 (#45)
225 | - Update module github.com/slack-go/slack to v0.9.1 (#47)
226 |
227 | ## [v4.0.0](https://github.com/containeroo/SyncFlaer/tree/v4.0.0) (2021-04-17)
228 |
229 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v3.0.1...v4.0.0)
230 |
231 | **Caution!** The flags have changed since v3.0.1! Please refer to the readme file for more information.
232 |
233 | **New features:**
234 |
235 | - use POSIX/GNU-style `--flags`
236 |
237 | ## [v3.0.1](https://github.com/containeroo/SyncFlaer/tree/v3.0.1) (2021-04-12)
238 |
239 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v3.0.0...v3.0.1)
240 |
241 | **New features:**
242 |
243 | - darwin/arm64 builds are now available in GitHub releases
244 |
245 | **Dependencies:**
246 |
247 | - Update module github.com/cloudflare/cloudflare-go to v0.16.0 (#43)
248 |
249 | ## [v3.0.0](https://github.com/containeroo/SyncFlaer/tree/v3.0.0) (2021-03-24)
250 |
251 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v2.2.1...v3.0.0)
252 |
253 | **Caution!** The configuration has been changed since v2.2.1! You need to change your config file as described in the example.
254 |
255 | **New features:**
256 |
257 | - add support for multiple Cloudflare sites (#35)
258 |
259 | **Improvements:**
260 |
261 | - add `https://checkip.amazonaws.com` to default ip providers list
262 |
263 | **Dependencies:**
264 |
265 | - Update module github.com/slack-go/slack to v0.8.2 (#37)
266 |
267 | ## [v2.2.1](https://github.com/containeroo/SyncFlaer/tree/v2.2.1) (2021-03-16)
268 |
269 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v2.2.0...v2.2.1)
270 |
271 | **Improvements:**
272 |
273 | - improved log messages
274 |
275 | ## [v2.2.0](https://github.com/containeroo/SyncFlaer/tree/v2.2.0) (2021-03-15)
276 |
277 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v2.1.0...v2.2.0)
278 |
279 | **Deprecations:**
280 |
281 | - using `cloudflare.email` and `cloudflare.apiKey` is deprecated, use `cloudflare.apiToken` instead
282 |
283 | **Changed:**
284 |
285 | - remove ability to authenticate with Cloudflare using global API key
286 | - add support for Cloudflare API token
287 |
288 | **Dependencies:**
289 |
290 | - Update module github.com/sirupsen/logrus to v1.8.1 (#31)
291 |
292 | ## [v2.1.0](https://github.com/containeroo/SyncFlaer/tree/v2.1.0) (2021-03-08)
293 |
294 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v2.0.1...v2.1.0)
295 |
296 | **Changed:**
297 |
298 | - due tue compatibility reasons, the `proxied` field in `additionalRecords` and `cloudflare.defaults.proxied` is not optional anymore. please see the examples for more information.
299 |
300 | **Bug fixes:**
301 |
302 | - fixes an issue that prevented DNS records from being deleted (#28)
303 |
304 | **Dependencies:**
305 |
306 | - Update module github.com/cloudflare/cloudflare-go to v0.14.0 (#27)
307 |
308 | ## [v2.0.1](https://github.com/containeroo/SyncFlaer/tree/v2.0.1) (2021-02-24)
309 |
310 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v2.0.0...v2.0.1)
311 |
312 | **Improvements:**
313 |
314 | - improved log messages
315 |
316 | **Dependencies:**
317 |
318 | - Update module sirupsen/logrus to v1.8.0 (#24)
319 |
320 | ## [v2.0.0](https://github.com/containeroo/SyncFlaer/tree/v2.0.0) (2021-02-15)
321 |
322 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.1.2...v2.0.0)
323 |
324 | **Caution!** The configuration has been changed since v1.1.2! You need to change your config file as described in the example.
325 |
326 | **New features:**
327 |
328 | - add support for multiple Traefik instances (#20)
329 |
330 | **Deprecations:**
331 |
332 | - Removed support for deprecated config option `rootDomain`
333 |
334 | **Dependencies:**
335 |
336 | - Update module slack-go/slack to v0.8.1 (#23)
337 |
338 | ## [v1.1.2](https://github.com/containeroo/SyncFlaer/tree/v1.1.2) (2021-02-13)
339 |
340 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.1.1...v1.1.2)
341 |
342 | **Bug fixes:**
343 |
344 | - fix an issue where changing a `cloudflare.default` config resulted in an unexpected error
345 | - various bug fixes for delete grace feature
346 |
347 | ## [v1.1.1](https://github.com/containeroo/SyncFlaer/tree/v1.1.1) (2021-02-01)
348 |
349 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.1.0...v1.1.1)
350 |
351 | **Improvements:**
352 |
353 | - validate `cloudflare.defaults.type` config
354 | - print debug log if defaults are applied
355 |
356 | ## [v1.1.0](https://github.com/containeroo/SyncFlaer/tree/v1.1.0) (2021-01-31)
357 |
358 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.0.7...v1.1.0)
359 |
360 | **New features:**
361 |
362 | - add `cloudflare.deleteGrace` config to prevent DNS records from getting deleted too quickly
363 | - add `cloudflare.zoneName` config to replace `rootDomain`
364 | - windows/amd64 builds are now available in GitHub releases
365 |
366 | **Deprecations:**
367 |
368 | - `rootDomain` is deprecated and will be removed in a future release, use `cloudflare.zoneName` instead
369 |
370 | **Dependencies:**
371 |
372 | - Update module cloudflare/cloudflare-go to v0.13.8 (#17)
373 |
374 | ## [v1.0.7](https://github.com/containeroo/SyncFlaer/tree/v1.0.7) (2021-01-26)
375 |
376 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.0.6...v1.0.7)
377 |
378 | **Dependencies:**
379 |
380 | - Update module cloudflare/cloudflare-go to v0.13.7 (#13)
381 | - Update module slack-go/slack to v0.8.0 (#14)
382 |
383 | ## [v1.0.6](https://github.com/containeroo/SyncFlaer/tree/v1.0.6) (2021-01-21)
384 |
385 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.0.5...v1.0.6)
386 |
387 | **Improvements:**
388 |
389 | - Improve Slack messages
390 |
391 | ## [v1.0.5](https://github.com/containeroo/SyncFlaer/tree/v1.0.5) (2021-01-11)
392 |
393 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.0.4...v1.0.5)
394 |
395 | **Bug fixes:**
396 |
397 | - print new line when using `-version`
398 |
399 | ## [v1.0.4](https://github.com/containeroo/SyncFlaer/tree/v1.0.4) (2021-01-11)
400 |
401 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.0.3...v1.0.4)
402 |
403 | **New features:**
404 |
405 | - add `-version` flag to print current version
406 |
407 | ## [v1.0.3](https://github.com/containeroo/SyncFlaer/tree/v1.0.3) (2021-01-11)
408 |
409 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.0.2...v1.0.3)
410 |
411 | **Bug fixes:**
412 |
413 | - add Cloudflare logo to Slack message (#2)
414 |
415 | ## [v1.0.2](https://github.com/containeroo/SyncFlaer/tree/v1.0.2) (2021-01-06)
416 |
417 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.0.1...v1.0.2)
418 |
419 | **Bug fixes:**
420 |
421 | - add check for Traefik http status code (#5)
422 |
423 | ## [v1.0.1](https://github.com/containeroo/SyncFlaer/tree/v1.0.1) (2021-01-03)
424 |
425 | [All Commits](https://github.com/containeroo/SyncFlaer/compare/v1.0.0...v1.0.1)
426 |
427 | **Bug fixes:**
428 |
429 | - add ipv4 validation
430 | - improved logging
431 |
432 | ## [v1.0.0](https://github.com/containeroo/SyncFlaer/tree/v1.0.0) (2020-12-29)
433 |
434 | Initial release
435 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23-alpine as builder
2 |
3 | RUN mkdir -p /go/src/github.com/containeroo/syncflaer
4 | WORKDIR /go/src/github.com/containeroo/syncflaer
5 |
6 | RUN apk add --no-cache git
7 |
8 | ADD . /go/src/github.com/containeroo/syncflaer
9 | RUN CGO_ENABLED=0 GO111MODULE=on go build -a -installsuffix nocgo -o /syncflaer github.com/containeroo/syncflaer/cmd/syncflaer
10 |
11 |
12 | FROM scratch
13 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
14 | COPY --from=builder /syncflaer ./
15 | ENTRYPOINT ["./syncflaer"]
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | SyncFlaer: Synchronize Traefik host rules and/or Kubernetes Ingresses with Cloudflare®.
635 | Copyright (C) 2023 containeroo
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | SyncFlaer Copyright (C) 2023 containeroo
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SyncFlaer
2 |
3 | Synchronize Traefik host rules and/or Kubernetes Ingresses with Cloudflare®.
4 |
5 | ## Why?
6 |
7 | - Dynamically create, update or delete Cloudflare® DNS records based on Traefik http rules and/or Kubernetes Ingresses (apiVersion: networking.k8s.io/v1)
8 | - Supports multiple Traefik instances
9 | - Supports Kubernetes Ingresses (apiVersion: networking.k8s.io/v1)
10 | - Supports multiple Cloudflare zones
11 | - Update DNS records when public IP changes
12 | - Supports configuring additional DNS records for services outside Traefik (i.e. vpn server)
13 |
14 | ## Contents
15 |
16 | - [Usage](#usage)
17 | - [Simple](#simple)
18 | - [Kubernetes](#kubernetes)
19 | - [Configuration](#configuration)
20 | - [Overview](#overview)
21 | - [Config File](#config-file)
22 | - [Using Multiple Traefik Instances](#using-multiple-traefik-instances)
23 | - [Overriding Default Settings](#overriding-default-settings)
24 | - [Kubernetes Ingress Support](#kubernetes-ingress-support)
25 | - [Environment Variables](#environment-variables)
26 | - [Defaults](#defaults)
27 | - [Additional Records](#additional-records)
28 | - [Example A Record](#example-a-record)
29 | - [Example CNAME Record](#example-cname-record)
30 | - [Cloudflare API Token](#cloudflare-api-token)
31 | - [Upgrade Notes](#upgrade-notes)
32 | - [From 4.x to 5.x](#from-4x-to-5x)
33 | - [Copyright](#copyright)
34 | - [License](#license)
35 |
36 | ## Usage
37 |
38 | ### Simple
39 |
40 | Create a config file based on the example located at `configs/config.yml`.
41 |
42 | ```shell
43 | syncflaer --config-path /opt/syncflaer.yml
44 | ```
45 |
46 | Flags:
47 |
48 | ```text
49 | -c, --config-path string Path to config file (default "config.yml")
50 | -d, --debug Enable debug mode
51 | -v, --version Print the current version and exit
52 | ```
53 |
54 | ### Kubernetes
55 |
56 | You can run SyncFlaer as a Kubernetes CronJob. For an example deployment, please refer to the files located at `deployments/kubernetes`.
57 |
58 | ## Configuration
59 |
60 | ### Overview
61 |
62 | SyncFlaer must be configured via a [YAML config file](#config-file). Some secrets can be configured using [environment variables](#environment-variables).
63 |
64 | #### Config File
65 |
66 | The full configuration file can be found at `configs/config.yml`.
67 |
68 | #### Using Multiple Traefik Instances
69 |
70 | You can configure SyncFlaer to gather host rules from multiple Traefik instances.
71 | The configuration for two instances would look like this:
72 |
73 | ```yaml
74 | traefikInstances:
75 | - name: instance1
76 | url: https://traefik1.example.com
77 | user: admin1
78 | password: supersecure
79 | customRequestHeaders:
80 | X-Example-Header: instance1
81 | ignoredRules:
82 | - instance1.example.com
83 | - name: instance2
84 | url: https://traefik2.example.com
85 | user: admin2
86 | password: stillsupersecure
87 | customRequestHeaders:
88 | Authorization: env:TREAFIK_AUTH_HEADER
89 | ignoredRules:
90 | - instance2.example.com
91 | ```
92 |
93 | Every instance can be configured to use different HTTP basic auth, custom request headers and ignored rules.
94 |
95 | #### Overriding Default Settings
96 |
97 | Let's say you have the following Cloudflare defaults defined in config file:
98 |
99 | ```yaml
100 | cloudflare:
101 | defaults:
102 | type: CNAME
103 | ttl: 1
104 | proxied: true
105 | ```
106 |
107 | Usually, every DNS record created for a Traefik host rule will have those defaults.
108 |
109 | If you want to override those defaults, you can do so by specifying them in the `defaultOverrides` section of the config file under `traefikInstances`:
110 |
111 | ```yaml
112 | traefikInstances:
113 | - name: main
114 | url: https://traefik.example.com
115 | defaultOverrides:
116 | - rule: app.example.com
117 | type: A
118 | ttl: 60
119 | proxied: false
120 | ```
121 |
122 | This will override the defaults for the `app.example.com` rule.
123 |
124 | #### Kubernetes Ingress Support
125 |
126 | SyncFlaer can be configured to support Kubernetes Ingresses. By default, SyncFlaer will sync all Ingresses.
127 |
128 | If you run SyncFlaer in a Kubernetes cluster, please refer to the `deployments/kubernetes` folder for an example deployment.
129 | If you run SyncFlaer outside a Kubernetes cluster, you can use the `KUBECONFIG` environment variable to configure a specific kubeconfig file.
130 | If the `KUBECONFIG` environment variable is not set, SyncFlaer will use the default kubeconfig file located at `$HOME/.kube/config`.
131 |
132 | If you want to ignore specific Ingresses, use the annotation `syncflaer.containeroo.ch/ignore=true`.
133 |
134 | To overwrite the default configuration for DNS records, you can specify the following annotations for each Ingress:
135 |
136 | | Annotation | Example |
137 | |------------------------------------|----------------|
138 | | `syncflaer.containeroo.ch/type` | `A` or `CNAME` |
139 | | `syncflaer.containeroo.ch/content` | `example.com` |
140 | | `syncflaer.containeroo.ch/proxied` | `true` |
141 | | `syncflaer.containeroo.ch/ttl` | `120` |
142 |
143 | #### Environment Variables
144 |
145 | Instead of putting secrets in the config file, SyncFlaer can grab secrets from environment variables.
146 |
147 | You can define the names of the environment variables by using the `env:` prefix.
148 |
149 | | Configuration | Example |
150 | |----------------------------------------------|---------------------------|
151 | | `notifications.slack.webhookURL` | `env:SLACK_TOKEN` |
152 | | `password` in `traefikInstances` | `env:TRAEFIK_K8S_PW` |
153 | | `customRequestHeaders` in `traefikInstances` | `env:TRAEFIK_AUTH_HEADER` |
154 | | `cloudflare.apiToken` | `env:CF_API_TOKEN` |
155 |
156 | #### Defaults
157 |
158 | If not specified, the following defaults apply:
159 |
160 | | Name | Default Value |
161 | |--------------------------------|--------------------------------------------------------------------------------------------------------------------|
162 | | `skipUpdateCheck` | `false` |
163 | | `ipProviders` | `["https://ifconfig.me/ip", "https://ipecho.net/plain", "https://checkip.amazonaws.com", "https://api.ipify.org"]` |
164 | | `kubernetes.enabled` | `false` |
165 | | `managedRootRecord` | `true` |
166 | | `cloudflare.deleteGrace` | `0` (delete records instantly) |
167 | | `cloudflare.defaults.type` | `CNAME` |
168 | | `cloudflare.defaults.proxied` | `true` |
169 | | `cloudflare.defaults.ttl` | `1` |
170 | | `notifications.slack.username` | `SyncFlaer` |
171 | | `notifications.slack.iconURL` | `https://www.cloudflare.com/img/cf-facebook-card.png` |
172 |
173 | ### Additional Records
174 |
175 | You can specify additional DNS records which are not configured as Traefik hosts.
176 |
177 | #### Example A Record
178 |
179 | | Key | Example | Default Value | Required |
180 | |-----------|-----------------|-------------------------------|----------|
181 | | `name` | `a.example.com` | none | yes |
182 | | `type` | `A` | `cloudflare.defaults.type` | no |
183 | | `proxied` | `true` | `cloudflare.defaults.proxied` | no |
184 | | `ttl` | `1` | `cloudflare.defaults.ttl` | no |
185 | | `content` | `1.1.1.1` | `current public IP` | no |
186 |
187 | #### Example CNAME Record
188 |
189 | | Key | Example | Default Value | Required |
190 | |-----------|-------------------|-------------------------------|----------|
191 | | `name` | `vpn.example.com` | none | yes |
192 | | `type` | `CNAME` | `cloudflare.defaults.type` | no |
193 | | `proxied` | `false` | `cloudflare.defaults.proxied` | no |
194 | | `ttl` | `120` | `cloudflare.defaults.ttl` | no |
195 | | `content` | `mysite.com` | `cloudflare.zoneName` | no |
196 |
197 | ### Cloudflare API Token
198 |
199 | To create an API token visit https://dash.cloudflare.com/profile/api-tokens, click on `Create token` and select `Get started`.
200 |
201 | Select the following settings:
202 |
203 | **Permissions:**
204 | - `Zone` - `DNS` - `Edit`
205 |
206 | **Zone Resources:**
207 | - `Include` - `All Zones`
208 |
209 | ## Upgrade Notes
210 |
211 | ### From 4.x to 5.x
212 |
213 | The `cloudflare.apiToken` config is now required to be present in config file.
214 | If you want to use environment variables for Slack webhook URL, Traefik HTTP basic auth password and Cloudflare API token, you have to use the `env:` prefix.
215 | Everything after the `env:` part will be used as the name of the environment variable.
216 |
217 | ## Copyright
218 |
219 | 2023 containeroo
220 |
221 | Cloudflare and the Cloudflare logo are registered trademarks owned by Cloudflare Inc.
222 | This project is not affiliated with Cloudflare®.
223 |
224 | ## License
225 |
226 | [GNU GPLv3](https://github.com/containeroo/SyncFlaer/blob/master/LICENSE)
227 |
--------------------------------------------------------------------------------
/cmd/syncflaer/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "strconv"
8 |
9 | "github.com/cloudflare/cloudflare-go"
10 | "github.com/containeroo/syncflaer/internal/kube"
11 | "github.com/google/go-github/v50/github"
12 |
13 | internal "github.com/containeroo/syncflaer/internal"
14 |
15 | log "github.com/sirupsen/logrus"
16 | )
17 |
18 | const version string = "5.5.3"
19 |
20 | var latestVersion string
21 |
22 | func checkVersionUpdate() {
23 | githubClient := github.NewClient(nil)
24 | latestRelease, _, err := githubClient.Repositories.GetLatestRelease(context.Background(), "containeroo", "syncflaer")
25 | if err != nil {
26 | log.Debugf("Failed to get latest release: %s", err)
27 | return
28 | }
29 | if latestRelease.GetTagName() != fmt.Sprintf("v%s", version) {
30 | latestVersion = latestRelease.GetTagName()
31 | }
32 | }
33 |
34 | func main() {
35 | log.SetOutput(os.Stdout)
36 | log.SetFormatter(&log.TextFormatter{
37 | FullTimestamp: true,
38 | })
39 |
40 | configFilePath, printVersion, debug := internal.ParseFlags()
41 |
42 | if printVersion {
43 | fmt.Println(version)
44 | os.Exit(0)
45 | }
46 |
47 | if debug {
48 | log.SetLevel(log.DebugLevel)
49 | log.Warn("Debug mode enabled! Sensitive data could be displayed in plain text!")
50 | } else {
51 | log.SetLevel(log.InfoLevel)
52 | }
53 |
54 | log.Debugf("SyncFlaer %s", version)
55 |
56 | slackHandler := internal.NewSlackHandler()
57 |
58 | config := internal.GetConfig(configFilePath)
59 |
60 | if !*config.SkipUpdateCheck {
61 | go checkVersionUpdate()
62 | }
63 |
64 | cf := internal.SetupCloudflareClient(&config.Cloudflare.APIToken)
65 | zoneIDs := internal.CreateCloudflareZoneMap(&config.Cloudflare.ZoneNames, cf)
66 | currentIP := internal.GetCurrentIP(&config.IPProviders)
67 |
68 | for zoneName, zoneID := range zoneIDs {
69 | cloudflareDNSRecords := internal.GetCloudflareDNSRecords(cf, zoneID)
70 | deleteGraceRecords := internal.GetDeleteGraceRecords(cf, zoneID)
71 |
72 | var userRecords []cloudflare.DNSRecord
73 | if config.TraefikInstances != nil {
74 | userRecords = internal.GetTraefikRules(config, currentIP, zoneName, userRecords)
75 | }
76 | if config.AdditionalRecords != nil {
77 | userRecords = internal.GetAdditionalRecords(config, currentIP, zoneName, userRecords)
78 | }
79 | if *config.Kubernetes.Enabled {
80 | kubeClient := kube.SetupKubernetesClient()
81 | userRecords = kube.GetIngresses(kubeClient, config, currentIP, zoneName, userRecords)
82 | }
83 |
84 | missingRecords := internal.GetMissingDNSRecords(cloudflareDNSRecords, userRecords)
85 | if missingRecords != nil {
86 | for _, missingRecord := range missingRecords {
87 | internal.CreateCloudflareDNSRecord(cf, zoneID, missingRecord, slackHandler)
88 | }
89 | } else {
90 | log.Debug("No missing DNS records")
91 | }
92 |
93 | orphanedRecords := internal.GetOrphanedDNSRecords(cloudflareDNSRecords, userRecords)
94 | if orphanedRecords != nil {
95 | for _, orphanedRecord := range orphanedRecords {
96 | if config.Cloudflare.DeleteGrace == 0 {
97 | internal.DeleteCloudflareDNSRecord(cf, zoneID, orphanedRecord, slackHandler)
98 | continue
99 | }
100 |
101 | existingDeleteGraceRecord := internal.GetDeleteGraceRecord(cf, orphanedRecord.Name, deleteGraceRecords)
102 | if existingDeleteGraceRecord.Name == "" {
103 | falseVar := false
104 | deleteGraceRecord := cloudflare.DNSRecord{
105 | Type: "TXT",
106 | Name: fmt.Sprintf("%s.%s", cf.DeleteGraceRecordPrefix(), orphanedRecord.Name),
107 | Content: strconv.Itoa(config.Cloudflare.DeleteGrace),
108 | Proxied: &falseVar,
109 | }
110 | internal.CreateCloudflareDNSRecord(cf, zoneID, deleteGraceRecord, slackHandler)
111 | continue
112 | }
113 |
114 | deleteGraceRecordContent, _ := strconv.Atoi(existingDeleteGraceRecord.Content)
115 | if deleteGraceRecordContent > 1 {
116 | internal.UpdateDeleteGraceRecord(cf, zoneID, existingDeleteGraceRecord, orphanedRecord.Name)
117 | continue
118 | }
119 |
120 | internal.DeleteCloudflareDNSRecord(cf, zoneID, orphanedRecord, slackHandler)
121 | internal.DeleteCloudflareDNSRecord(cf, zoneID, existingDeleteGraceRecord, slackHandler)
122 | }
123 | } else {
124 | log.Debug("No orphaned DNS records")
125 | }
126 |
127 | internal.CleanupDeleteGraceRecords(cf, zoneID, userRecords, cloudflareDNSRecords, deleteGraceRecords, slackHandler)
128 |
129 | internal.UpdateCloudflareDNSRecords(cf, zoneID, cloudflareDNSRecords, userRecords, slackHandler)
130 | }
131 |
132 | slackHandler.SendSlackMessage(config)
133 |
134 | if latestVersion != "" {
135 | log.Infof("New version available: %s, download here: https://github.com/containeroo/syncflaer/releases/tag/%s", latestVersion, latestVersion)
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/configs/config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # enable or disable update check
3 | skipUpdateCheck: false
4 |
5 | # a list of services that return the public IP
6 | ipProviders:
7 | - https://ifconfig.me/ip
8 | - https://ipecho.net/plain
9 | - https://myip.is/ip
10 |
11 | # configure Slack notifications for SyncFlaer
12 | notifications:
13 | slack:
14 | # Slack webhook URL
15 | # you can set the value directly in config file
16 | webhookURL: https://hooks.slack.com/services/abc/def
17 | # or by using an environment variable by using the 'env:' prefix
18 | # webhookURL: env:SLACK_WEBHOOK_URL # in this case the contents of $SLACK_WEBHOOK_URL environment variable will be used as the value
19 | username: SyncFlaer
20 | channel: "#syncflaer"
21 | iconURL: https://url.to/image.png
22 |
23 | traefikInstances:
24 | # the name of the Traefik instance
25 | - name: main
26 | # base URL for Traefik dashboard and API (https://doc.traefik.io/traefik/operations/api/)
27 | url: https://traefik.example.com
28 | # HTTP basic auth credentials for Traefik
29 | username: admin
30 | # you can set the value directly in config file
31 | password: supersecure
32 | # or by using an environment variable using the 'env:' prefix
33 | # password: env:TRAEFIK_PW # in this case the contents of $TRAEFIK_PW environment variable will be used as the value
34 | # you can set http headers that will be added to the Traefik api request
35 | # requires string keys and string values
36 | customRequestHeaders:
37 | # headers can either be key value pairs in plain text
38 | X-Example-Header: Example-Value
39 | # or the value can be imported from environment variables using the 'env:' prefix
40 | Authorization: env:MY_AUTH_VAR # in this case the contents of $MY_AUTH_VAR environment variable will be used as the value
41 | # a list of rules which will be ignored
42 | # these rules are matched as a substring of the entire Traefik rule (i.e test.local.example.com would also match)
43 | ignoredRules:
44 | - local.example.com
45 | - dev.example.com
46 | # you can define overrides for the default settings for each Traefik rule
47 | # this can be useful, if you want to have some rules with different settings than the ones defined in cloudflare.defaults
48 | defaultOverrides:
49 | # host must match with the host of the Traefik rule
50 | - rule: my.example.com
51 | # specify the overrides (type, content, proxied, ttl)
52 | proxied: false
53 | ttl: 120
54 | - rule: other.example.com
55 | type: A
56 | content: 1.1.1.1
57 | proxied: false
58 | ttl: 300
59 | # you can add a second instance
60 | - name: secondary
61 | url: https://traefik-secondary.example.com
62 | username: admin
63 | password: stillsupersecure
64 | ignoredRules:
65 | - example.example.com
66 | - internal.example.com
67 |
68 | # enable kubernetes ingress integration
69 | kubernetes:
70 | enabled: true
71 |
72 | # set whether you want to have the root record automatically managed by SyncFlaer
73 | # if you don't need a root record, you can set this to false, the root record gets deleted automatically
74 | # you also have to set this to false, if your root record points to a different server
75 | # you can then use additionalRecords to configure your root record by yourself
76 | managedRootRecord: true
77 |
78 | # specify additional DNS records for services absent in Traefik (i.e. vpn server)
79 | additionalRecords:
80 | - name: vpn.example.com
81 | ttl: 120
82 | proxied: false
83 | - name: a.example.com
84 | proxied: true
85 | type: A
86 | content: 1.1.1.1
87 |
88 | cloudflare:
89 | # global Cloudflare API token
90 | # you can set the value directly in config file
91 | apiToken: abc
92 | # or by using an environment variable using the 'env:' prefix
93 | # apiToken: env:CF_API_TOKEN # in this case the contents of $CF_API_TOKEN environment variable will be used as the value
94 | # a list of Cloudflare zone names
95 | zoneNames:
96 | - example.com
97 | - othersite.com
98 | # define how many skips should happen until a DNS record gets deleted
99 | # every run of SyncFlaer counts as a skip
100 | deleteGrace: 5
101 | # define a set of defaults applied to all DNS records
102 | # they can be overridden by either specifying them in traefikInstances.defaultOverrides or by using Kubernetes Ingress annotations
103 | # check the README for more information
104 | defaults:
105 | type: CNAME
106 | proxied: true
107 | ttl: 1
108 |
--------------------------------------------------------------------------------
/deployments/kubernetes/configmap.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: syncflaer
6 | data:
7 | config.yml: |
8 | ---
9 | notifications:
10 | slack:
11 | webhookURL: env:SLACK_WEBHOOK_URL
12 | # traefikInstances:
13 | # - name: main
14 | # url: https://traefik.example.com
15 | # ignoredRules:
16 | # - local.example.com
17 | # - dev.example.com
18 |
19 | kubernetes:
20 | enabled: true
21 |
22 | additionalRecords:
23 | - name: vpn.example.com
24 | ttl: 120
25 | proxied: false
26 |
27 | cloudflare:
28 | apiToken: env:CF_API_TOKEN
29 | zoneNames:
30 | - example.com
31 | deleteGrace: 5
32 | defaults:
33 | type: CNAME
34 | proxied: true
35 | ttl: 1
36 |
--------------------------------------------------------------------------------
/deployments/kubernetes/cronjob.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: batch/v1beta1
3 | kind: CronJob
4 | metadata:
5 | name: syncflaer
6 | spec:
7 | suspend: false
8 | schedule: '*/3 * * * *'
9 | failedJobsHistoryLimit: 3
10 | successfulJobsHistoryLimit: 1
11 | concurrencyPolicy: Forbid
12 | jobTemplate:
13 | spec:
14 | template:
15 | metadata:
16 | labels:
17 | job: syncflaer
18 | spec:
19 | containers:
20 | - name: syncflaer
21 | image: ghcr.io/containeroo/syncflaer:latest
22 | args:
23 | - --debug
24 | envFrom:
25 | - secretRef:
26 | name: syncflaer
27 | volumeMounts:
28 | - mountPath: /config.yml
29 | name: config
30 | subPath: config.yml
31 | restartPolicy: Never
32 | serviceAccountName: syncflaer
33 | volumes:
34 | - name: config
35 | configMap:
36 | name: syncflaer
37 |
--------------------------------------------------------------------------------
/deployments/kubernetes/namespace.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Namespace
4 | metadata:
5 | name: syncflaer
6 |
--------------------------------------------------------------------------------
/deployments/kubernetes/prometheusrule.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: monitoring.coreos.com/v1
3 | kind: PrometheusRule
4 | metadata:
5 | name: syncflaer
6 | namespace: monitoring
7 | spec:
8 | groups:
9 | - name: syncflaer
10 | rules:
11 | - alert: SyncFlaerNotRunning
12 | annotations:
13 | description: The last execution of SyncFlaer failed.
14 | summary: SyncFlaer failed.
15 | expr: kube_job_spec_completions{job="kube-state-metrics",namespace="syncflaer"} - kube_job_status_succeeded{job="kube-state-metrics",namespace="syncflaer"} > 0
16 | for: 5m
17 | labels:
18 | severity: critical
19 |
--------------------------------------------------------------------------------
/deployments/kubernetes/rbac.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ServiceAccount
4 | metadata:
5 | name: syncflaer
6 | namespace: syncflaer
7 | ---
8 | apiVersion: rbac.authorization.k8s.io/v1
9 | kind: ClusterRole
10 | metadata:
11 | name: syncflaer
12 | rules:
13 | - apiGroups:
14 | - networking.k8s.io
15 | resources:
16 | - ingresses
17 | verbs:
18 | - get
19 | - list
20 | ---
21 | apiVersion: rbac.authorization.k8s.io/v1
22 | kind: syncflaer
23 | metadata:
24 | name: syncflaer
25 | roleRef:
26 | apiGroup: rbac.authorization.k8s.io
27 | kind: ClusterRole
28 | name: syncflaer
29 | subjects:
30 | - kind: ServiceAccount
31 | name: syncflaer
32 | namespace: syncflaer
33 |
--------------------------------------------------------------------------------
/deployments/kubernetes/secret.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Secret
4 | metadata:
5 | name: syncflaer
6 | type: Opaque
7 | stringData:
8 | SLACK_WEBHOOK_URL: https://hooks.slack.com/services/abc/def
9 | CF_API_TOKEN: abc
10 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/containeroo/syncflaer
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.1
6 |
7 | require (
8 | github.com/cloudflare/cloudflare-go v0.104.0
9 | github.com/google/go-github/v50 v50.2.0
10 | github.com/sirupsen/logrus v1.9.3
11 | github.com/slack-go/slack v0.14.0
12 | github.com/spf13/pflag v1.0.5
13 | github.com/traefik/traefik/v2 v2.11.10
14 | gopkg.in/yaml.v3 v3.0.1
15 | k8s.io/apimachinery v0.31.1
16 | k8s.io/client-go v0.31.1
17 | )
18 |
19 | require (
20 | github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
21 | github.com/cloudflare/circl v1.3.7 // indirect
22 | github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd // indirect
23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
24 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect
25 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect
26 | github.com/go-logr/logr v1.4.2 // indirect
27 | github.com/go-openapi/jsonpointer v0.19.6 // indirect
28 | github.com/go-openapi/jsonreference v0.20.2 // indirect
29 | github.com/go-openapi/swag v0.22.4 // indirect
30 | github.com/goccy/go-json v0.10.3 // indirect
31 | github.com/gogo/protobuf v1.3.2 // indirect
32 | github.com/golang/protobuf v1.5.4 // indirect
33 | github.com/google/gnostic-models v0.6.8 // indirect
34 | github.com/google/go-cmp v0.6.0 // indirect
35 | github.com/google/go-querystring v1.1.0 // indirect
36 | github.com/google/gofuzz v1.2.0 // indirect
37 | github.com/google/uuid v1.6.0 // indirect
38 | github.com/gorilla/mux v1.8.1 // indirect
39 | github.com/gorilla/websocket v1.5.0 // indirect
40 | github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
41 | github.com/imdario/mergo v0.3.16 // indirect
42 | github.com/jonboulle/clockwork v0.4.0 // indirect
43 | github.com/josharian/intern v1.0.0 // indirect
44 | github.com/json-iterator/go v1.1.12 // indirect
45 | github.com/mailru/easyjson v0.7.7 // indirect
46 | github.com/miekg/dns v1.1.59 // indirect
47 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
48 | github.com/modern-go/reflect2 v1.0.2 // indirect
49 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
50 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
51 | github.com/traefik/paerser v0.2.1 // indirect
52 | github.com/vulcand/predicate v1.2.0 // indirect
53 | github.com/x448/float16 v0.8.4 // indirect
54 | golang.org/x/crypto v0.27.0 // indirect
55 | golang.org/x/mod v0.21.0 // indirect
56 | golang.org/x/net v0.29.0 // indirect
57 | golang.org/x/oauth2 v0.21.0 // indirect
58 | golang.org/x/sync v0.8.0 // indirect
59 | golang.org/x/sys v0.25.0 // indirect
60 | golang.org/x/term v0.24.0 // indirect
61 | golang.org/x/text v0.18.0 // indirect
62 | golang.org/x/time v0.6.0 // indirect
63 | golang.org/x/tools v0.25.0 // indirect
64 | google.golang.org/protobuf v1.34.2 // indirect
65 | gopkg.in/inf.v0 v0.9.1 // indirect
66 | gopkg.in/yaml.v2 v2.4.0 // indirect
67 | k8s.io/api v0.31.1 // indirect
68 | k8s.io/klog/v2 v2.130.1 // indirect
69 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
70 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
71 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
72 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
73 | sigs.k8s.io/yaml v1.4.0 // indirect
74 | )
75 |
76 | replace (
77 | github.com/abbot/go-http-auth => github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e
78 | github.com/go-check/check => github.com/containous/check v0.0.0-20170915194414-ca0bf163426a
79 | github.com/gorilla/mux => github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f
80 | )
81 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
4 | github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
5 | github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
6 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
7 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
8 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
9 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
10 | github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
11 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
12 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
13 | github.com/cloudflare/cloudflare-go v0.104.0 h1:R/lB0dZupaZbOgibAH/BRrkFbZ6Acn/WsKg2iX2xXuY=
14 | github.com/cloudflare/cloudflare-go v0.104.0/go.mod h1:pfUQ4PIG4ISI0/Mmc21Bp86UnFU0ktmPf3iTgbSL+cM=
15 | github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd h1:0n+lFLh5zU0l6KSk3KpnDwfbPGAR44aRLgTbCnhRBHU=
16 | github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd/go.mod h1:BbQgeDS5i0tNvypwEoF1oNjOJw8knRAE1DnVvjDstcQ=
17 | github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f h1:1uEtynq2C0ljy3630jt7EAxg8jZY2gy6YHdGwdqEpWw=
18 | github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg=
19 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
22 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
25 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
26 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
27 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
28 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
29 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
30 | github.com/go-acme/lego/v4 v4.18.0 h1:2hH8KcdRBSb+p5o9VZIm61GAOXYALgILUCSs1Q+OYsk=
31 | github.com/go-acme/lego/v4 v4.18.0/go.mod h1:Blkg3izvXpl3zxk7WKngIuwR2I/hvYVP3vRnvgBp7m8=
32 | github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
33 | github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
34 | github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
35 | github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
36 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
37 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
38 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
39 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
40 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
41 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
42 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
43 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
44 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
45 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
46 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
47 | github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
48 | github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
49 | github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
50 | github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
51 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
52 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
53 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
54 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
55 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
56 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
57 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
58 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
59 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
60 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
61 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
62 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
63 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
64 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
65 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
66 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
67 | github.com/google/go-github/v50 v50.2.0 h1:j2FyongEHlO9nxXLc+LP3wuBSVU9mVxfpdYUexMpIfk=
68 | github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
69 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
70 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
71 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
72 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
73 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
74 | github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ=
75 | github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
76 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
77 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
78 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
79 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
80 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
81 | github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf h1:C1GPyPJrOlJlIrcaBBiBpDsqZena2Ks8spa5xZqr1XQ=
82 | github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf/go.mod h1:zXqxTI6jXDdKnlf8s+nT+3c8LrwUEy3yNpO4XJL90lA=
83 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
84 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
85 | github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
86 | github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
87 | github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
88 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
89 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
90 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
91 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
92 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
93 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
94 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
95 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
96 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
97 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
98 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
99 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
100 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
101 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
102 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
103 | github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
104 | github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
105 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
106 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
107 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
108 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
109 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
110 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
111 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
112 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
113 | github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
114 | github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
115 | github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
116 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
117 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
118 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
119 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
120 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
121 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
122 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
123 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
124 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
125 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
126 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
127 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
128 | github.com/slack-go/slack v0.14.0 h1:6c0UTfbRnvRssZUsZ2qe0Iu07VAMPjRqOa6oX8ewF4k=
129 | github.com/slack-go/slack v0.14.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
130 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
131 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
132 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
133 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
134 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
135 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
136 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
137 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
138 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
139 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
140 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
141 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
142 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
143 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
144 | github.com/traefik/paerser v0.2.1 h1:LFgeak1NmjEHF53c9ENdXdL1UMkF/lD5t+7Evsz4hH4=
145 | github.com/traefik/paerser v0.2.1/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
146 | github.com/traefik/traefik/v2 v2.11.10 h1:QQY4XAKcjXhuXHs8VO8Y/l01pP5LLFxRxQV/ezUL+KQ=
147 | github.com/traefik/traefik/v2 v2.11.10/go.mod h1:fZvS5aKAK7pB1BPTr/ENxC/LsBMuerrxcO+y4bjp/2M=
148 | github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50=
149 | github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA=
150 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
151 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
152 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
153 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
154 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
155 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
156 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
157 | golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
158 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
159 | golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
160 | golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
161 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
162 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
163 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
164 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
165 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
166 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
167 | golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
168 | golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
169 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
170 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
171 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
172 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
173 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
174 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
175 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
176 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
177 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
178 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
179 | golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
180 | golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
181 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
182 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
183 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
184 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
185 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
186 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
187 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
188 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
189 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
190 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
191 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
192 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
193 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
194 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
195 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
196 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
197 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
198 | golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
199 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
200 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
201 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
202 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
203 | golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
204 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
205 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
206 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
207 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
208 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
209 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
210 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
211 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
212 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
213 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
214 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
215 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
216 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
217 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
218 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
219 | golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
220 | golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
221 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
222 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
223 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
224 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
225 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
226 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
227 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
228 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
229 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
230 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
231 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
232 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
233 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
234 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
235 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
236 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
237 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
238 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
239 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
240 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
241 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
242 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
243 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
244 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
245 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
246 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
247 | k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU=
248 | k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI=
249 | k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
250 | k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
251 | k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
252 | k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
253 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
254 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
255 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
256 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
257 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
258 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
259 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
260 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
261 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
262 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
263 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
264 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
265 |
--------------------------------------------------------------------------------
/internal/additionalRecords.go:
--------------------------------------------------------------------------------
1 | package sf
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/cloudflare/cloudflare-go"
7 | log "github.com/sirupsen/logrus"
8 | )
9 |
10 | func GetAdditionalRecords(config *Configuration, currentIP, zoneName string, userRecords []cloudflare.DNSRecord) []cloudflare.DNSRecord {
11 | var additionalRecordNames []string
12 |
13 | additionalRecords:
14 | for _, additionalRecord := range config.AdditionalRecords {
15 | if additionalRecord.Name == "" {
16 | log.Error("Additional DNS record name cannot be empty")
17 | continue
18 | }
19 | if !strings.HasSuffix(additionalRecord.Name, zoneName) {
20 | continue
21 | }
22 | for _, userRecord := range userRecords {
23 | if userRecord.Name == additionalRecord.Name {
24 | log.Warnf("DNS record %s already exists. Skipping...", userRecord.Name)
25 | continue additionalRecords
26 | }
27 | }
28 | additionalRecord.Name = strings.TrimSuffix(additionalRecord.Name, ".")
29 | if additionalRecord.Type == "" {
30 | additionalRecord.Type = config.Cloudflare.Defaults.Type
31 | }
32 | if additionalRecord.Content == "" {
33 | switch additionalRecord.Type {
34 | case "A":
35 | additionalRecord.Content = currentIP
36 | case "CNAME":
37 | additionalRecord.Content = zoneName
38 | default:
39 | log.Errorf("%s is an unsupported type, only A or CNAME are supported", additionalRecord.Type)
40 | continue
41 | }
42 | }
43 | if additionalRecord.TTL == 0 {
44 | additionalRecord.TTL = 1
45 | }
46 | if additionalRecord.Proxied == nil {
47 | additionalRecord.Proxied = config.Cloudflare.Defaults.Proxied
48 | }
49 | userRecords = append(userRecords, additionalRecord)
50 | additionalRecordNames = append(additionalRecordNames, additionalRecord.Name)
51 | }
52 | if *config.ManagedRootRecord {
53 | rootDNSRecord := cloudflare.DNSRecord{
54 | Type: "A",
55 | Name: zoneName,
56 | Content: currentIP,
57 | Proxied: config.Cloudflare.Defaults.Proxied,
58 | TTL: config.Cloudflare.Defaults.TTL,
59 | }
60 | userRecords = append(userRecords, rootDNSRecord)
61 | }
62 |
63 | log.Debugf("Found additional DNS records: %s", strings.Join(additionalRecordNames, ", "))
64 |
65 | return userRecords
66 | }
67 |
--------------------------------------------------------------------------------
/internal/cloudflare.go:
--------------------------------------------------------------------------------
1 | package sf
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/cloudflare/cloudflare-go"
7 | log "github.com/sirupsen/logrus"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | type CloudflareClient struct {
13 | client *cloudflare.API
14 | }
15 |
16 | func (c *CloudflareClient) DeleteGraceRecordPrefix() string {
17 | return "_syncflaer._deletegrace"
18 | }
19 |
20 | func SetupCloudflareClient(apiToken *string) *CloudflareClient {
21 | cf, err := cloudflare.NewWithAPIToken(*apiToken)
22 | if err != nil {
23 | log.Fatalf("Unable to setup Cloudflare client: %s", err)
24 | }
25 | cfc := CloudflareClient{client: cf}
26 |
27 | return &cfc
28 | }
29 |
30 | func CreateCloudflareZoneMap(zoneNames *[]string, cf *CloudflareClient) map[string]string {
31 | zoneIDs := make(map[string]string)
32 | for _, zoneName := range *zoneNames {
33 | zoneID, err := cf.client.ZoneIDByName(zoneName)
34 | if err != nil {
35 | log.Fatalf("Unable to get Cloudflare zone %s id: %s", zoneName, err)
36 | }
37 | log.Debugf("Using Cloudflare DNS zone %s with id %s", zoneName, zoneID)
38 | zoneIDs[zoneName] = zoneID
39 | }
40 |
41 | return zoneIDs
42 | }
43 |
44 | func GetCloudflareDNSRecords(cf *CloudflareClient, zoneID string) []cloudflare.DNSRecord {
45 | dnsRecords, err := cf.client.DNSRecords(context.Background(), zoneID, cloudflare.DNSRecord{})
46 | if err != nil {
47 | log.Fatalf("Unable to get Cloudflare DNS records: %s", err)
48 | }
49 |
50 | var cloudflareDNSRecords []cloudflare.DNSRecord
51 | var cloudflareDNSRecordNames []string
52 |
53 | for _, dnsRecord := range dnsRecords {
54 | if dnsRecord.Type != "CNAME" && dnsRecord.Type != "A" {
55 | continue
56 | }
57 | cloudflareDNSRecords = append(cloudflareDNSRecords, dnsRecord)
58 | cloudflareDNSRecordNames = append(cloudflareDNSRecordNames, dnsRecord.Name)
59 | }
60 | log.Debugf("Found Cloudflare DNS records: %s", strings.Join(cloudflareDNSRecordNames, ", "))
61 |
62 | return cloudflareDNSRecords
63 | }
64 |
65 | func GetDeleteGraceRecords(cf *CloudflareClient, zoneID string) []cloudflare.DNSRecord {
66 | dnsRecords, err := cf.client.DNSRecords(context.Background(), zoneID, cloudflare.DNSRecord{
67 | Type: "TXT",
68 | })
69 | if err != nil {
70 | log.Fatalf("Unable to get delete grace DNS records: %s", err)
71 | }
72 |
73 | var deleteGraceRecords []cloudflare.DNSRecord
74 | var deleteGraceRecordNames []string
75 |
76 | for _, dnsRecord := range dnsRecords {
77 | if !strings.HasPrefix(dnsRecord.Name, cf.DeleteGraceRecordPrefix()) {
78 | continue
79 | }
80 | deleteGraceRecordNames = append(deleteGraceRecordNames, dnsRecord.Name)
81 | deleteGraceRecords = append(deleteGraceRecords, dnsRecord)
82 | }
83 | if deleteGraceRecordNames != nil {
84 | log.Debugf("Found delete grace DNS records: %s", strings.Join(deleteGraceRecordNames, " ,"))
85 | }
86 |
87 | return deleteGraceRecords
88 | }
89 |
90 | func CreateCloudflareDNSRecord(cf *CloudflareClient, zoneID string, record cloudflare.DNSRecord, slackHandler *SlackHandler) {
91 | _, err := cf.client.CreateDNSRecord(context.Background(), zoneID, record)
92 | if err != nil {
93 | errMsg := fmt.Sprintf("Unable to create Cloudflare DNS record %s: %s", record.Name, err)
94 | slackHandler.AddSlackMessage(errMsg, "danger")
95 | log.Error(errMsg)
96 | return
97 | }
98 |
99 | infoMsg := fmt.Sprintf("Created: name: %s, type: %s, content: %s, proxied: %t, ttl: %d", record.Name, record.Type, record.Content, *record.Proxied, record.TTL)
100 | if record.Type != "TXT" {
101 | slackHandler.AddSlackMessage(infoMsg, "good")
102 | log.Info(infoMsg)
103 | return
104 | }
105 | log.Debug(infoMsg)
106 | }
107 |
108 | func DeleteCloudflareDNSRecord(cf *CloudflareClient, zoneID string, record cloudflare.DNSRecord, slackHandler *SlackHandler) {
109 | err := cf.client.DeleteDNSRecord(context.Background(), zoneID, record.ID)
110 | if err != nil {
111 | errMsg := fmt.Sprintf("Unable to delete Cloudflare DNS record %s: %s", record.Name, err)
112 | slackHandler.AddSlackMessage(errMsg, "danger")
113 | log.Error(errMsg)
114 | return
115 | }
116 |
117 | infoMsg := fmt.Sprintf("Deleted: %s", record.Name)
118 | if record.Type != "TXT" {
119 | slackHandler.AddSlackMessage(infoMsg, "good")
120 | log.Info(infoMsg)
121 | return
122 | }
123 | log.Debug(infoMsg)
124 | }
125 |
126 | func UpdateCloudflareDNSRecords(cf *CloudflareClient, zoneID string, cloudflareDNSRecords, userRecords []cloudflare.DNSRecord, slackHandler *SlackHandler) {
127 | for _, dnsRecord := range cloudflareDNSRecords {
128 | for _, userRecord := range userRecords {
129 | if dnsRecord.Name != userRecord.Name {
130 | continue
131 | }
132 | if *dnsRecord.Proxied == *userRecord.Proxied && dnsRecord.TTL == userRecord.TTL && dnsRecord.Content == userRecord.Content {
133 | continue
134 | }
135 | updatedDNSRecord := cloudflare.DNSRecord{
136 | Type: userRecord.Type,
137 | Content: userRecord.Content,
138 | Proxied: userRecord.Proxied,
139 | TTL: userRecord.TTL,
140 | }
141 | err := cf.client.UpdateDNSRecord(context.Background(), zoneID, dnsRecord.ID, updatedDNSRecord)
142 | if err != nil {
143 | errMsg := fmt.Sprintf("Unable to update Cloudflare DNS record %s: %s", dnsRecord.Name, err)
144 | slackHandler.AddSlackMessage(errMsg, "danger")
145 | log.Error(errMsg)
146 | continue
147 | }
148 |
149 | infoMsg := fmt.Sprintf("Updated: name: %s, type: %s, content: %s, proxied: %t, ttl: %d", dnsRecord.Name, updatedDNSRecord.Type, updatedDNSRecord.Content, *updatedDNSRecord.Proxied, updatedDNSRecord.TTL)
150 | slackHandler.AddSlackMessage(infoMsg, "good")
151 | log.Info(infoMsg)
152 | }
153 | }
154 | }
155 |
156 | func GetMissingDNSRecords(cloudflareDNSRecords, userRecords []cloudflare.DNSRecord) []cloudflare.DNSRecord {
157 | var missingRecords []cloudflare.DNSRecord
158 |
159 | for _, userRecord := range userRecords {
160 | recordFound := false
161 | for _, cloudflareDNSRecord := range cloudflareDNSRecords {
162 | if userRecord.Name == cloudflareDNSRecord.Name {
163 | recordFound = true
164 | break
165 | }
166 | }
167 | if !recordFound {
168 | missingRecords = append(missingRecords, userRecord)
169 | }
170 | }
171 |
172 | return missingRecords
173 | }
174 |
175 | func GetOrphanedDNSRecords(cloudflareDNSRecords, userRecords []cloudflare.DNSRecord) []cloudflare.DNSRecord {
176 | var orphanedRecords []cloudflare.DNSRecord
177 |
178 | for _, cloudflareDNSRecord := range cloudflareDNSRecords {
179 | recordFound := false
180 | for _, userRecord := range userRecords {
181 | if userRecord.Name == cloudflareDNSRecord.Name {
182 | recordFound = true
183 | break
184 | }
185 | }
186 | if !recordFound {
187 | orphanedRecords = append(orphanedRecords, cloudflareDNSRecord)
188 | }
189 | }
190 |
191 | return orphanedRecords
192 | }
193 |
194 | func GetDeleteGraceRecord(cf *CloudflareClient, orphanedRecordName string, deleteGraceRecords []cloudflare.DNSRecord) cloudflare.DNSRecord {
195 | var deleteGraceRecordFound cloudflare.DNSRecord
196 | for _, deleteGraceRecord := range deleteGraceRecords {
197 | if deleteGraceRecord.Name == fmt.Sprintf("%s.%s", cf.DeleteGraceRecordPrefix(), orphanedRecordName) {
198 | deleteGraceRecordFound = deleteGraceRecord
199 | break
200 | }
201 | }
202 |
203 | return deleteGraceRecordFound
204 | }
205 |
206 | func UpdateDeleteGraceRecord(cf *CloudflareClient, zoneID string, deleteGraceRecord cloudflare.DNSRecord, orphanedRecordName string) {
207 | newDeleteGrace, _ := strconv.Atoi(deleteGraceRecord.Content)
208 | newDeleteGrace--
209 | deleteGraceRecord.Content = strconv.Itoa(newDeleteGrace)
210 | err := cf.client.UpdateDNSRecord(context.Background(), zoneID, deleteGraceRecord.ID, deleteGraceRecord)
211 | if err != nil {
212 | log.Errorf("Unable to update delete grace DNS record %s: %s", deleteGraceRecord.Name, err)
213 | return
214 | }
215 |
216 | log.Infof("Waiting %s more runs until DNS record %s gets deleted", deleteGraceRecord.Content, orphanedRecordName)
217 | }
218 |
219 | func CleanupDeleteGraceRecords(cf *CloudflareClient, zoneID string, userRecords, cloudflareDNSRecords, deleteGraceRecords []cloudflare.DNSRecord, slackHandler *SlackHandler) {
220 | for _, deleteGraceRecord := range deleteGraceRecords {
221 | dnsRecordFound := false
222 | var dnsRecordName string
223 | for _, userRecord := range userRecords {
224 | if deleteGraceRecord.Name == fmt.Sprintf("%s.%s", cf.DeleteGraceRecordPrefix(), userRecord.Name) {
225 | dnsRecordFound = true
226 | dnsRecordName = userRecord.Name
227 | break
228 | }
229 | }
230 | if dnsRecordFound {
231 | DeleteCloudflareDNSRecord(cf, zoneID, deleteGraceRecord, slackHandler)
232 | log.Infof("DNS record %s is not orphaned anymore", dnsRecordName)
233 | continue
234 | }
235 | for _, cloudflareDNSRecord := range cloudflareDNSRecords {
236 | if deleteGraceRecord.Name == fmt.Sprintf("%s.%s", cf.DeleteGraceRecordPrefix(), cloudflareDNSRecord.Name) {
237 | dnsRecordFound = true
238 | break
239 | }
240 | }
241 | if !dnsRecordFound {
242 | DeleteCloudflareDNSRecord(cf, zoneID, deleteGraceRecord, slackHandler)
243 | log.Debugf("Cleaned up delete grace DNS record %s", deleteGraceRecord.Name)
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/internal/config.go:
--------------------------------------------------------------------------------
1 | package sf
2 |
3 | import (
4 | "os"
5 | "strings"
6 |
7 | "github.com/cloudflare/cloudflare-go"
8 |
9 | log "github.com/sirupsen/logrus"
10 | "gopkg.in/yaml.v3"
11 | )
12 |
13 | type Configuration struct {
14 | SkipUpdateCheck *bool `yaml:"skipUpdateCheck"`
15 | IPProviders []string `yaml:"ipProviders"`
16 | Notifications struct {
17 | Slack struct {
18 | WebhookURL string `yaml:"webhookURL"`
19 | Username string `yaml:"username"`
20 | Channel string `yaml:"channel"`
21 | IconURL string `yaml:"iconURL"`
22 | } `yaml:"slack"`
23 | } `yaml:"notifications"`
24 | TraefikInstances []struct {
25 | Name string `yaml:"name"`
26 | URL string `yaml:"url"`
27 | Username string `yaml:"username"`
28 | Password string `yaml:"password"`
29 | CustomRequestHeaders map[string]string `yaml:"customRequestHeaders"`
30 | IgnoredRules []string `yaml:"ignoredRules"`
31 | DefaultOverrides []struct {
32 | Rule string `yaml:"rule"`
33 | Type string `yaml:"type"`
34 | Content string `yaml:"content"`
35 | Proxied *bool `yaml:"proxied"`
36 | TTL int `yaml:"ttl"`
37 | } `yaml:"defaultOverrides"`
38 | } `yaml:"traefikInstances"`
39 | Kubernetes struct {
40 | Enabled *bool `yaml:"enabled"`
41 | } `yaml:"kubernetes"`
42 | ManagedRootRecord *bool `yaml:"managedRootRecord"`
43 | AdditionalRecords []cloudflare.DNSRecord `yaml:"additionalRecords"`
44 | Cloudflare struct {
45 | APIToken string `yaml:"apiToken"`
46 | DeleteGrace int `yaml:"deleteGrace"`
47 | ZoneNames []string `yaml:"zoneNames"`
48 | Defaults struct {
49 | Type string `yaml:"type"`
50 | Proxied *bool `yaml:"proxied"`
51 | TTL int `yaml:"ttl"`
52 | } `yaml:"defaults"`
53 | } `yaml:"cloudflare"`
54 | }
55 |
56 | func maskValue(value string) string {
57 | rs := []rune(value)
58 | for i := 0; i < len(rs)-3; i++ {
59 | rs[i] = '*'
60 | }
61 | return string(rs)
62 | }
63 |
64 | func GetConfig(configFilePath string) *Configuration {
65 | log.Debugf("Loading config file %s", configFilePath)
66 | configFile, err := os.ReadFile(configFilePath)
67 | if err != nil {
68 | log.Fatalf("Unable to load config file %s from disk: %s", configFilePath, err)
69 | }
70 |
71 | var config Configuration
72 | err = yaml.Unmarshal(configFile, &config)
73 | if err != nil {
74 | log.Fatalf("Unable to read config file: %s", err)
75 | }
76 |
77 | // Check if environment variables are used
78 | var envVarName string
79 |
80 | for i, traefikInstance := range config.TraefikInstances {
81 | if strings.HasPrefix(traefikInstance.Password, "env:") {
82 | envVarName = strings.Replace(traefikInstance.Password, "env:", "", 1)
83 | config.TraefikInstances[i].Password = os.Getenv(envVarName)
84 | log.Debugf("Got Traefik %s password '%s' from environment variable '%s'", traefikInstance.Name, maskValue(config.TraefikInstances[i].Password), envVarName)
85 | }
86 |
87 | for k, v := range traefikInstance.CustomRequestHeaders {
88 | if strings.HasPrefix(v, "env:") {
89 | envVarName = strings.Replace(v, "env:", "", 1)
90 | config.TraefikInstances[i].CustomRequestHeaders[k] = os.Getenv(envVarName)
91 | log.Debugf("Got Traefik %s customRequestHeader '%s' from environment variable '%s' with value '%s'", traefikInstance.Name, k, envVarName, maskValue(config.TraefikInstances[i].CustomRequestHeaders[k]))
92 | }
93 | }
94 | }
95 |
96 | if strings.HasPrefix(config.Notifications.Slack.WebhookURL, "env:") {
97 | envVarName = strings.Replace(config.Notifications.Slack.WebhookURL, "env:", "", 1)
98 | config.Notifications.Slack.WebhookURL = os.Getenv(envVarName)
99 | log.Debugf("Got Slack webhook URL '%s' from environment variable '%s'", maskValue(config.Notifications.Slack.WebhookURL), envVarName)
100 | }
101 |
102 | if strings.HasPrefix(config.Cloudflare.APIToken, "env:") {
103 | envVarName = strings.Replace(config.Cloudflare.APIToken, "env:", "", 1)
104 | config.Cloudflare.APIToken = os.Getenv(envVarName)
105 | log.Debugf("Got Cloudflare API token '%s' from environment variable '%s'", maskValue(config.Cloudflare.APIToken), envVarName)
106 | }
107 |
108 | // Set default values
109 | trueVar := true
110 | falseVar := false
111 | if config.ManagedRootRecord == nil {
112 | config.ManagedRootRecord = &trueVar
113 | log.Debugf("ManagedRootRecord is not set, defaulting to %t", *config.ManagedRootRecord)
114 | }
115 |
116 | if config.Kubernetes.Enabled == nil {
117 | falseVar := false
118 | config.Kubernetes.Enabled = &falseVar
119 | log.Debugf("Kubernetes enabled is not set, defaulting to %t", *config.Kubernetes.Enabled)
120 | }
121 |
122 | if config.Cloudflare.Defaults.Type == "" {
123 | config.Cloudflare.Defaults.Type = "CNAME"
124 | log.Debugf("Cloudflare default type is empty, defaulting to %s", config.Cloudflare.Defaults.Type)
125 | }
126 |
127 | if config.Cloudflare.Defaults.Proxied == nil {
128 | config.Cloudflare.Defaults.Proxied = &trueVar
129 | log.Debugf("Cloudflare default proxied is empty, defaulting to %t", *config.Cloudflare.Defaults.Proxied)
130 | }
131 |
132 | if config.Cloudflare.Defaults.TTL == 0 || *config.Cloudflare.Defaults.Proxied {
133 | config.Cloudflare.Defaults.TTL = 1
134 | log.Debugf("Cloudflare default TTL is empty, defaulting to %d", config.Cloudflare.Defaults.TTL)
135 | }
136 |
137 | if config.IPProviders == nil {
138 | config.IPProviders = append(config.IPProviders, "https://ifconfig.me/ip", "https://ipecho.net/plain", "https://checkip.amazonaws.com", "https://api.ipify.org")
139 | log.Debugf("IP providers is empty, defaulting to %s", strings.Join(config.IPProviders, ", "))
140 | }
141 |
142 | if config.SkipUpdateCheck == nil {
143 | config.SkipUpdateCheck = &falseVar
144 | log.Debugf("SkipUpdateCheck is not set, defaulting to %t", *config.SkipUpdateCheck)
145 | }
146 |
147 | if config.Notifications.Slack.Username == "" {
148 | config.Notifications.Slack.Username = "SyncFlaer"
149 | log.Debugf("Slack username is empty, defaulting to %s", config.Notifications.Slack.Username)
150 | }
151 |
152 | if config.Notifications.Slack.IconURL == "" {
153 | config.Notifications.Slack.IconURL = "https://www.cloudflare.com/img/cf-facebook-card.png"
154 | log.Debugf("Slack icon URL is empty, defaulting to %s", config.Notifications.Slack.IconURL)
155 | }
156 |
157 | // Validate config
158 | for _, traefikInstance := range config.TraefikInstances {
159 | if traefikInstance.Name == "" {
160 | log.Fatal("Traefik instance name cannot be empty")
161 | }
162 |
163 | if traefikInstance.URL == "" {
164 | log.Fatalf("Traefik URL for instance %s cannot be empty", traefikInstance.Name)
165 | }
166 | }
167 |
168 | if config.Cloudflare.APIToken == "" {
169 | log.Fatal("Cloudflare API token cannot be empty")
170 | }
171 |
172 | if config.Cloudflare.ZoneNames == nil {
173 | log.Fatal("Cloudflare zone name cannot be empty")
174 | }
175 |
176 | if config.Cloudflare.Defaults.Type != "A" && config.Cloudflare.Defaults.Type != "CNAME" {
177 | log.Fatal("Supported Cloudflare default types are A or CNAME")
178 | }
179 |
180 | return &config
181 | }
182 |
--------------------------------------------------------------------------------
/internal/flags.go:
--------------------------------------------------------------------------------
1 | package sf
2 |
3 | import flag "github.com/spf13/pflag"
4 |
5 | func ParseFlags() (string, bool, bool) {
6 | configFilePath := flag.StringP("config-path", "c", "config.yml", "Path to config file")
7 | printVersion := flag.BoolP("version", "v", false, "Print the current version and exit")
8 | debug := flag.BoolP("debug", "d", false, "Enable debug mode")
9 | flag.Parse()
10 |
11 | return *configFilePath, *printVersion, *debug
12 | }
13 |
--------------------------------------------------------------------------------
/internal/ip.go:
--------------------------------------------------------------------------------
1 | package sf
2 |
3 | import (
4 | "io"
5 | "math/rand"
6 | "net"
7 | "net/http"
8 | "strings"
9 | "time"
10 |
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | func GetCurrentIP(ipProviders *[]string) string {
15 | rand.Seed(time.Now().UnixNano())
16 | providers := *ipProviders
17 | rand.Shuffle(len(providers), func(i, j int) { providers[i], providers[j] = providers[j], providers[i] })
18 |
19 | var success bool
20 | var currentIP string
21 | for _, provider := range providers {
22 | success = false
23 | resp, err := http.Get(provider)
24 | if err != nil {
25 | log.Errorf("Unable to get public ip from %s: %s", provider, err)
26 | continue
27 | }
28 | if resp.StatusCode != 200 {
29 | log.Errorf("Unable to get public ip from %s: http status code %d", provider, resp.StatusCode)
30 | continue
31 | }
32 | ip, err := io.ReadAll(resp.Body)
33 | if err != nil {
34 | log.Errorf("Unable to get public ip from %s: %s", provider, err)
35 | continue
36 | }
37 | currentIP = strings.TrimSpace(string(ip))
38 | if net.ParseIP(currentIP) == nil {
39 | log.Errorf("Public ip %s from %s is invalid", currentIP, provider)
40 | continue
41 | }
42 | log.Debugf("Got public ip %s from provider %s", currentIP, provider)
43 | success = true
44 | break
45 | }
46 | if !success {
47 | log.Fatal("Unable to get public ip from any configured provider")
48 | }
49 |
50 | return currentIP
51 | }
52 |
--------------------------------------------------------------------------------
/internal/kube/client.go:
--------------------------------------------------------------------------------
1 | package kube
2 |
3 | import (
4 | "os"
5 | "path"
6 |
7 | log "github.com/sirupsen/logrus"
8 | "k8s.io/client-go/kubernetes"
9 | "k8s.io/client-go/rest"
10 | "k8s.io/client-go/tools/clientcmd"
11 | )
12 |
13 | func SetupKubernetesClient() kubernetes.Interface {
14 | var kubeConfig *rest.Config
15 | var err error
16 |
17 | if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
18 | kubeConfig, err = rest.InClusterConfig()
19 | if err != nil {
20 | log.Fatal(err)
21 | }
22 | } else {
23 | kubeconfigPath := os.Getenv("KUBECONFIG")
24 | if kubeconfigPath == "" {
25 | kubeconfigPath = path.Join(os.Getenv("HOME"), ".kube", "config")
26 | }
27 | kubeConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath)
28 | if err != nil {
29 | log.Fatal(err)
30 | }
31 | }
32 |
33 | clientset, err := kubernetes.NewForConfig(kubeConfig)
34 | if err != nil {
35 | log.Fatal(err)
36 | }
37 |
38 | return clientset
39 | }
40 |
--------------------------------------------------------------------------------
/internal/kube/ingresses.go:
--------------------------------------------------------------------------------
1 | package kube
2 |
3 | import (
4 | "context"
5 | "github.com/cloudflare/cloudflare-go"
6 | internal "github.com/containeroo/syncflaer/internal"
7 | log "github.com/sirupsen/logrus"
8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9 | "k8s.io/client-go/kubernetes"
10 | "strings"
11 | )
12 |
13 | func GetIngresses(kubeClient kubernetes.Interface, config *internal.Configuration, currentIP, zoneName string, userRecords []cloudflare.DNSRecord) []cloudflare.DNSRecord {
14 | ingresses, err := kubeClient.NetworkingV1().Ingresses("").List(context.Background(), metav1.ListOptions{})
15 | if err != nil {
16 | log.Errorf("Error getting ingresses: %s", err)
17 | }
18 |
19 | var ingressNames []string
20 |
21 | for _, ingress := range ingresses.Items {
22 | if ingress.Annotations["syncflaer.containeroo.ch/ignore"] == "true" {
23 | log.Debugf("Ignoring ingress %s/%s", ingress.Namespace, ingress.Name)
24 | continue
25 | }
26 |
27 | rules:
28 | for _, rule := range ingress.Spec.Rules {
29 | if !strings.HasSuffix(rule.Host, zoneName) {
30 | continue
31 | }
32 | for _, userRecord := range userRecords {
33 | if userRecord.Name == rule.Host {
34 | log.Warnf("DNS record %s (in ingress: %s/%s) already defined elsewhere. Skipping...", userRecord.Name, ingress.Namespace, ingress.Name)
35 | continue rules
36 | }
37 | }
38 | dnsRecord := cloudflare.DNSRecord{
39 | Name: rule.Host,
40 | }
41 | if ingress.Annotations["syncflaer.containeroo.ch/type"] == "" {
42 | dnsRecord.Type = config.Cloudflare.Defaults.Type
43 | }
44 | if ingress.Annotations["syncflaer.containeroo.ch/content"] == "" {
45 | switch dnsRecord.Type {
46 | case "A":
47 | dnsRecord.Content = currentIP
48 | case "CNAME":
49 | dnsRecord.Content = zoneName
50 | default:
51 | log.Errorf("%s is an unsupported type, only A and CNAME are supported", ingress.Annotations["syncflaer.containeroo.ch/type"])
52 | continue
53 | }
54 | }
55 | if ingress.Annotations["syncflaer.containeroo.ch/ttl"] == "" {
56 | dnsRecord.TTL = config.Cloudflare.Defaults.TTL
57 | }
58 | if ingress.Annotations["syncflaer.containeroo.ch/proxied"] == "" {
59 | dnsRecord.Proxied = config.Cloudflare.Defaults.Proxied
60 | }
61 | userRecords = append(userRecords, dnsRecord)
62 | ingressNames = append(ingressNames, rule.Host)
63 | }
64 | }
65 |
66 | log.Debugf("Found Kubernetes ingresses: %s", strings.Join(ingressNames, ", "))
67 |
68 | return userRecords
69 | }
70 |
--------------------------------------------------------------------------------
/internal/slack.go:
--------------------------------------------------------------------------------
1 | package sf
2 |
3 | import (
4 | log "github.com/sirupsen/logrus"
5 | "github.com/slack-go/slack"
6 | )
7 |
8 | type SlackHandler struct {
9 | messages []slack.Attachment
10 | }
11 |
12 | func (s *SlackHandler) AddSlackMessage(message, color string) {
13 | newMessage := slack.Attachment{
14 | Text: message,
15 | Color: color,
16 | }
17 | s.messages = append(s.messages, newMessage)
18 | }
19 |
20 | func (s *SlackHandler) SendSlackMessage(config *Configuration) {
21 | if config.Notifications.Slack.WebhookURL == "" || len(s.messages) == 0 {
22 | return
23 | }
24 |
25 | msg := slack.WebhookMessage{
26 | Username: config.Notifications.Slack.Username,
27 | IconURL: config.Notifications.Slack.IconURL,
28 | Channel: config.Notifications.Slack.Channel,
29 | Attachments: s.messages,
30 | }
31 |
32 | err := slack.PostWebhook(config.Notifications.Slack.WebhookURL, &msg)
33 | if err != nil {
34 | log.Errorf("Unable to send Slack message: %s", err)
35 | }
36 | }
37 |
38 | func NewSlackHandler() *SlackHandler {
39 | return &SlackHandler{}
40 | }
41 |
--------------------------------------------------------------------------------
/internal/traefik.go:
--------------------------------------------------------------------------------
1 | package sf
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "net/http"
7 | "net/url"
8 | "path"
9 | "strings"
10 |
11 | "github.com/cloudflare/cloudflare-go"
12 | log "github.com/sirupsen/logrus"
13 | httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http"
14 | )
15 |
16 | type TraefikRouter struct {
17 | EntryPoints []string `json:"entryPoints"`
18 | Middlewares []string `json:"middlewares,omitempty"`
19 | Service string `json:"service"`
20 | Rule string `json:"rule"`
21 | TLS struct {
22 | CertResolver string `json:"certResolver"`
23 | Domains []struct {
24 | Main string `json:"main"`
25 | Sans []string `json:"sans"`
26 | } `json:"domains"`
27 | } `json:"tls,omitempty"`
28 | Status string `json:"status"`
29 | Using []string `json:"using"`
30 | Name string `json:"name"`
31 | Provider string `json:"provider"`
32 | Priority int64 `json:"priority,omitempty"`
33 | }
34 |
35 | func checkDuplicateRule(rule string, rules []cloudflare.DNSRecord) bool {
36 | for _, r := range rules {
37 | if r.Name == rule {
38 | return true
39 | }
40 | }
41 | return false
42 | }
43 |
44 | func GetTraefikRules(config *Configuration, currentIP, zoneName string, userRecords []cloudflare.DNSRecord) []cloudflare.DNSRecord {
45 | for _, traefikInstance := range config.TraefikInstances {
46 | traefikURL, err := url.Parse(traefikInstance.URL)
47 | if err != nil {
48 | log.Fatalf("Unable to parse Traefik url %s: %s", traefikInstance.URL, err)
49 | }
50 | traefikURL.Path = path.Join(traefikURL.Path, "/api/http/routers")
51 | traefikHost := traefikURL.String()
52 |
53 | client := &http.Client{}
54 | req, err := http.NewRequest("GET", traefikHost, nil)
55 | if err != nil {
56 | log.Fatalf("Error creating http client for Traefik %s: %s", traefikInstance.Name, err)
57 | }
58 | if traefikInstance.Username != "" && traefikInstance.Password != "" {
59 | req.SetBasicAuth(traefikInstance.Username, traefikInstance.Password)
60 | }
61 | for k, v := range traefikInstance.CustomRequestHeaders {
62 | req.Header.Add(k, v)
63 | log.Debugf("Adding request header to Traefik %s: '%s: %s'", traefikInstance.Name, k, maskValue(v))
64 | }
65 | resp, err := client.Do(req)
66 | if err != nil {
67 | log.Fatalf("Unable to get Traefik %s rules: %s", traefikInstance.Name, err)
68 | }
69 | if resp.StatusCode != 200 {
70 | log.Fatalf("Unable to get Traefik %s rules: http status code %d", traefikInstance.Name, resp.StatusCode)
71 | }
72 |
73 | respData, err := io.ReadAll(resp.Body)
74 | if err != nil {
75 | log.Fatalf("Unable to read Traefik %s rules: %s", traefikInstance.Name, err)
76 | }
77 |
78 | var traefikRouters []TraefikRouter
79 | err = json.Unmarshal(respData, &traefikRouters)
80 | if err != nil {
81 | log.Fatalf("Unable to load Traefik %s rules: %s", traefikInstance.Name, err)
82 | }
83 |
84 | var content string
85 | switch config.Cloudflare.Defaults.Type {
86 | case "A":
87 | content = currentIP
88 | case "CNAME":
89 | content = zoneName
90 | }
91 |
92 | var ruleNames []string
93 | for _, router := range traefikRouters {
94 | parsedDomains, err := httpmuxer.ParseDomains(router.Rule)
95 | if err != nil {
96 | log.Fatalf("Unable to parse rule %s for Traefik %s: %s", router.Rule, traefikInstance.Name, err)
97 | }
98 |
99 | parsedDomain:
100 | for _, parsedDomain := range parsedDomains {
101 | if !strings.HasSuffix(parsedDomain, zoneName) {
102 | continue
103 | }
104 | if parsedDomain == zoneName {
105 | continue
106 | }
107 | if checkDuplicateRule(parsedDomain, userRecords) {
108 | continue
109 | }
110 | for _, ignoredRule := range traefikInstance.IgnoredRules {
111 | if strings.HasSuffix(parsedDomain, ignoredRule) {
112 | continue parsedDomain
113 | }
114 | }
115 | traefikRecord := cloudflare.DNSRecord{}
116 | traefikRecord.Name = parsedDomain
117 | traefikRecord.Type = config.Cloudflare.Defaults.Type
118 | traefikRecord.Content = content
119 | traefikRecord.Proxied = config.Cloudflare.Defaults.Proxied
120 | traefikRecord.TTL = config.Cloudflare.Defaults.TTL
121 |
122 | for _, defaultOverride := range traefikInstance.DefaultOverrides {
123 | if defaultOverride.Rule != parsedDomain {
124 | continue
125 | }
126 | if defaultOverride.Type != "" {
127 | traefikRecord.Type = defaultOverride.Type
128 | }
129 | if defaultOverride.Content != "" {
130 | traefikRecord.Content = defaultOverride.Content
131 | }
132 | if defaultOverride.Proxied != nil {
133 | traefikRecord.Proxied = defaultOverride.Proxied
134 | }
135 | if defaultOverride.TTL != 0 {
136 | traefikRecord.TTL = defaultOverride.TTL
137 | }
138 | }
139 |
140 | userRecords = append(userRecords, traefikRecord)
141 | ruleNames = append(ruleNames, parsedDomain)
142 | }
143 | }
144 | log.Debugf("Found rules in Traefik %s: %s", traefikInstance.Name, strings.Join(ruleNames, ", "))
145 | }
146 |
147 | return userRecords
148 | }
149 |
--------------------------------------------------------------------------------