Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5acd3beff5 | ||
|
|
e10830434b | ||
|
|
c7a1d5dcef | ||
|
|
7b4c342631 | ||
|
|
700dbd2d63 | ||
|
|
f5c9e822a7 | ||
|
|
fb5afb2505 | ||
|
|
2882785b9b | ||
|
|
7d9edf4fa0 | ||
|
|
bc70795769 | ||
|
|
ea92d376c7 | ||
|
|
16e97cd9b2 | ||
|
|
20fe83745f | ||
|
|
961cf4dbb5 | ||
|
|
81d1bbf27b | ||
|
|
0fe6bb3a0b | ||
|
|
847a38fe1c | ||
|
|
1db6a5a4e0 | ||
|
|
6388eec95f | ||
|
|
31ac480737 | ||
|
|
6877b58b02 | ||
|
|
d212045058 | ||
|
|
83f34fb377 | ||
|
|
f1a943491a | ||
|
|
75d9724db6 | ||
|
|
531d63d04f | ||
|
|
84999be6fc | ||
|
|
190ccdad8a | ||
|
|
49054242f8 | ||
|
|
930684ffc2 | ||
|
|
3fac1d2ced | ||
|
|
c85579ce4a | ||
|
|
d0dd73220d | ||
|
|
80f4436d98 | ||
|
|
fecc60cfca | ||
|
|
b3d052db80 | ||
|
|
9cd88a0b47 | ||
|
|
56eb522ea5 | ||
|
|
e6feaddc15 | ||
|
|
70ae93f73a | ||
|
|
c3d6e696a1 | ||
|
|
89f2a1eb82 | ||
|
|
307b050579 | ||
|
|
caddf2a029 | ||
|
|
20ff0192d5 | ||
|
|
7d8cfbaeed | ||
|
|
b07101c97a | ||
|
|
447bfb24a9 | ||
|
|
4fff8f4302 | ||
|
|
04bd12d7dd | ||
|
|
4e33a88163 | ||
|
|
4a38df1b59 | ||
|
|
9ec5c0412e | ||
|
|
01676e12f3 | ||
|
|
78f7d08398 | ||
|
|
11d4d14f4d | ||
|
|
ff91f4873a | ||
|
|
778bf7b516 | ||
|
|
cce24c1223 | ||
|
|
b4fabf687b | ||
|
|
74ae6a9096 | ||
|
|
92129ab069 | ||
|
|
d7f50086c3 | ||
|
|
50f883337a | ||
|
|
5a35f7c9d1 | ||
|
|
1b1a712a65 | ||
|
|
724a20e47e | ||
|
|
64c922a1d8 | ||
|
|
905c783ca9 | ||
|
|
4eac62e6ce | ||
|
|
418409ac47 | ||
|
|
4895bfdc55 | ||
|
|
976902f861 | ||
|
|
b06a1cfec0 | ||
|
|
449fce9d6a | ||
|
|
6b45caadfb | ||
|
|
06b49cb810 | ||
|
|
ae58133a0d | ||
|
|
2b0bfb126e | ||
|
|
97841e3f32 | ||
|
|
2fc2baf6f8 | ||
|
|
06355a71da | ||
|
|
9782c430af | ||
|
|
cafdf14008 | ||
|
|
e7569de5c6 | ||
|
|
2b69fe15ed | ||
|
|
bb1f2d02c5 | ||
|
|
3bd093ceb9 | ||
|
|
05c0eab4a8 | ||
|
|
57745f2953 | ||
|
|
9f5ac14aa1 | ||
|
|
8f2aa58fe8 | ||
|
|
01e23d3b69 | ||
|
|
c2da171c85 | ||
|
|
b44dd16ad8 | ||
|
|
ba00016be8 | ||
|
|
e301f483c6 | ||
|
|
ebf25a8ddf | ||
|
|
56d6644194 | ||
|
|
51401dac81 | ||
|
|
18b07898ea | ||
|
|
223739f4a7 | ||
|
|
02ff6a11f0 | ||
|
|
2bff464f65 | ||
|
|
8fe0fe27ea | ||
|
|
5c00caa296 | ||
|
|
344337a3d5 | ||
|
|
e1287564c3 | ||
|
|
8f61de83bf | ||
|
|
f3810cf241 | ||
|
|
3d385f409e | ||
|
|
19bdd47b3c | ||
|
|
32c728ccf8 | ||
|
|
327d67c9ef | ||
|
|
edd7f82351 | ||
|
|
012d43b950 | ||
|
|
e2298f6f1e | ||
|
|
18350f94a6 | ||
|
|
1a9d5df5cb | ||
|
|
8ef1eb8c2a | ||
|
|
7c2018acbc | ||
|
|
2079f6a3eb | ||
|
|
d9419446bf | ||
|
|
c0cbccb63c | ||
|
|
5444dda08a | ||
|
|
224617aae6 | ||
|
|
c941c8a100 | ||
|
|
07a25cd094 | ||
|
|
54d98c10fe |
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -58,7 +58,7 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
id: logs
|
id: logs
|
||||||
attributes:
|
attributes:
|
||||||
label: Relevant log output
|
label: Relevant DEBUG log output
|
||||||
description: |
|
description: |
|
||||||
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
|
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
|
||||||
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.
|
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.
|
||||||
|
|||||||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -42,11 +42,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/docker-images.yml
vendored
2
.github/workflows/docker-images.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
|||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Modify Dockerfile
|
- name: Modify Dockerfile
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/e2e.yaml
vendored
2
.github/workflows/e2e.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
protocol: http
|
protocol: http
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup jq
|
- name: Setup jq
|
||||||
uses: dcarbone/install-jq-action@v3
|
uses: dcarbone/install-jq-action@v3
|
||||||
|
|||||||
26
.github/workflows/go.yml
vendored
26
.github/workflows/go.yml
vendored
@@ -15,28 +15,30 @@ jobs:
|
|||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: Lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
run: make lint
|
||||||
with:
|
|
||||||
skip-cache: true
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: test
|
name: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
@@ -47,7 +49,9 @@ jobs:
|
|||||||
run: make test-ci
|
run: make test-ci
|
||||||
|
|
||||||
- name: Send coverage
|
- name: Send coverage
|
||||||
|
if: runner.os == 'Linux'
|
||||||
uses: shogo82148/actions-goveralls@v1
|
uses: shogo82148/actions-goveralls@v1
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
path-to-profile: coverage.out
|
path-to-profile: coverage.out
|
||||||
|
|
||||||
@@ -57,10 +61,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
stale-issue-message: 'This issue has been inactive for 60 days. If the issue is still relevant please comment to re-activate the issue. If no action is taken within 7 days, the issue will be marked closed.'
|
stale-issue-message: 'This issue has been inactive for 60 days. If the issue is still relevant please comment to re-activate the issue. If no action is taken within 7 days, the issue will be marked closed.'
|
||||||
|
|||||||
16
.github/workflows/virustotal.yaml
vendored
Normal file
16
.github/workflows/virustotal.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: Scan GitHub Release with VirusTotal
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
scan_release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Analyze Build Assets
|
||||||
|
uses: bakito/virustotal-action@main
|
||||||
|
with:
|
||||||
|
release_name: ${{github.event.release.tag_name}}
|
||||||
|
vt_api_key: ${{secrets.VT_API_KEY}}
|
||||||
215
.golangci.yaml
Normal file
215
.golangci.yaml
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
version: '2'
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
- canonicalheader
|
||||||
|
- containedctx
|
||||||
|
- copyloopvar
|
||||||
|
- decorder
|
||||||
|
- dogsled
|
||||||
|
- dupword
|
||||||
|
- durationcheck
|
||||||
|
- err113
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
- exptostd
|
||||||
|
- fatcontext
|
||||||
|
- forcetypeassert
|
||||||
|
- gocheckcompilerdirectives
|
||||||
|
- gochecksumtype
|
||||||
|
- gocritic
|
||||||
|
- godot
|
||||||
|
- gomodguard
|
||||||
|
- goprintffuncname
|
||||||
|
- gosmopolitan
|
||||||
|
- grouper
|
||||||
|
- iface
|
||||||
|
- importas
|
||||||
|
- inamedparam
|
||||||
|
- interfacebloat
|
||||||
|
- intrange
|
||||||
|
- loggercheck
|
||||||
|
- makezero
|
||||||
|
- mirror
|
||||||
|
- misspell
|
||||||
|
- nilerr
|
||||||
|
- nilnesserr
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- nosprintfhostport
|
||||||
|
- perfsprint
|
||||||
|
- predeclared
|
||||||
|
- promlinter
|
||||||
|
- protogetter
|
||||||
|
- reassign
|
||||||
|
- revive
|
||||||
|
- rowserrcheck
|
||||||
|
- sloglint
|
||||||
|
- spancheck
|
||||||
|
- sqlclosecheck
|
||||||
|
- staticcheck
|
||||||
|
- tagalign
|
||||||
|
- testableexamples
|
||||||
|
- testifylint
|
||||||
|
- thelper
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- usestdlibvars
|
||||||
|
- usetesting
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
- zerologlint
|
||||||
|
disable:
|
||||||
|
- asasalint
|
||||||
|
- contextcheck
|
||||||
|
- cyclop
|
||||||
|
- depguard
|
||||||
|
- dupl
|
||||||
|
- errchkjson
|
||||||
|
- exhaustive
|
||||||
|
- exhaustruct
|
||||||
|
- forbidigo
|
||||||
|
- funlen
|
||||||
|
- ginkgolinter
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- gocognit
|
||||||
|
- goconst
|
||||||
|
- gocyclo
|
||||||
|
- godox
|
||||||
|
- goheader
|
||||||
|
- gomoddirectives
|
||||||
|
- gosec
|
||||||
|
- ireturn
|
||||||
|
- lll
|
||||||
|
- maintidx
|
||||||
|
- musttag
|
||||||
|
- nakedret
|
||||||
|
- nestif
|
||||||
|
- nilnil
|
||||||
|
- nlreturn
|
||||||
|
- nonamedreturns
|
||||||
|
- paralleltest
|
||||||
|
- prealloc
|
||||||
|
- recvcheck
|
||||||
|
- tagliatelle
|
||||||
|
- testpackage
|
||||||
|
- tparallel
|
||||||
|
- varnamelen
|
||||||
|
- wrapcheck
|
||||||
|
- wsl
|
||||||
|
settings:
|
||||||
|
gocritic:
|
||||||
|
enable-all: true
|
||||||
|
disabled-checks:
|
||||||
|
- emptyFallthrough
|
||||||
|
- hugeParam
|
||||||
|
- rangeValCopy
|
||||||
|
- unnamedResult
|
||||||
|
- whyNoLint
|
||||||
|
govet:
|
||||||
|
disable:
|
||||||
|
- fieldalignment
|
||||||
|
- shadow
|
||||||
|
enable-all: true
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
revive:
|
||||||
|
enable-all-rules: true
|
||||||
|
rules:
|
||||||
|
- name: add-constant
|
||||||
|
disabled: true
|
||||||
|
- name: cognitive-complexity
|
||||||
|
disabled: true
|
||||||
|
- name: cyclomatic
|
||||||
|
disabled: true
|
||||||
|
- name: deep-exit
|
||||||
|
disabled: true
|
||||||
|
- name: dot-imports
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
exclude: [""]
|
||||||
|
arguments:
|
||||||
|
- allowedPackages: ["github.com/onsi/ginkgo/v2", "github.com/onsi/gomega"]
|
||||||
|
- name: empty-block
|
||||||
|
disabled: true
|
||||||
|
- name: exported
|
||||||
|
disabled: true
|
||||||
|
- name: filename-format
|
||||||
|
arguments:
|
||||||
|
- ^[a-z][-0-9_a-z]*(?:\.gen)?\.go$
|
||||||
|
- name: flag-parameter
|
||||||
|
disabled: true
|
||||||
|
- name: function-length
|
||||||
|
disabled: true
|
||||||
|
- name: function-result-limit
|
||||||
|
disabled: true
|
||||||
|
- name: import-shadowing
|
||||||
|
disabled: true
|
||||||
|
- name: line-length-limit
|
||||||
|
disabled: true
|
||||||
|
- name: max-control-nesting
|
||||||
|
disabled: true
|
||||||
|
- name: max-public-structs
|
||||||
|
disabled: true
|
||||||
|
- name: nested-structs
|
||||||
|
disabled: true
|
||||||
|
- name: package-comments
|
||||||
|
disabled: true
|
||||||
|
- name: unused-parameter
|
||||||
|
disabled: true
|
||||||
|
- name: unused-receiver
|
||||||
|
disabled: true
|
||||||
|
- name: var-naming
|
||||||
|
disabled: true
|
||||||
|
- name: enforce-switch-style
|
||||||
|
disabled: true
|
||||||
|
- name: blank-imports
|
||||||
|
disabled: true
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- 'all'
|
||||||
|
- '-ST1000'
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
rules:
|
||||||
|
- linters:
|
||||||
|
- err113
|
||||||
|
text: do not define dynamic errors, use wrapped static errors instead
|
||||||
|
- linters:
|
||||||
|
- forbidigo
|
||||||
|
path: ^internal/cmds/
|
||||||
|
- linters:
|
||||||
|
- forcetypeassert
|
||||||
|
path: _test\.go$
|
||||||
|
- linters:
|
||||||
|
- forbidigo
|
||||||
|
path: assets/scripts/generate-commit.go
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gci
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
- golines
|
||||||
|
settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- default
|
||||||
|
- prefix(github.com/bakito/adguardhome-sync)
|
||||||
|
gofumpt:
|
||||||
|
module-path: github.com/bakito/adguardhome-sync
|
||||||
|
extra-rules: true
|
||||||
|
goimports:
|
||||||
|
local-prefixes:
|
||||||
|
- github.com/bakito/adguardhome-sync
|
||||||
|
golines:
|
||||||
|
max-len: 128
|
||||||
|
tab-len: 4
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
run:
|
|
||||||
timeout: 5m
|
|
||||||
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- asciicheck
|
|
||||||
- bodyclose
|
|
||||||
- dogsled
|
|
||||||
- durationcheck
|
|
||||||
- errcheck
|
|
||||||
- errorlint
|
|
||||||
- gci
|
|
||||||
- gofmt
|
|
||||||
- gofumpt
|
|
||||||
- goimports
|
|
||||||
- gosec
|
|
||||||
- gosimple
|
|
||||||
- govet
|
|
||||||
- importas
|
|
||||||
- ineffassign
|
|
||||||
- misspell
|
|
||||||
- nakedret
|
|
||||||
- nolintlint
|
|
||||||
- staticcheck
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
linters-settings:
|
|
||||||
gosec:
|
|
||||||
# Exclude generated files
|
|
||||||
exclude-generated: true
|
|
||||||
excludes:
|
|
||||||
- G601 # not applicable in go 1.22 anymore
|
|
||||||
gofmt:
|
|
||||||
# simplify code: gofmt with `-s` option, true by default
|
|
||||||
simplify: true
|
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ builds:
|
|||||||
- linux
|
- linux
|
||||||
- windows
|
- windows
|
||||||
- darwin
|
- darwin
|
||||||
|
- openbsd
|
||||||
goarch:
|
goarch:
|
||||||
- 386
|
- 386
|
||||||
- amd64
|
- amd64
|
||||||
@@ -28,10 +29,14 @@ builds:
|
|||||||
goarch: arm
|
goarch: arm
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
- goos: openbsd
|
||||||
|
goarch: 386
|
||||||
|
- goos: openbsd
|
||||||
|
goarch: arm
|
||||||
hooks:
|
hooks:
|
||||||
post:
|
post:
|
||||||
# don't upx windows binaries as they make trouble with virus scanners
|
# don't upx windows binaries as they make trouble with virus scanners
|
||||||
- bash -c 'if [[ "{{ .Path }}" != *.exe ]] && [[ "{{ .Path }}" != *darwin* ]]; then upx {{ .Path }}; fi'
|
- bash -c 'if [[ "{{ .Path }}" != *.exe ]] && [[ "{{ .Path }}" != *darwin* ]] && [[ "{{ .Path }}" != *openbsd* ]]; then upx {{ .Path }}; fi'
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
snapshot:
|
snapshot:
|
||||||
|
|||||||
116
.toolbox.mk
116
.toolbox.mk
@@ -6,99 +6,87 @@ TB_LOCALDIR ?= $(shell which cygpath > /dev/null 2>&1 && cygpath -m $$(pwd) || p
|
|||||||
## Location to install dependencies to
|
## Location to install dependencies to
|
||||||
TB_LOCALBIN ?= $(TB_LOCALDIR)/bin
|
TB_LOCALBIN ?= $(TB_LOCALDIR)/bin
|
||||||
$(TB_LOCALBIN):
|
$(TB_LOCALBIN):
|
||||||
mkdir -p $(TB_LOCALBIN)
|
if [ ! -e $(TB_LOCALBIN) ]; then mkdir -p $(TB_LOCALBIN); fi
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
STRIP_V = $(patsubst v%,%,$(1))
|
||||||
|
|
||||||
## Tool Binaries
|
## Tool Binaries
|
||||||
TB_DEEPCOPY_GEN ?= $(TB_LOCALBIN)/deepcopy-gen
|
TB_CONTROLLER_GEN ?= $(TB_LOCALBIN)/controller-gen
|
||||||
TB_GINKGO ?= $(TB_LOCALBIN)/ginkgo
|
TB_GINKGO ?= $(TB_LOCALBIN)/ginkgo
|
||||||
TB_GOFUMPT ?= $(TB_LOCALBIN)/gofumpt
|
|
||||||
TB_GOLANGCI_LINT ?= $(TB_LOCALBIN)/golangci-lint
|
TB_GOLANGCI_LINT ?= $(TB_LOCALBIN)/golangci-lint
|
||||||
TB_GOLINES ?= $(TB_LOCALBIN)/golines
|
|
||||||
TB_GORELEASER ?= $(TB_LOCALBIN)/goreleaser
|
TB_GORELEASER ?= $(TB_LOCALBIN)/goreleaser
|
||||||
TB_MOCKGEN ?= $(TB_LOCALBIN)/mockgen
|
TB_MOCKGEN ?= $(TB_LOCALBIN)/mockgen
|
||||||
TB_OAPI_CODEGEN ?= $(TB_LOCALBIN)/oapi-codegen
|
TB_OAPI_CODEGEN ?= $(TB_LOCALBIN)/oapi-codegen
|
||||||
TB_SEMVER ?= $(TB_LOCALBIN)/semver
|
TB_SEMVER ?= $(TB_LOCALBIN)/semver
|
||||||
|
|
||||||
## Tool Versions
|
## Tool Versions
|
||||||
# renovate: packageName=k8s.io/code-generator/cmd/deepcopy-gen
|
# renovate: packageName=github.com/kubernetes-sigs/controller-tools
|
||||||
TB_DEEPCOPY_GEN_VERSION ?= v0.32.2
|
TB_CONTROLLER_GEN_VERSION ?= v0.19.0
|
||||||
# renovate: packageName=mvdan.cc/gofumpt
|
# renovate: packageName=github.com/golangci/golangci-lint/v2
|
||||||
TB_GOFUMPT_VERSION ?= v0.7.0
|
TB_GOLANGCI_LINT_VERSION ?= v2.4.0
|
||||||
# renovate: packageName=github.com/golangci/golangci-lint/cmd/golangci-lint
|
TB_GOLANGCI_LINT_VERSION_NUM ?= $(call STRIP_V,$(TB_GOLANGCI_LINT_VERSION))
|
||||||
TB_GOLANGCI_LINT_VERSION ?= v1.64.5
|
|
||||||
# renovate: packageName=github.com/segmentio/golines
|
|
||||||
TB_GOLINES_VERSION ?= v0.12.2
|
|
||||||
# renovate: packageName=github.com/goreleaser/goreleaser/v2
|
# renovate: packageName=github.com/goreleaser/goreleaser/v2
|
||||||
TB_GORELEASER_VERSION ?= v2.7.0
|
TB_GORELEASER_VERSION ?= v2.12.0
|
||||||
# renovate: packageName=go.uber.org/mock/mockgen
|
TB_GORELEASER_VERSION_NUM ?= $(call STRIP_V,$(TB_GORELEASER_VERSION))
|
||||||
TB_MOCKGEN_VERSION ?= v0.5.0
|
# renovate: packageName=github.com/uber-go/mock
|
||||||
# renovate: packageName=github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
|
TB_MOCKGEN_VERSION ?= v0.6.0
|
||||||
TB_OAPI_CODEGEN_VERSION ?= v2.4.1
|
# renovate: packageName=github.com/oapi-codegen/oapi-codegen/v2
|
||||||
|
TB_OAPI_CODEGEN_VERSION ?= v2.5.0
|
||||||
# renovate: packageName=github.com/bakito/semver
|
# renovate: packageName=github.com/bakito/semver
|
||||||
TB_SEMVER_VERSION ?= v1.1.3
|
TB_SEMVER_VERSION ?= v1.1.7
|
||||||
|
TB_SEMVER_VERSION_NUM ?= $(call STRIP_V,$(TB_SEMVER_VERSION))
|
||||||
|
|
||||||
## Tool Installer
|
## Tool Installer
|
||||||
.PHONY: tb.deepcopy-gen
|
.PHONY: tb.controller-gen
|
||||||
tb.deepcopy-gen: $(TB_DEEPCOPY_GEN) ## Download deepcopy-gen locally if necessary.
|
tb.controller-gen: ## Download controller-gen locally if necessary.
|
||||||
$(TB_DEEPCOPY_GEN): $(TB_LOCALBIN)
|
@test -s $(TB_CONTROLLER_GEN) || \
|
||||||
test -s $(TB_LOCALBIN)/deepcopy-gen || GOBIN=$(TB_LOCALBIN) go install k8s.io/code-generator/cmd/deepcopy-gen@$(TB_DEEPCOPY_GEN_VERSION)
|
GOBIN=$(TB_LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(TB_CONTROLLER_GEN_VERSION)
|
||||||
.PHONY: tb.ginkgo
|
.PHONY: tb.ginkgo
|
||||||
tb.ginkgo: $(TB_GINKGO) ## Download ginkgo locally if necessary.
|
tb.ginkgo: ## Download ginkgo locally if necessary.
|
||||||
$(TB_GINKGO): $(TB_LOCALBIN)
|
@test -s $(TB_GINKGO) || \
|
||||||
test -s $(TB_LOCALBIN)/ginkgo || GOBIN=$(TB_LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
|
GOBIN=$(TB_LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
|
||||||
.PHONY: tb.gofumpt
|
|
||||||
tb.gofumpt: $(TB_GOFUMPT) ## Download gofumpt locally if necessary.
|
|
||||||
$(TB_GOFUMPT): $(TB_LOCALBIN)
|
|
||||||
test -s $(TB_LOCALBIN)/gofumpt || GOBIN=$(TB_LOCALBIN) go install mvdan.cc/gofumpt@$(TB_GOFUMPT_VERSION)
|
|
||||||
.PHONY: tb.golangci-lint
|
.PHONY: tb.golangci-lint
|
||||||
tb.golangci-lint: $(TB_GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
tb.golangci-lint: ## Download golangci-lint locally if necessary.
|
||||||
$(TB_GOLANGCI_LINT): $(TB_LOCALBIN)
|
@test -s $(TB_GOLANGCI_LINT) && $(TB_GOLANGCI_LINT) --version | grep -q $(TB_GOLANGCI_LINT_VERSION_NUM) || \
|
||||||
test -s $(TB_LOCALBIN)/golangci-lint || GOBIN=$(TB_LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(TB_GOLANGCI_LINT_VERSION)
|
GOBIN=$(TB_LOCALBIN) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(TB_GOLANGCI_LINT_VERSION)
|
||||||
.PHONY: tb.golines
|
|
||||||
tb.golines: $(TB_GOLINES) ## Download golines locally if necessary.
|
|
||||||
$(TB_GOLINES): $(TB_LOCALBIN)
|
|
||||||
test -s $(TB_LOCALBIN)/golines || GOBIN=$(TB_LOCALBIN) go install github.com/segmentio/golines@$(TB_GOLINES_VERSION)
|
|
||||||
.PHONY: tb.goreleaser
|
.PHONY: tb.goreleaser
|
||||||
tb.goreleaser: $(TB_GORELEASER) ## Download goreleaser locally if necessary.
|
tb.goreleaser: ## Download goreleaser locally if necessary.
|
||||||
$(TB_GORELEASER): $(TB_LOCALBIN)
|
@test -s $(TB_GORELEASER) && $(TB_GORELEASER) --version | grep -q $(TB_GORELEASER_VERSION_NUM) || \
|
||||||
test -s $(TB_LOCALBIN)/goreleaser || GOBIN=$(TB_LOCALBIN) go install github.com/goreleaser/goreleaser/v2@$(TB_GORELEASER_VERSION)
|
GOBIN=$(TB_LOCALBIN) go install github.com/goreleaser/goreleaser/v2@$(TB_GORELEASER_VERSION)
|
||||||
.PHONY: tb.mockgen
|
.PHONY: tb.mockgen
|
||||||
tb.mockgen: $(TB_MOCKGEN) ## Download mockgen locally if necessary.
|
tb.mockgen: ## Download mockgen locally if necessary.
|
||||||
$(TB_MOCKGEN): $(TB_LOCALBIN)
|
@test -s $(TB_MOCKGEN) || \
|
||||||
test -s $(TB_LOCALBIN)/mockgen || GOBIN=$(TB_LOCALBIN) go install go.uber.org/mock/mockgen@$(TB_MOCKGEN_VERSION)
|
GOBIN=$(TB_LOCALBIN) go install go.uber.org/mock/mockgen@$(TB_MOCKGEN_VERSION)
|
||||||
.PHONY: tb.oapi-codegen
|
.PHONY: tb.oapi-codegen
|
||||||
tb.oapi-codegen: $(TB_OAPI_CODEGEN) ## Download oapi-codegen locally if necessary.
|
tb.oapi-codegen: ## Download oapi-codegen locally if necessary.
|
||||||
$(TB_OAPI_CODEGEN): $(TB_LOCALBIN)
|
@test -s $(TB_OAPI_CODEGEN) || \
|
||||||
test -s $(TB_LOCALBIN)/oapi-codegen || GOBIN=$(TB_LOCALBIN) go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@$(TB_OAPI_CODEGEN_VERSION)
|
GOBIN=$(TB_LOCALBIN) go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@$(TB_OAPI_CODEGEN_VERSION)
|
||||||
.PHONY: tb.semver
|
.PHONY: tb.semver
|
||||||
tb.semver: $(TB_SEMVER) ## Download semver locally if necessary.
|
tb.semver: ## Download semver locally if necessary.
|
||||||
$(TB_SEMVER): $(TB_LOCALBIN)
|
@test -s $(TB_SEMVER) && $(TB_SEMVER) -version | grep -q $(TB_SEMVER_VERSION_NUM) || \
|
||||||
test -s $(TB_LOCALBIN)/semver || GOBIN=$(TB_LOCALBIN) go install github.com/bakito/semver@$(TB_SEMVER_VERSION)
|
GOBIN=$(TB_LOCALBIN) go install github.com/bakito/semver@$(TB_SEMVER_VERSION)
|
||||||
|
|
||||||
## Reset Tools
|
## Reset Tools
|
||||||
.PHONY: tb.reset
|
.PHONY: tb.reset
|
||||||
tb.reset:
|
tb.reset:
|
||||||
@rm -f \
|
@rm -f \
|
||||||
$(TB_LOCALBIN)/deepcopy-gen \
|
$(TB_CONTROLLER_GEN) \
|
||||||
$(TB_LOCALBIN)/ginkgo \
|
$(TB_GINKGO) \
|
||||||
$(TB_LOCALBIN)/gofumpt \
|
$(TB_GOLANGCI_LINT) \
|
||||||
$(TB_LOCALBIN)/golangci-lint \
|
$(TB_GORELEASER) \
|
||||||
$(TB_LOCALBIN)/golines \
|
$(TB_MOCKGEN) \
|
||||||
$(TB_LOCALBIN)/goreleaser \
|
$(TB_OAPI_CODEGEN) \
|
||||||
$(TB_LOCALBIN)/mockgen \
|
$(TB_SEMVER)
|
||||||
$(TB_LOCALBIN)/oapi-codegen \
|
|
||||||
$(TB_LOCALBIN)/semver
|
|
||||||
|
|
||||||
## Update Tools
|
## Update Tools
|
||||||
.PHONY: tb.update
|
.PHONY: tb.update
|
||||||
tb.update: tb.reset
|
tb.update: tb.reset
|
||||||
toolbox makefile --renovate -f $(TB_LOCALDIR)/Makefile \
|
toolbox makefile --renovate -f $(TB_LOCALDIR)/Makefile \
|
||||||
k8s.io/code-generator/cmd/deepcopy-gen@github.com/kubernetes/code-generator \
|
sigs.k8s.io/controller-tools/cmd/controller-gen@github.com/kubernetes-sigs/controller-tools \
|
||||||
mvdan.cc/gofumpt@github.com/mvdan/gofumpt \
|
github.com/golangci/golangci-lint/v2/cmd/golangci-lint?--version \
|
||||||
github.com/golangci/golangci-lint/cmd/golangci-lint \
|
github.com/goreleaser/goreleaser/v2?--version \
|
||||||
github.com/segmentio/golines \
|
|
||||||
github.com/goreleaser/goreleaser/v2 \
|
|
||||||
go.uber.org/mock/mockgen@github.com/uber-go/mock \
|
go.uber.org/mock/mockgen@github.com/uber-go/mock \
|
||||||
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen \
|
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen \
|
||||||
github.com/bakito/semver
|
github.com/bakito/semver?-version
|
||||||
## toolbox - end
|
## toolbox - end
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.24-alpine AS builder
|
FROM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
|
|||||||
38
Makefile
38
Makefile
@@ -9,16 +9,21 @@ lint: tb.golangci-lint
|
|||||||
tidy:
|
tidy:
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|
||||||
generate: tb.deepcopy-gen
|
generate: model mocks deepcopy-gen
|
||||||
|
deepcopy-gen: tb.controller-gen
|
||||||
@mkdir -p ./tmp
|
@mkdir -p ./tmp
|
||||||
@touch ./tmp/deepcopy-gen-boilerplate.go.txt
|
@touch ./tmp/deepcopy-gen-boilerplate.go.txt
|
||||||
$(TB_DEEPCOPY_GEN) --go-header-file ./tmp/deepcopy-gen-boilerplate.go.txt --bounding-dirs ./pkg/types
|
$(TB_CONTROLLER_GEN) paths=./internal/types object
|
||||||
|
|
||||||
fmt: tb.golines tb.gofumpt
|
.PHONY: docs
|
||||||
$(TB_GOLINES) --base-formatter="$(TB_GOFUMPT)" --max-len=120 --write-output .
|
docs:
|
||||||
|
go run cmd/docs/main.go
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
test: generate fmt lint test-ci
|
test: generate lint test-ci
|
||||||
|
|
||||||
|
fuzz:
|
||||||
|
go test -fuzz=FuzzMask -v ./internal/types/ -fuzztime=60s
|
||||||
|
|
||||||
# Run ci tests
|
# Run ci tests
|
||||||
test-ci: mocks tidy tb.ginkgo
|
test-ci: mocks tidy tb.ginkgo
|
||||||
@@ -27,18 +32,19 @@ test-ci: mocks tidy tb.ginkgo
|
|||||||
go tool cover -func=coverage.out
|
go tool cover -func=coverage.out
|
||||||
|
|
||||||
mocks: tb.mockgen
|
mocks: tb.mockgen
|
||||||
$(TB_MOCKGEN) -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
|
$(TB_MOCKGEN) -package client -destination internal/mocks/client/mock.go github.com/bakito/adguardhome-sync/internal/client Client
|
||||||
$(TB_MOCKGEN) -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags
|
$(TB_MOCKGEN) -package client -destination internal/mocks/flags/mock.go github.com/bakito/adguardhome-sync/internal/config Flags
|
||||||
|
|
||||||
release: tb.semver tb.goreleaser
|
release: tb.semver tb.goreleaser
|
||||||
@version=$$($(TB_SEMVER)); \
|
@version=$$($(TB_SEMVER)); \
|
||||||
git tag -s $$version -m"Release $$version"
|
git tag -s $$version -m"Release $$version"
|
||||||
$(TB_GORELEASER) --clean
|
$(TB_GORELEASER) --clean --parallelism 2
|
||||||
|
|
||||||
test-release: tb.goreleaser
|
test-release: tb.goreleaser
|
||||||
$(TB_GORELEASER) --skip=publish --snapshot --clean
|
$(TB_GORELEASER) --skip=publish --snapshot --clean --parallelism 2
|
||||||
|
|
||||||
start-replica:
|
start-replica:
|
||||||
|
docker rm -f adguardhome-replica
|
||||||
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest
|
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest
|
||||||
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
||||||
|
|
||||||
@@ -46,6 +52,7 @@ copy-replica-config:
|
|||||||
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
|
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
|
||||||
|
|
||||||
start-replica2:
|
start-replica2:
|
||||||
|
docker rm -f adguardhome-replica2
|
||||||
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest
|
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest
|
||||||
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
||||||
|
|
||||||
@@ -68,14 +75,17 @@ kind-test:
|
|||||||
@./testdata/e2e/bin/install-chart.sh
|
@./testdata/e2e/bin/install-chart.sh
|
||||||
|
|
||||||
# renovate: packageName=AdguardTeam/AdGuardHome
|
# renovate: packageName=AdguardTeam/AdGuardHome
|
||||||
ADGUARD_HOME_VERSION ?= v0.107.57
|
ADGUARD_HOME_VERSION ?= v0.107.66
|
||||||
|
|
||||||
model: tb.oapi-codegen
|
model: tb.oapi-codegen
|
||||||
@mkdir -p tmp
|
@mkdir -p tmp
|
||||||
go run openapi/main.go $(ADGUARD_HOME_VERSION)
|
go run cmd/openapi/main.go $(ADGUARD_HOME_VERSION)
|
||||||
$(TB_OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go
|
$(TB_OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > internal/client/model/model_generated.go
|
||||||
|
|
||||||
model-diff:
|
model-diff:
|
||||||
go run openapi/main.go $(ADGUARD_HOME_VERSION)
|
go run cmd/openapi/main.go $(ADGUARD_HOME_VERSION)
|
||||||
go run openapi/main.go
|
go run cmd/openapi/main.go
|
||||||
diff tmp/schema.yaml tmp/schema-master.yaml
|
diff tmp/schema.yaml tmp/schema-master.yaml
|
||||||
|
|
||||||
|
zellij:
|
||||||
|
zellij -l ./testdata/test-layout.kdl
|
||||||
|
|||||||
162
README.md
162
README.md
@@ -3,7 +3,9 @@
|
|||||||
[](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
|
[](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
|
||||||
[](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
|
[](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
|
||||||
|
|
||||||
# AdGuardHome sync
|
|
||||||
|
|
||||||
|
# <img src="./media/adguardhome-sync.svg" alt="AdGuardHome sync" width="50"/> AdGuardHome sync
|
||||||
|
|
||||||
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
|
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
|
||||||
|
|
||||||
@@ -43,7 +45,7 @@ go install github.com/bakito/adguardhome-sync@latest
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Both the origin instance must be initially setup via the AdguardHome installation wizard.
|
Both the origin instance and replica(s) must be initially set up with AdguardHome via the AdguardHome installation wizard.
|
||||||
|
|
||||||
## Username / Password vs. Cookie
|
## Username / Password vs. Cookie
|
||||||
|
|
||||||
@@ -141,9 +143,8 @@ set REPLICA1_USERNAME=username
|
|||||||
set REPLICA1_PASSWORD=password
|
set REPLICA1_PASSWORD=password
|
||||||
# set REPLICA1_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
# set REPLICA1_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
|
||||||
set FEATURES_DHCP=false
|
set FEATURES_DHCP_SERVER_CONFIG=false
|
||||||
set FEATURES_DHCP_SERVERCONFIG=false
|
set FEATURES_DHCP_STATIC_LEASES=false
|
||||||
set FEATURES_DHCP_STATICLEASES=false
|
|
||||||
|
|
||||||
# run once
|
# run once
|
||||||
adguardhome-sync run
|
adguardhome-sync run
|
||||||
@@ -182,68 +183,70 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
```
|
```
|
||||||
|
|
||||||
### env
|
## Config via environment variables
|
||||||
|
|
||||||
```yaml
|
For Replicas replace `#` with the index number for the replica. E.g.: `REPLICA#_URL` -> `REPLICA1_URL`
|
||||||
---
|
<!-- env-doc-start -->
|
||||||
version: "2.1"
|
| Name | Type | Description |
|
||||||
services:
|
| :--- | ---- |:----------- |
|
||||||
adguardhome-sync:
|
| ORIGIN_URL (string) | string | URL of adguardhome instance |
|
||||||
image: ghcr.io/bakito/adguardhome-sync
|
| ORIGIN_WEB_URL (string) | string | Web URL of adguardhome instance |
|
||||||
container_name: adguardhome-sync
|
| ORIGIN_API_PATH (string) | string | API Path |
|
||||||
command: run
|
| ORIGIN_USERNAME (string) | string | Adguardhome username |
|
||||||
environment:
|
| ORIGIN_PASSWORD (string) | string | Adguardhome password |
|
||||||
LOG_LEVEL: "info"
|
| ORIGIN_COOKIE (string) | string | Adguardhome cookie |
|
||||||
ORIGIN_URL: "https://192.168.1.2:3000"
|
| ORIGIN_REQUEST_HEADERS (map) | map | Request Headers 'key1:value1,key2:value2' |
|
||||||
# ORIGIN_WEB_URL: "https://some-other.url" # used in the web interface (default: <origin-url>
|
| ORIGIN_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
|
||||||
|
| ORIGIN_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
|
||||||
|
| ORIGIN_INTERFACE_NAME (string) | string | Network interface name |
|
||||||
|
| ORIGIN_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
|
||||||
|
| REPLICA#_URL (string) | string | URL of adguardhome instance |
|
||||||
|
| REPLICA#_WEB_URL (string) | string | Web URL of adguardhome instance |
|
||||||
|
| REPLICA#_API_PATH (string) | string | API Path |
|
||||||
|
| REPLICA#_USERNAME (string) | string | Adguardhome username |
|
||||||
|
| REPLICA#_PASSWORD (string) | string | Adguardhome password |
|
||||||
|
| REPLICA#_COOKIE (string) | string | Adguardhome cookie |
|
||||||
|
| REPLICA#_REQUEST_HEADERS (map) | map | Request Headers 'key1:value1,key2:value2' |
|
||||||
|
| REPLICA#_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
|
||||||
|
| REPLICA#_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
|
||||||
|
| REPLICA#_INTERFACE_NAME (string) | string | Network interface name |
|
||||||
|
| REPLICA#_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
|
||||||
|
| CRON (string) | string | Cron expression for the sync interval |
|
||||||
|
| RUN_ON_START (bool) | bool | Run the sync on startup |
|
||||||
|
| PRINT_CONFIG_ONLY (bool) | bool | Print current config only and stop the application |
|
||||||
|
| CONTINUE_ON_ERROR (bool) | bool | Continue sync on errors |
|
||||||
|
| API_PORT (int) | int | API port (API is disabled if port is set to 0) |
|
||||||
|
| API_USERNAME (string) | string | API username |
|
||||||
|
| API_PASSWORD (string) | string | API password |
|
||||||
|
| API_DARK_MODE (bool) | bool | API dark mode |
|
||||||
|
| API_METRICS_ENABLED (bool) | bool | Enable metrics |
|
||||||
|
| API_METRICS_SCRAPE_INTERVAL (int64) | int64 | Interval for metrics scraping |
|
||||||
|
| API_METRICS_QUERY_LOG_LIMIT (int) | int | Metrics log query limit |
|
||||||
|
| API_TLS_CERT_DIR (string) | string | API TLS certificate directory |
|
||||||
|
| API_TLS_CERT_NAME (string) | string | API TLS certificate file name |
|
||||||
|
| API_TLS_KEY_NAME (string) | string | API TLS key file name |
|
||||||
|
| FEATURES_DNS_ACCESS_LISTS (bool) | bool | Sync DNS access lists |
|
||||||
|
| FEATURES_DNS_SERVER_CONFIG (bool) | bool | Sync DNS server config |
|
||||||
|
| FEATURES_DNS_REWRITES (bool) | bool | Sync DNS rewrites |
|
||||||
|
| FEATURES_DHCP_SERVER_CONFIG (bool) | bool | Sync DHCP server config |
|
||||||
|
| FEATURES_DHCP_STATIC_LEASES (bool) | bool | Sync DHCP static leases |
|
||||||
|
| FEATURES_GENERAL_SETTINGS (bool) | bool | Sync general settings |
|
||||||
|
| FEATURES_QUERY_LOG_CONFIG (bool) | bool | Sync query log config |
|
||||||
|
| FEATURES_STATS_CONFIG (bool) | bool | Sync stats config |
|
||||||
|
| FEATURES_CLIENT_SETTINGS (bool) | bool | Sync client settings |
|
||||||
|
| FEATURES_SERVICES (bool) | bool | Sync services |
|
||||||
|
| FEATURES_FILTERS (bool) | bool | Sync filters |
|
||||||
|
| FEATURES_THEME (bool) | bool | Sync the web UI theme |
|
||||||
|
| FEATURES_TLS_CONFIG (bool) | bool | Sync the TLS config |
|
||||||
|
<!-- env-doc-end -->
|
||||||
|
|
||||||
ORIGIN_USERNAME: "username"
|
### Unraid
|
||||||
ORIGIN_PASSWORD: "password"
|
|
||||||
REPLICA1_URL: "http://192.168.1.3"
|
|
||||||
REPLICA1_USERNAME: "username"
|
|
||||||
REPLICA1_PASSWORD: "password"
|
|
||||||
REPLICA2_URL: "http://192.168.1.4"
|
|
||||||
REPLICA2_USERNAME: "username"
|
|
||||||
REPLICA2_PASSWORD: "password"
|
|
||||||
REPLICA2_API_PATH: "/some/path/control"
|
|
||||||
# REPLICA2_WEB_URL: "https://some-other.url" # used in the web interface (default: <replica-url>
|
|
||||||
# REPLICA2_AUTO_SETUP: true # if true, AdGuardHome is automatically initialized.
|
|
||||||
# REPLICA2_INTERFACE_NAME: 'ens18' # use custom dhcp interface name
|
|
||||||
# REPLICA2_DHCP_SERVER_ENABLED: true/false (optional) enables/disables the dhcp server on the replica
|
|
||||||
CRON: "0 */2 * * *" # run every 2 hours
|
|
||||||
RUN_ON_START: "true"
|
|
||||||
# CONTINUE_ON_ERROR: false # If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
|
|
||||||
|
|
||||||
# Configure the sync API server, disabled if api port is 0
|
⚠️ Disclaimer: Tere exists an unraid tepmlate for this application. This template is not managed by this project.
|
||||||
API_PORT: 8080
|
Also, as unraid is not known to me, I can not give any support on unraind templates.
|
||||||
# API_DARK_MODE: "true"
|
|
||||||
# API_USERNAME: admin
|
|
||||||
# API_PASSWORD: secret
|
|
||||||
# the directory of the provided tls certs
|
|
||||||
# API_TLS_CERT_DIR: /path/to/certs
|
|
||||||
# the name of the cert file (default: tls.crt)
|
|
||||||
# API_TLS_CERT_NAME: foo.crt
|
|
||||||
# the name of the key file (default: tls.key)
|
|
||||||
# API_TLS_KEY_NAME: bar.key
|
|
||||||
# API_METRICS_ENABLED: "true"
|
|
||||||
|
|
||||||
# Configure sync features; by default all features are enabled.
|
Note when running the Docker container in Unraid please remove unneeded env variables if don't needed.
|
||||||
# FEATURES_GENERAL_SETTINGS: "true"
|
If replica2 isn't used this can cause sync errors.
|
||||||
# FEATURES_QUERY_LOG_CONFIG: "true"
|
|
||||||
# FEATURES_STATS_CONFIG: "true"
|
|
||||||
# FEATURES_CLIENT_SETTINGS: "true"
|
|
||||||
# FEATURES_SERVICES: "true"
|
|
||||||
# FEATURES_FILTERS: "true"
|
|
||||||
# FEATURES_DHCP_SERVER_CONFIG: "true"
|
|
||||||
# FEATURES_DHCP_STATIC_LEASES: "true"
|
|
||||||
# FEATURES_DNS_SERVER_CONFIG: "true"
|
|
||||||
# FEATURES_DNS_ACCESS_LISTS: "true"
|
|
||||||
# FEATURES_DNS_REWRITES: "true"
|
|
||||||
# FEATURES_THEME: "true" # if false the UI theme is not synced
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
### Config file
|
### Config file
|
||||||
|
|
||||||
@@ -267,6 +270,8 @@ origin:
|
|||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
# requestHeaders: # Additional request headers
|
||||||
|
# AAA: bbb
|
||||||
|
|
||||||
# replicas instances
|
# replicas instances
|
||||||
replicas:
|
replicas:
|
||||||
@@ -281,6 +286,8 @@ replicas:
|
|||||||
# cookie: Replica2-Cookie-Name=CCCOOOKKKIIIEEE
|
# cookie: Replica2-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
# autoSetup: true # if true, AdGuardHome is automatically initialized.
|
# autoSetup: true # if true, AdGuardHome is automatically initialized.
|
||||||
# webURL: "https://some-other.url" # used in the web interface (default: <replica-url>
|
# webURL: "https://some-other.url" # used in the web interface (default: <replica-url>
|
||||||
|
# requestHeaders: # Additional request headers
|
||||||
|
# AAA: bbb
|
||||||
|
|
||||||
# Configure the sync API server, disabled if api port is 0
|
# Configure the sync API server, disabled if api port is 0
|
||||||
api:
|
api:
|
||||||
@@ -294,9 +301,9 @@ api:
|
|||||||
|
|
||||||
# enable metrics on path '/metrics' (api port must be != 0)
|
# enable metrics on path '/metrics' (api port must be != 0)
|
||||||
# metrics:
|
# metrics:
|
||||||
# enabled: true
|
# enabled: true
|
||||||
# scrapeInterval: 30s
|
# scrapeInterval: 30s
|
||||||
# queryLogLimit: 10000
|
# queryLogLimit: 10000
|
||||||
|
|
||||||
# enable tls for the api server
|
# enable tls for the api server
|
||||||
# tls:
|
# tls:
|
||||||
@@ -324,6 +331,29 @@ features:
|
|||||||
rewrites: true
|
rewrites: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Home Assistant AdGuard Home Add-on users
|
||||||
|
|
||||||
|
To enable syncing with a Home Assistant instance using
|
||||||
|
the [AdGuard Home Add-on](https://github.com/hassio-addons/addon-adguard-home), you will need to enable the disabled
|
||||||
|
ports, under the Network heading
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
And then set the port of your choice for the Web interface
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Don't forget to save and restart the add-on.
|
||||||
|
|
||||||
|
Depending on your setup, you may also need to disable SSL for the add-on.
|
||||||
|
|
||||||
|
The username:password required for the Home Assistant replica is the one you use to login to your instance, however it's
|
||||||
|
recommended to setup a new local only user with minimal permissions.
|
||||||
|
|
||||||
|
All credit for this method goes to [Brunty](https://github.com/brunty) who has a far
|
||||||
|
more [detailed write up](https://brunty.me/post/replicate-adguard-home-settings-into-home-assistant-adguard-home-addon/)
|
||||||
|
about this on his blog.
|
||||||
|
|
||||||
## Log Level
|
## Log Level
|
||||||
|
|
||||||
The log level can be set with the environment variable: `LOG_LEVEL`
|
The log level can be set with the environment variable: `LOG_LEVEL`
|
||||||
|
|||||||
96
cmd/docs/main.go
Normal file
96
cmd/docs/main.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// Print the available environment variables
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Read the README.md file
|
||||||
|
content, err := os.ReadFile("README.md")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to string for easier manipulation
|
||||||
|
fileContent := string(content)
|
||||||
|
|
||||||
|
// Generate the environment variables documentation
|
||||||
|
var buf strings.Builder
|
||||||
|
_, _ = buf.WriteString("| Name | Type | Description |\n")
|
||||||
|
_, _ = buf.WriteString("| :--- | ---- |:----------- |\n")
|
||||||
|
printEnvTags(&buf, reflect.TypeOf(types.Config{}), "")
|
||||||
|
|
||||||
|
// Find the markers and replace content between them
|
||||||
|
startMarker := "<!-- env-doc-start -->"
|
||||||
|
endMarker := "<!-- env-doc-end -->"
|
||||||
|
|
||||||
|
start := strings.Index(fileContent, startMarker)
|
||||||
|
end := strings.Index(fileContent, endMarker)
|
||||||
|
|
||||||
|
if start == -1 || end == -1 {
|
||||||
|
log.Fatal("Could not find markers in README.md")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct new content
|
||||||
|
newContent := fileContent[:start+len(startMarker)] + "\n" + buf.String() + fileContent[end:]
|
||||||
|
|
||||||
|
// Write back to README.md
|
||||||
|
err = os.WriteFile("README.md", []byte(newContent), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printEnvTags recursively prints all fields with `env` tags.
|
||||||
|
func printEnvTags(w io.Writer, t reflect.Type, prefix string) {
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field := range reflect.VisibleFields(t) {
|
||||||
|
if field.PkgPath != "" { // unexported field
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
envTag := field.Tag.Get("env")
|
||||||
|
if envTag == "" {
|
||||||
|
switch field.Name {
|
||||||
|
case "Origin":
|
||||||
|
envTag = "ORIGIN"
|
||||||
|
case "Replica":
|
||||||
|
envTag = "REPLICA#"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
combinedTag := envTag
|
||||||
|
if prefix != "" && envTag != "" {
|
||||||
|
combinedTag = prefix + "_" + envTag
|
||||||
|
} else if prefix != "" {
|
||||||
|
combinedTag = prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
ft := field.Type
|
||||||
|
if ft.Kind() == reflect.Ptr {
|
||||||
|
ft = ft.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ft.Kind() == reflect.Struct && ft.Name() != "Time" { // skip time.Time
|
||||||
|
printEnvTags(w, ft, strings.TrimSuffix(combinedTag, "_"))
|
||||||
|
} else if envTag != "" {
|
||||||
|
envVar := strings.Trim(combinedTag, "_") + " (" + ft.Kind().String() + ")"
|
||||||
|
docs := field.Tag.Get("documentation")
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", envVar, ft.Kind().String(), docs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@@ -20,21 +21,33 @@ func main() {
|
|||||||
}
|
}
|
||||||
log.Printf("Patching schema version %s\n", version)
|
log.Printf("Patching schema version %s\n", version)
|
||||||
|
|
||||||
resp, err := http.Get(
|
ctx := context.Background() // Or use context.WithTimeout
|
||||||
|
req, err := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
fmt.Sprintf("https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/%s/openapi/openapi.yaml", version),
|
fmt.Sprintf("https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/%s/openapi/openapi.yaml", version),
|
||||||
|
http.NoBody,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
schema := make(map[string]interface{})
|
schema := make(map[string]any)
|
||||||
err = yaml.Unmarshal(data, &schema)
|
err = yaml.Unmarshal(data, &schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if requestBodies, ok, _ := unstructured.NestedMap(schema, "components", "requestBodies"); ok {
|
if requestBodies, ok, _ := unstructured.NestedMap(schema, "components", "requestBodies"); ok {
|
||||||
@@ -47,19 +60,47 @@ func main() {
|
|||||||
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); ok {
|
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); ok {
|
||||||
if allOf, ok, _ := unstructured.NestedSlice(dnsInfo, "allOf"); ok && len(allOf) == 2 {
|
if allOf, ok, _ := unstructured.NestedSlice(dnsInfo, "allOf"); ok && len(allOf) == 2 {
|
||||||
delete(dnsInfo, "allOf")
|
delete(dnsInfo, "allOf")
|
||||||
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]interface{}),
|
//nolint:forcetypeassert
|
||||||
|
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]any),
|
||||||
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); err != nil {
|
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); err != nil {
|
||||||
log.Fatalln(err)
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
correctEntries(schema)
|
||||||
|
|
||||||
|
addFakeTags(schema)
|
||||||
|
|
||||||
b, err := yaml.Marshal(&schema)
|
b, err := yaml.Marshal(&schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
log.Printf("Writing schema file tmp/%s", fileName)
|
log.Printf("Writing schema file tmp/%s", fileName)
|
||||||
err = os.WriteFile("tmp/"+fileName, b, 0o600)
|
err = os.WriteFile("tmp/"+fileName, b, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func correctEntries(schema map[string]any) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFakeTags(schema map[string]any) {
|
||||||
|
fake := map[string]any{"faker": `slice_len=24`}
|
||||||
|
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "blocked_filtering", "x-oapi-codegen-extra-tags"); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "dns_queries", "x-oapi-codegen-extra-tags"); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_parental", "x-oapi-codegen-extra-tags"); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_safebrowsing", "x-oapi-codegen-extra-tags"); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed print-config.md
|
|
||||||
var printConfigTemplate string
|
|
||||||
|
|
||||||
func printConfig(cfg *types.Config, usedCfgFile string, cfgContent string) error {
|
|
||||||
config, err := yaml.Marshal(cfg)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := template.New("printConfigTemplate").Parse(printConfigTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
env := os.Environ()
|
|
||||||
sort.Strings(env)
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
if err = t.Execute(&buf, map[string]interface{}{
|
|
||||||
"AggregatedConfig": string(config),
|
|
||||||
"ConfigFilePath": usedCfgFile,
|
|
||||||
"ConfigFileContent": cfgContent,
|
|
||||||
"EnvironmentVariables": strings.Join(env, "\n"),
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Infof(
|
|
||||||
"Printing adguardhome-sync aggregated config (THE APPLICATION WILL NOT START IN THIS MODE):\n%s",
|
|
||||||
buf.String(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<!-- PLEASE COPY THE FOLLOWING OUTPUT AS IS INTO THE GITHUB ISSUE (Don't forget to mask your usernames, passwords and IPs when using this in an issue ) -->
|
|
||||||
|
|
||||||
### AdGuardHome sync aggregated config
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
{{ .AggregatedConfig }}
|
|
||||||
```
|
|
||||||
{{- if .ConfigFilePath }}
|
|
||||||
### AdGuardHome sync unmodified config file
|
|
||||||
|
|
||||||
Config file path: {{ .ConfigFilePath }}
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
{{ .ConfigFileContent }}
|
|
||||||
```
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
```ini
|
|
||||||
{{ .EnvironmentVariables }}
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- END OF GITHUB ISSUE CONTENT -->
|
|
||||||
@@ -4,9 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/version"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -14,7 +15,7 @@ var (
|
|||||||
logger = log.GetLogger("root")
|
logger = log.GetLogger("root")
|
||||||
)
|
)
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
// rootCmd represents the base command when called without any subcommands.
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "adguardhome-sync",
|
Use: "adguardhome-sync",
|
||||||
Short: "Synchronize config from one AdGuardHome instance to another",
|
Short: "Synchronize config from one AdGuardHome instance to another",
|
||||||
@@ -25,7 +26,7 @@ var rootCmd = &cobra.Command{
|
|||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
func Execute() {
|
func Execute() {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
_, _ = fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
cmd/run.go
41
cmd/run.go
@@ -1,20 +1,21 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bakito/adguardhome-sync/pkg/config"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/sync"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/config"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// runCmd represents the run command
|
// runCmd represents the run command.
|
||||||
var doCmd = &cobra.Command{
|
var doCmd = &cobra.Command{
|
||||||
Use: "run",
|
Use: "run",
|
||||||
Short: "Start a synchronisation from origin to replica",
|
Short: "Start a synchronization from origin to replica",
|
||||||
Long: `Synchronizes the configuration form an origin instance to a replica`,
|
Long: `Synchronizes the configuration form an origin instance to a replica`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
logger = log.GetLogger("run")
|
logger = log.GetLogger("run")
|
||||||
cfg, usedCfgFile, cfgContent, err := config.Get(cfgFile, cmd.Flags())
|
cfg, err := config.Get(cfgFile, cmd.Flags())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return err
|
return err
|
||||||
@@ -25,9 +26,8 @@ var doCmd = &cobra.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.PrintConfigOnly {
|
if cfg.PrintConfigOnly() {
|
||||||
if err := printConfig(cfg, usedCfgFile, cfgContent); err != nil {
|
if err := cfg.Print(); err != nil {
|
||||||
|
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ var doCmd = &cobra.Command{
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return sync.Sync(cfg)
|
return sync.Sync(cfg.Get())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,21 +45,21 @@ func init() {
|
|||||||
doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.")
|
doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.")
|
||||||
doCmd.PersistentFlags().Bool(config.FlagPrintConfigOnly, false, "Prints the configuration only and exists. "+
|
doCmd.PersistentFlags().Bool(config.FlagPrintConfigOnly, false, "Prints the configuration only and exists. "+
|
||||||
"Can be used to debug the config E.g: when having authentication issues.")
|
"Can be used to debug the config E.g: when having authentication issues.")
|
||||||
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronisation task "+
|
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronization task "+
|
||||||
"will not fail on single errors, but will log the errors and continue.")
|
"will not fail on single errors, but will log the errors and continue.")
|
||||||
|
|
||||||
doCmd.PersistentFlags().
|
doCmd.PersistentFlags().
|
||||||
Int(config.FlagApiPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
|
Int(config.FlagAPIPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
|
||||||
doCmd.PersistentFlags().String(config.FlagApiUsername, "", "Sync API username")
|
doCmd.PersistentFlags().String(config.FlagAPIUsername, "", "Sync API username")
|
||||||
doCmd.PersistentFlags().String(config.FlagApiPassword, "", "Sync API password")
|
doCmd.PersistentFlags().String(config.FlagAPIPassword, "", "Sync API password")
|
||||||
doCmd.PersistentFlags().String(config.FlagApiDarkMode, "", "API UI in dark mode")
|
doCmd.PersistentFlags().String(config.FlagAPIDarkMode, "", "API UI in dark mode")
|
||||||
|
|
||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature")
|
||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature")
|
||||||
|
|
||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsServerConfig, true, "Enable DNS server config feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDNSServerConfig, true, "Enable DNS server config feature")
|
||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsAccessLists, true, "Enable DNS server access lists feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDNSAccessLists, true, "Enable DNS server access lists feature")
|
||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsRewrites, true, "Enable DNS rewrites feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDNSRewrites, true, "Enable DNS rewrites feature")
|
||||||
|
|
||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureGeneral, true, "Enable general settings feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureGeneral, true, "Enable general settings feature")
|
||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureQueryLog, true, "Enable query log config feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureQueryLog, true, "Enable query log config feature")
|
||||||
@@ -67,11 +67,12 @@ func init() {
|
|||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureClient, true, "Enable client settings feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureClient, true, "Enable client settings feature")
|
||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureServices, true, "Enable services sync feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureServices, true, "Enable services sync feature")
|
||||||
doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature")
|
||||||
|
doCmd.PersistentFlags().Bool(config.FlagFeatureTLSConfig, false, "Enable TLS config sync feature")
|
||||||
|
|
||||||
doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url")
|
doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url")
|
||||||
doCmd.PersistentFlags().
|
doCmd.PersistentFlags().
|
||||||
String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
|
String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
|
||||||
doCmd.PersistentFlags().String(config.FlagOriginApiPath, "/control", "Origin instance API path")
|
doCmd.PersistentFlags().String(config.FlagOriginAPIPath, "/control", "Origin instance API path")
|
||||||
doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username")
|
doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username")
|
||||||
doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password")
|
doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password")
|
||||||
doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication")
|
doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication")
|
||||||
@@ -80,7 +81,7 @@ func init() {
|
|||||||
doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url")
|
doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url")
|
||||||
doCmd.PersistentFlags().
|
doCmd.PersistentFlags().
|
||||||
String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: <replica-url>)")
|
String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: <replica-url>)")
|
||||||
doCmd.PersistentFlags().String(config.FlagReplicaApiPath, "/control", "Replica instance API path")
|
doCmd.PersistentFlags().String(config.FlagReplicaAPIPath, "/control", "Replica instance API path")
|
||||||
doCmd.PersistentFlags().String(config.FlagReplicaUsername, "", "Replica instance username")
|
doCmd.PersistentFlags().String(config.FlagReplicaUsername, "", "Replica instance username")
|
||||||
doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password")
|
doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password")
|
||||||
doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication")
|
doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication")
|
||||||
|
|||||||
89
go.mod
89
go.mod
@@ -1,78 +1,79 @@
|
|||||||
module github.com/bakito/adguardhome-sync
|
module github.com/bakito/adguardhome-sync
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/caarlos0/env/v11 v11.3.1
|
github.com/caarlos0/env/v11 v11.3.1
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/go-faker/faker/v4 v4.6.0
|
github.com/go-faker/faker/v4 v4.6.2
|
||||||
github.com/go-resty/resty/v2 v2.16.5
|
github.com/go-resty/resty/v2 v2.16.5
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/oapi-codegen/runtime v1.1.1
|
github.com/oapi-codegen/runtime v1.1.2
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2
|
github.com/onsi/ginkgo/v2 v2.25.3
|
||||||
github.com/onsi/gomega v1.36.2
|
github.com/onsi/gomega v1.38.2
|
||||||
github.com/prometheus/client_golang v1.21.0
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.10.1
|
||||||
go.uber.org/mock v0.5.0
|
go.uber.org/mock v0.6.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/mod v0.23.0
|
golang.org/x/mod v0.28.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
k8s.io/apimachinery v0.32.2
|
k8s.io/apimachinery v0.34.1
|
||||||
k8s.io/utils v0.0.0-20241210054802-24370beab758
|
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
github.com/bytedance/sonic v1.13.2 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
|
||||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.62.0 // indirect
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.9 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/arch v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/tools v0.28.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.1 // indirect
|
golang.org/x/text v0.29.0 // indirect
|
||||||
|
golang.org/x/tools v0.36.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.8 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
202
go.sum
202
go.sum
@@ -1,64 +1,62 @@
|
|||||||
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||||
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-faker/faker/v4 v4.6.0 h1:6aOPzNptRiDwD14HuAnEtlTa+D1IfFuEHO8+vEFwjTs=
|
github.com/go-faker/faker/v4 v4.6.2 h1:IR1uQUYotFZnuTL7Iuy0FDGtHM5Rt1Q+2nipH9gnqKs=
|
||||||
github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0=
|
github.com/go-faker/faker/v4 v4.6.2/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
|
||||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
@@ -70,11 +68,11 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
|||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -89,54 +87,53 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
|
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
|
||||||
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
|
||||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
@@ -145,60 +142,64 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
|||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
|
||||||
|
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
@@ -207,17 +208,18 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
|
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
|
||||||
k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
|
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0=
|
||||||
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
|
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||||
|
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||||
|
|||||||
@@ -17,14 +17,18 @@ func (cl *client) doGet(req *resty.Request, url string) error {
|
|||||||
rl.Debug("do get")
|
rl.Debug("do get")
|
||||||
resp, err := req.Get(url)
|
resp, err := req.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp != nil && resp.StatusCode() == http.StatusFound {
|
l := rl
|
||||||
loc := resp.Header().Get("Location")
|
if resp != nil {
|
||||||
if loc == "/install.html" || loc == "/control/install.html" {
|
if resp.StatusCode() == http.StatusFound {
|
||||||
return ErrSetupNeeded
|
loc := resp.Header().Get("Location")
|
||||||
|
if loc == "/install.html" || loc == "/control/install.html" {
|
||||||
|
return ErrSetupNeeded
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
l = l.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
|
l.Debug("error in do get")
|
||||||
return detailedError(resp, err)
|
return detailedError(resp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,19 +11,33 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const envRedirectPolicyNoOfRedirects = "REDIRECT_POLICY_NO_OF_REDIRECTS"
|
const envRedirectPolicyNoOfRedirects = "REDIRECT_POLICY_NO_OF_REDIRECTS"
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
message string
|
||||||
|
errorCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Code() int {
|
||||||
|
return e.errorCode
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
l = log.GetLogger("client")
|
l = log.GetLogger("client")
|
||||||
// ErrSetupNeeded custom error
|
// ErrSetupNeeded custom error.
|
||||||
ErrSetupNeeded = errors.New("setup needed")
|
ErrSetupNeeded = errors.New("setup needed")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,16 +47,19 @@ func detailedError(resp *resty.Response, err error) error {
|
|||||||
e += fmt.Sprintf("(%s)", string(resp.Body()))
|
e += fmt.Sprintf("(%s)", string(resp.Body()))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e += fmt.Sprintf(": %s", err.Error())
|
e += ": " + err.Error()
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
message: e,
|
||||||
|
errorCode: resp.StatusCode(),
|
||||||
}
|
}
|
||||||
return errors.New(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create a new client
|
// New create a new client.
|
||||||
func New(config types.AdGuardInstance) (Client, error) {
|
func New(config types.AdGuardInstance) (Client, error) {
|
||||||
var apiURL string
|
var apiURL string
|
||||||
if config.APIPath == "" {
|
if config.APIPath == "" {
|
||||||
apiURL = fmt.Sprintf("%s/control", config.URL)
|
apiURL = config.URL + "/control"
|
||||||
} else {
|
} else {
|
||||||
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
|
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
|
||||||
}
|
}
|
||||||
@@ -51,7 +68,7 @@ func New(config types.AdGuardInstance) (Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
u.Path = path.Clean(u.Path)
|
u.Path = path.Clean(u.Path)
|
||||||
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true)
|
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true).SetHeaders(config.RequestHeaders)
|
||||||
|
|
||||||
// #nosec G402 has to be explicitly enabled
|
// #nosec G402 has to be explicitly enabled
|
||||||
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: config.InsecureSkipVerify})
|
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: config.InsecureSkipVerify})
|
||||||
@@ -84,7 +101,9 @@ func New(config types.AdGuardInstance) (Client, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client AdguardHome API client interface
|
// Client AdguardHome API client interface.
|
||||||
|
//
|
||||||
|
//nolint:interfacebloat
|
||||||
type Client interface {
|
type Client interface {
|
||||||
Host() string
|
Host() string
|
||||||
Status() (*model.ServerStatus, error)
|
Status() (*model.ServerStatus, error)
|
||||||
@@ -116,18 +135,20 @@ type Client interface {
|
|||||||
UpdateClient(client *model.Client) error
|
UpdateClient(client *model.Client) error
|
||||||
DeleteClient(client *model.Client) error
|
DeleteClient(client *model.Client) error
|
||||||
QueryLogConfig() (*model.QueryLogConfigWithIgnored, error)
|
QueryLogConfig() (*model.QueryLogConfigWithIgnored, error)
|
||||||
SetQueryLogConfig(*model.QueryLogConfigWithIgnored) error
|
SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error
|
||||||
StatsConfig() (*model.GetStatsConfigResponse, error)
|
StatsConfig() (*model.GetStatsConfigResponse, error)
|
||||||
SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error
|
SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error
|
||||||
Setup() error
|
Setup() error
|
||||||
AccessList() (*model.AccessList, error)
|
AccessList() (*model.AccessList, error)
|
||||||
SetAccessList(*model.AccessList) error
|
SetAccessList(accessList *model.AccessList) error
|
||||||
DNSConfig() (*model.DNSConfig, error)
|
DNSConfig() (*model.DNSConfig, error)
|
||||||
SetDNSConfig(*model.DNSConfig) error
|
SetDNSConfig(config *model.DNSConfig) error
|
||||||
DhcpConfig() (*model.DhcpStatus, error)
|
DhcpConfig() (*model.DhcpStatus, error)
|
||||||
SetDhcpConfig(*model.DhcpStatus) error
|
SetDhcpConfig(status *model.DhcpStatus) error
|
||||||
AddDHCPStaticLease(lease model.DhcpStaticLease) error
|
AddDHCPStaticLease(lease model.DhcpStaticLease) error
|
||||||
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
|
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
|
||||||
|
TLSConfig() (*model.TlsConfig, error)
|
||||||
|
SetTLSConfig(tls *model.TlsConfig) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
@@ -224,7 +245,7 @@ func (cl *client) toggleStatus(mode string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) toggleBool(mode string, enable bool) error {
|
func (cl *client) toggleBool(mode string, enable bool) error {
|
||||||
cl.log.With("enable", enable).Info(fmt.Sprintf("Toggle %s", mode))
|
cl.log.With("enable", enable).Info("Toggle " + mode)
|
||||||
var target string
|
var target string
|
||||||
if enable {
|
if enable {
|
||||||
target = "enable"
|
target = "enable"
|
||||||
@@ -447,3 +468,14 @@ func (cl *client) SetProfileInfo(profile *model.ProfileInfo) error {
|
|||||||
cl.log.With("language", profile.Language, "theme", profile.Theme).Info("Set profile")
|
cl.log.With("language", profile.Language, "theme", profile.Theme).Info("Set profile")
|
||||||
return cl.doPut(cl.client.R().EnableTrace().SetBody(profile), "/profile/update")
|
return cl.doPut(cl.client.R().EnableTrace().SetBody(profile), "/profile/update")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cl *client) TLSConfig() (*model.TlsConfig, error) {
|
||||||
|
tlsc := &model.TlsConfig{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(tlsc), "/tls/status")
|
||||||
|
return tlsc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetTLSConfig(tlsc *model.TlsConfig) error {
|
||||||
|
cl.log.With("enabled", tlsc.Enabled).Info("Set TLS config")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(tlsc), "/tls/configure")
|
||||||
|
}
|
||||||
@@ -8,13 +8,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -140,7 +141,7 @@ var _ = Describe("Client", func() {
|
|||||||
ts, cl = ClientPost(
|
ts, cl = ClientPost(
|
||||||
"/install/configure",
|
"/install/configure",
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
`{"web":{"ip":"0.0.0.0","port":3000,"status":"","can_autofix":false},"dns":{"ip":"0.0.0.0","port":53,"status":"","can_autofix":false},"username":"%s","password":"%s"}`,
|
`{"web":{"ip":"0.0.0.0","port":3000,"status":"","can_autofix":false},"dns":{"ip":"0.0.0.0","port":53,"status":"","can_autofix":false},"username":%q,"password":%q}`,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
),
|
),
|
||||||
@@ -373,10 +374,10 @@ var _ = Describe("Client", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
func ClientGet(file string, path string) (*httptest.Server, client.Client) {
|
func ClientGet(file, path string) (*httptest.Server, client.Client) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
||||||
b, err := os.ReadFile(filepath.Join("../../testdata", file))
|
b, err := os.ReadFile(filepath.Join("..", "..", "testdata", file))
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_, err = w.Write(b)
|
_, err = w.Write(b)
|
||||||
@@ -11,19 +11,20 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var l = log.GetLogger("client")
|
var l = log.GetLogger("client")
|
||||||
|
|
||||||
// New create a new api client
|
// New create a new api client.
|
||||||
func New(config types.AdGuardInstance) (Client, error) {
|
func New(config types.AdGuardInstance) (Client, error) {
|
||||||
var apiURL string
|
var apiURL string
|
||||||
if config.APIPath == "" {
|
if config.APIPath == "" {
|
||||||
apiURL = fmt.Sprintf("%s/control", config.URL)
|
apiURL = config.URL + "/control"
|
||||||
} else {
|
} else {
|
||||||
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
|
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,7 @@ func (a apiClient) SetFilteringConfig(ctx context.Context, config model.FilterCo
|
|||||||
return write(ctx, config, a.client.FilteringConfig)
|
return write(ctx, config, a.client.FilteringConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func write[B interface{}](
|
func write[B any](
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
body B,
|
body B,
|
||||||
req func(ctx context.Context, body B, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
req func(ctx context.Context, body B, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
||||||
@@ -112,7 +113,7 @@ func write[B interface{}](
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func read[I interface{}](
|
func read[I any](
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req func(ctx context.Context, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
req func(ctx context.Context, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
||||||
parse func(rsp *http.Response) (*I, error),
|
parse func(rsp *http.Response) (*I, error),
|
||||||
@@ -3,7 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
@@ -3,8 +3,9 @@ package client
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ model.HttpRequestDoer = &adapter{}
|
var _ model.HttpRequestDoer = &adapter{}
|
||||||
@@ -5,13 +5,14 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"k8s.io/utils/ptr"
|
"k8s.io/utils/ptr"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Clone the config
|
// Clone the config.
|
||||||
func (c *DhcpStatus) Clone() *DhcpStatus {
|
func (c *DhcpStatus) Clone() *DhcpStatus {
|
||||||
clone := &DhcpStatus{}
|
clone := &DhcpStatus{}
|
||||||
_ = copier.Copy(clone, c)
|
_ = copier.Copy(clone, c)
|
||||||
@@ -27,16 +28,16 @@ func (c *DhcpStatus) cleanV4V6() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance
|
// CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance.
|
||||||
func (c *DhcpStatus) CleanAndEquals(o *DhcpStatus) bool {
|
func (c *DhcpStatus) CleanAndEquals(o *DhcpStatus) bool {
|
||||||
c.cleanV4V6()
|
c.cleanV4V6()
|
||||||
o.cleanV4V6()
|
o.cleanV4V6()
|
||||||
return c.Equals(o)
|
return c.Equals(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals dhcp server config equal check
|
// Equals dhcp server config equal check.
|
||||||
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
|
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
|
||||||
return utils.JsonEquals(c, o)
|
return utils.JSONEquals(c, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DhcpStatus) HasConfig() bool {
|
func (c *DhcpStatus) HasConfig() bool {
|
||||||
@@ -56,8 +57,8 @@ func (j DhcpConfigV6) isValid() bool {
|
|||||||
|
|
||||||
type DhcpStaticLeases []DhcpStaticLease
|
type DhcpStaticLeases []DhcpStaticLease
|
||||||
|
|
||||||
// MergeDhcpStaticLeases the leases
|
// MergeDhcpStaticLeases the leases.
|
||||||
func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (DhcpStaticLeases, DhcpStaticLeases) {
|
func MergeDhcpStaticLeases(l, other *[]DhcpStaticLease) (adds, removes DhcpStaticLeases) {
|
||||||
var thisLeases []DhcpStaticLease
|
var thisLeases []DhcpStaticLease
|
||||||
var otherLeases []DhcpStaticLease
|
var otherLeases []DhcpStaticLease
|
||||||
|
|
||||||
@@ -69,8 +70,6 @@ func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (Dhcp
|
|||||||
}
|
}
|
||||||
current := make(map[string]DhcpStaticLease)
|
current := make(map[string]DhcpStaticLease)
|
||||||
|
|
||||||
var adds DhcpStaticLeases
|
|
||||||
var removes DhcpStaticLeases
|
|
||||||
for _, le := range thisLeases {
|
for _, le := range thisLeases {
|
||||||
current[le.Mac] = le
|
current[le.Mac] = le
|
||||||
}
|
}
|
||||||
@@ -90,21 +89,21 @@ func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (Dhcp
|
|||||||
return adds, removes
|
return adds, removes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals dns config equal check
|
// Equals dns config equal check.
|
||||||
func (c *DNSConfig) Equals(o *DNSConfig) bool {
|
func (c *DNSConfig) Equals(o *DNSConfig) bool {
|
||||||
cc := c.Clone()
|
cc := c.Clone()
|
||||||
oo := o.Clone()
|
oo := o.Clone()
|
||||||
cc.Sort()
|
cc.Sort()
|
||||||
oo.Sort()
|
oo.Sort()
|
||||||
|
|
||||||
return utils.JsonEquals(cc, oo)
|
return utils.JSONEquals(cc, oo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DNSConfig) Clone() *DNSConfig {
|
func (c *DNSConfig) Clone() *DNSConfig {
|
||||||
return utils.Clone(c, &DNSConfig{})
|
return utils.Clone(c, &DNSConfig{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort sort dns config
|
// Sort dns config.
|
||||||
func (c *DNSConfig) Sort() {
|
func (c *DNSConfig) Sort() {
|
||||||
if c.UpstreamDns != nil {
|
if c.UpstreamDns != nil {
|
||||||
sort.Strings(*c.UpstreamDns)
|
sort.Strings(*c.UpstreamDns)
|
||||||
@@ -119,14 +118,14 @@ func (c *DNSConfig) Sort() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals access list equal check
|
// Equals access list equal check.
|
||||||
func (al *AccessList) Equals(o *AccessList) bool {
|
func (al *AccessList) Equals(o *AccessList) bool {
|
||||||
return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) &&
|
return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) &&
|
||||||
EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) &&
|
EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) &&
|
||||||
EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true)
|
EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EqualsStringSlice(a *[]string, b *[]string, sortIt bool) bool {
|
func EqualsStringSlice(a, b *[]string, sortIt bool) bool {
|
||||||
if a == nil && b == nil {
|
if a == nil && b == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -152,7 +151,7 @@ func EqualsStringSlice(a *[]string, b *[]string, sortIt bool) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort clients
|
// Sort clients.
|
||||||
func (cl *Client) Sort() {
|
func (cl *Client) Sort() {
|
||||||
if cl.Ids != nil {
|
if cl.Ids != nil {
|
||||||
sort.Strings(*cl.Ids)
|
sort.Strings(*cl.Ids)
|
||||||
@@ -168,28 +167,26 @@ func (cl *Client) Sort() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareDiff timezone BlockedServicesSchedule might differ if all other fields are empty,
|
// PrepareDiff so we skip it in diff.
|
||||||
// so we skip it in diff
|
|
||||||
func (cl *Client) PrepareDiff() *string {
|
func (cl *Client) PrepareDiff() *string {
|
||||||
var tz *string
|
var tz *string
|
||||||
bss := cl.BlockedServicesSchedule
|
bss := cl.BlockedServicesSchedule
|
||||||
if bss != nil && bss.Mon == nil && bss.Tue == nil && bss.Wed == nil &&
|
if bss != nil && bss.Mon == nil && bss.Tue == nil && bss.Wed == nil &&
|
||||||
bss.Thu == nil && bss.Fri == nil && bss.Sat == nil && bss.Sun == nil {
|
bss.Thu == nil && bss.Fri == nil && bss.Sat == nil && bss.Sun == nil {
|
||||||
|
|
||||||
tz = cl.BlockedServicesSchedule.TimeZone
|
tz = cl.BlockedServicesSchedule.TimeZone
|
||||||
cl.BlockedServicesSchedule.TimeZone = nil
|
cl.BlockedServicesSchedule.TimeZone = nil
|
||||||
}
|
}
|
||||||
return tz
|
return tz
|
||||||
}
|
}
|
||||||
|
|
||||||
// AfterDiff reset after diff
|
// AfterDiff reset after diff.
|
||||||
func (cl *Client) AfterDiff(tz *string) {
|
func (cl *Client) AfterDiff(tz *string) {
|
||||||
if cl.BlockedServicesSchedule != nil {
|
if cl.BlockedServicesSchedule != nil {
|
||||||
cl.BlockedServicesSchedule.TimeZone = tz
|
cl.BlockedServicesSchedule.TimeZone = tz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals Clients equal check
|
// Equals Clients equal check.
|
||||||
func (cl *Client) Equals(o *Client) bool {
|
func (cl *Client) Equals(o *Client) bool {
|
||||||
cl.Sort()
|
cl.Sort()
|
||||||
o.Sort()
|
o.Sort()
|
||||||
@@ -202,10 +199,10 @@ func (cl *Client) Equals(o *Client) bool {
|
|||||||
o.AfterDiff(bssO)
|
o.AfterDiff(bssO)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return utils.JsonEquals(cl, o)
|
return utils.JSONEquals(cl, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add ac client
|
// Add ac client.
|
||||||
func (clients *Clients) Add(cl Client) {
|
func (clients *Clients) Add(cl Client) {
|
||||||
if clients.Clients == nil {
|
if clients.Clients == nil {
|
||||||
clients.Clients = &ClientsArray{cl}
|
clients.Clients = &ClientsArray{cl}
|
||||||
@@ -215,8 +212,8 @@ func (clients *Clients) Add(cl Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge merge Clients
|
// Merge merge Clients.
|
||||||
func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client) {
|
func (clients *Clients) Merge(other *Clients) (adds, removes, updates []*Client) {
|
||||||
current := make(map[string]*Client)
|
current := make(map[string]*Client)
|
||||||
if clients.Clients != nil {
|
if clients.Clients != nil {
|
||||||
cc := *clients.Clients
|
cc := *clients.Clients
|
||||||
@@ -233,10 +230,6 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var adds []*Client
|
|
||||||
var removes []*Client
|
|
||||||
var updates []*Client
|
|
||||||
|
|
||||||
for _, cl := range expected {
|
for _, cl := range expected {
|
||||||
if oc, ok := current[*cl.Name]; ok {
|
if oc, ok := current[*cl.Name]; ok {
|
||||||
if !cl.Equals(oc) {
|
if !cl.Equals(oc) {
|
||||||
@@ -255,7 +248,7 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
|
|||||||
return adds, updates, removes
|
return adds, updates, removes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key RewriteEntry key
|
// Key RewriteEntry key.
|
||||||
func (re *RewriteEntry) Key() string {
|
func (re *RewriteEntry) Key() string {
|
||||||
var d string
|
var d string
|
||||||
var a string
|
var a string
|
||||||
@@ -268,16 +261,13 @@ func (re *RewriteEntry) Key() string {
|
|||||||
return fmt.Sprintf("%s#%s", d, a)
|
return fmt.Sprintf("%s#%s", d, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RewriteEntries list of RewriteEntry
|
// RewriteEntries list of RewriteEntry.
|
||||||
type RewriteEntries []RewriteEntry
|
type RewriteEntries []RewriteEntry
|
||||||
|
|
||||||
// Merge RewriteEntries
|
// Merge RewriteEntries.
|
||||||
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, RewriteEntries, RewriteEntries) {
|
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (adds, removes, duplicates RewriteEntries) {
|
||||||
current := make(map[string]RewriteEntry)
|
current := make(map[string]RewriteEntry)
|
||||||
|
|
||||||
var adds RewriteEntries
|
|
||||||
var removes RewriteEntries
|
|
||||||
var duplicates RewriteEntries
|
|
||||||
processed := make(map[string]bool)
|
processed := make(map[string]bool)
|
||||||
for _, rr := range *rwe {
|
for _, rr := range *rwe {
|
||||||
if _, ok := processed[rr.Key()]; !ok {
|
if _, ok := processed[rr.Key()]; !ok {
|
||||||
@@ -310,16 +300,13 @@ func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, Rewrite
|
|||||||
return adds, removes, duplicates
|
return adds, removes, duplicates
|
||||||
}
|
}
|
||||||
|
|
||||||
func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter) {
|
func MergeFilters(this, other *[]Filter) (adds, updates, removes []Filter) {
|
||||||
if this == nil && other == nil {
|
if this == nil && other == nil {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
current := make(map[string]*Filter)
|
current := make(map[string]*Filter)
|
||||||
|
|
||||||
var adds []Filter
|
|
||||||
var updates []Filter
|
|
||||||
var removes []Filter
|
|
||||||
if this != nil {
|
if this != nil {
|
||||||
for _, fi := range *this {
|
for _, fi := range *this {
|
||||||
current[fi.Url] = &fi
|
current[fi.Url] = &fi
|
||||||
@@ -346,7 +333,7 @@ func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter
|
|||||||
return adds, updates, removes
|
return adds, updates, removes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals Filter equal check
|
// Equals Filter equal check.
|
||||||
func (f *Filter) Equals(o *Filter) bool {
|
func (f *Filter) Equals(o *Filter) bool {
|
||||||
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
|
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
|
||||||
}
|
}
|
||||||
@@ -358,17 +345,17 @@ type QueryLogConfigWithIgnored struct {
|
|||||||
Ignored []string `json:"ignored,omitempty"`
|
Ignored []string `json:"ignored,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals QueryLogConfig equal check
|
// Equals QueryLogConfig equal check.
|
||||||
func (qlc *QueryLogConfigWithIgnored) Equals(o *QueryLogConfigWithIgnored) bool {
|
func (qlc *QueryLogConfigWithIgnored) Equals(o *QueryLogConfigWithIgnored) bool {
|
||||||
return utils.JsonEquals(qlc, o)
|
return utils.JSONEquals(qlc, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals QueryLogConfigInterval equal check
|
// Equals QueryLogConfigInterval equal check.
|
||||||
func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool {
|
func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool {
|
||||||
return ptrEquals(qlc, o)
|
return ptrEquals(qlc, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ptrEquals[T comparable](a *T, b *T) bool {
|
func ptrEquals[T comparable](a, b *T) bool {
|
||||||
if a == nil && b == nil {
|
if a == nil && b == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -384,7 +371,7 @@ func ptrEquals[T comparable](a *T, b *T) bool {
|
|||||||
return aa == bb
|
return aa == bb
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableConfig API struct
|
// EnableConfig API struct.
|
||||||
type EnableConfig struct {
|
type EnableConfig struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
}
|
}
|
||||||
@@ -421,7 +408,7 @@ func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo, withTheme bool) *ProfileInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
|
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
|
||||||
return utils.JsonEquals(bss, o)
|
return utils.JSONEquals(bss, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bss *BlockedServicesSchedule) ServicesString() string {
|
func (bss *BlockedServicesSchedule) ServicesString() string {
|
||||||
@@ -449,9 +436,9 @@ func (c *DNSConfig) Sanitize(l *zap.SugaredLogger) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals GetStatsConfigResponse equal check
|
// Equals GetStatsConfigResponse equal check.
|
||||||
func (sc *GetStatsConfigResponse) Equals(o *GetStatsConfigResponse) bool {
|
func (sc *GetStatsConfigResponse) Equals(o *GetStatsConfigResponse) bool {
|
||||||
return utils.JsonEquals(sc, o)
|
return utils.JSONEquals(sc, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStats() *Stats {
|
func NewStats() *Stats {
|
||||||
@@ -482,19 +469,19 @@ func (s *Stats) Add(other *Stats) {
|
|||||||
s.ReplacedSafebrowsing = sumUp(s.ReplacedSafebrowsing, other.ReplacedSafebrowsing)
|
s.ReplacedSafebrowsing = sumUp(s.ReplacedSafebrowsing, other.ReplacedSafebrowsing)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addInt(t *int, add *int) *int {
|
func addInt(t, add *int) *int {
|
||||||
if add != nil {
|
if add != nil {
|
||||||
return ptr.To(*t + *add)
|
return ptr.To(*t + *add)
|
||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func sumUp(t *[]int, o *[]int) *[]int {
|
func sumUp(t, o *[]int) *[]int {
|
||||||
if o != nil {
|
if o != nil {
|
||||||
tt := *t
|
tt := *t
|
||||||
oo := *o
|
oo := *o
|
||||||
var sum []int
|
var sum []int
|
||||||
for i := 0; i < len(tt); i++ {
|
for i := range tt {
|
||||||
if len(oo) >= i {
|
if len(oo) >= i {
|
||||||
sum = append(sum, tt[i]+oo[i])
|
sum = append(sum, tt[i]+oo[i])
|
||||||
}
|
}
|
||||||
@@ -503,3 +490,7 @@ func sumUp(t *[]int, o *[]int) *[]int {
|
|||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *TlsConfig) Equals(config *TlsConfig) bool {
|
||||||
|
return utils.JSONEquals(c, config)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package model provides primitives to interact with the openapi HTTP API.
|
// Package model provides primitives to interact with the openapi HTTP API.
|
||||||
//
|
//
|
||||||
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
|
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.0 DO NOT EDIT.
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -91,6 +91,15 @@ const (
|
|||||||
QueryLogConfigIntervalN90 QueryLogConfigInterval = 90
|
QueryLogConfigIntervalN90 QueryLogConfigInterval = 90
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for QueryLogItemClientProto.
|
||||||
|
const (
|
||||||
|
Dnscrypt QueryLogItemClientProto = "dnscrypt"
|
||||||
|
Doh QueryLogItemClientProto = "doh"
|
||||||
|
Doq QueryLogItemClientProto = "doq"
|
||||||
|
Dot QueryLogItemClientProto = "dot"
|
||||||
|
Empty QueryLogItemClientProto = ""
|
||||||
|
)
|
||||||
|
|
||||||
// Defines values for QueryLogItemReason.
|
// Defines values for QueryLogItemReason.
|
||||||
const (
|
const (
|
||||||
QueryLogItemReasonFilteredBlackList QueryLogItemReason = "FilteredBlackList"
|
QueryLogItemReasonFilteredBlackList QueryLogItemReason = "FilteredBlackList"
|
||||||
@@ -294,7 +303,7 @@ type Client struct {
|
|||||||
// SafeSearch Safe search settings.
|
// SafeSearch Safe search settings.
|
||||||
SafeSearch *SafeSearchConfig `json:"safe_search,omitempty"`
|
SafeSearch *SafeSearchConfig `json:"safe_search,omitempty"`
|
||||||
SafebrowsingEnabled *bool `json:"safebrowsing_enabled,omitempty"`
|
SafebrowsingEnabled *bool `json:"safebrowsing_enabled,omitempty"`
|
||||||
// Deprecated:
|
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
|
||||||
SafesearchEnabled *bool `json:"safesearch_enabled,omitempty"`
|
SafesearchEnabled *bool `json:"safesearch_enabled,omitempty"`
|
||||||
Tags *[]string `json:"tags,omitempty"`
|
Tags *[]string `json:"tags,omitempty"`
|
||||||
Upstreams *[]string `json:"upstreams,omitempty"`
|
Upstreams *[]string `json:"upstreams,omitempty"`
|
||||||
@@ -360,7 +369,7 @@ type ClientFindSubEntry struct {
|
|||||||
// SafeSearch Safe search settings.
|
// SafeSearch Safe search settings.
|
||||||
SafeSearch *SafeSearchConfig `json:"safe_search,omitempty"`
|
SafeSearch *SafeSearchConfig `json:"safe_search,omitempty"`
|
||||||
SafebrowsingEnabled *bool `json:"safebrowsing_enabled,omitempty"`
|
SafebrowsingEnabled *bool `json:"safebrowsing_enabled,omitempty"`
|
||||||
// Deprecated:
|
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
|
||||||
SafesearchEnabled *bool `json:"safesearch_enabled,omitempty"`
|
SafesearchEnabled *bool `json:"safesearch_enabled,omitempty"`
|
||||||
Upstreams *[]string `json:"upstreams,omitempty"`
|
Upstreams *[]string `json:"upstreams,omitempty"`
|
||||||
UseGlobalBlockedServices *bool `json:"use_global_blocked_services,omitempty"`
|
UseGlobalBlockedServices *bool `json:"use_global_blocked_services,omitempty"`
|
||||||
@@ -417,16 +426,23 @@ type DNSConfig struct {
|
|||||||
BlockingMode *DNSConfigBlockingMode `json:"blocking_mode,omitempty"`
|
BlockingMode *DNSConfigBlockingMode `json:"blocking_mode,omitempty"`
|
||||||
|
|
||||||
// BootstrapDns Bootstrap servers, port is optional after colon. Empty value will reset it to default values.
|
// BootstrapDns Bootstrap servers, port is optional after colon. Empty value will reset it to default values.
|
||||||
BootstrapDns *[]string `json:"bootstrap_dns,omitempty"`
|
BootstrapDns *[]string `json:"bootstrap_dns,omitempty"`
|
||||||
CacheOptimistic *bool `json:"cache_optimistic,omitempty"`
|
|
||||||
CacheSize *int `json:"cache_size,omitempty"`
|
// CacheEnabled Enables or disables the DNS response cache.
|
||||||
CacheTtlMax *int `json:"cache_ttl_max,omitempty"`
|
//
|
||||||
CacheTtlMin *int `json:"cache_ttl_min,omitempty"`
|
// If `cache_enabled` is `true`, the companion field `cache_size` must
|
||||||
DisableIpv6 *bool `json:"disable_ipv6,omitempty"`
|
// be present and greater than 0, or the `dns.cache_size` setting in
|
||||||
DnssecEnabled *bool `json:"dnssec_enabled,omitempty"`
|
// the configuration file must already be greater than 0.
|
||||||
EdnsCsCustomIp *string `json:"edns_cs_custom_ip,omitempty"`
|
CacheEnabled *bool `json:"cache_enabled,omitempty"`
|
||||||
EdnsCsEnabled *bool `json:"edns_cs_enabled,omitempty"`
|
CacheOptimistic *bool `json:"cache_optimistic,omitempty"`
|
||||||
EdnsCsUseCustom *bool `json:"edns_cs_use_custom,omitempty"`
|
CacheSize *int `json:"cache_size,omitempty"`
|
||||||
|
CacheTtlMax *int `json:"cache_ttl_max,omitempty"`
|
||||||
|
CacheTtlMin *int `json:"cache_ttl_min,omitempty"`
|
||||||
|
DisableIpv6 *bool `json:"disable_ipv6,omitempty"`
|
||||||
|
DnssecEnabled *bool `json:"dnssec_enabled,omitempty"`
|
||||||
|
EdnsCsCustomIp *string `json:"edns_cs_custom_ip,omitempty"`
|
||||||
|
EdnsCsEnabled *bool `json:"edns_cs_enabled,omitempty"`
|
||||||
|
EdnsCsUseCustom *bool `json:"edns_cs_use_custom,omitempty"`
|
||||||
|
|
||||||
// FallbackDns List of fallback DNS servers used when upstream DNS servers are not responding. Empty value will clear the list.
|
// FallbackDns List of fallback DNS servers used when upstream DNS servers are not responding. Empty value will clear the list.
|
||||||
FallbackDns *[]string `json:"fallback_dns,omitempty"`
|
FallbackDns *[]string `json:"fallback_dns,omitempty"`
|
||||||
@@ -609,7 +625,7 @@ type FilterCheckHostResponse struct {
|
|||||||
|
|
||||||
// FilterId In case if there's a rule applied to this DNS request, this is ID of the filter list that the rule belongs to.
|
// FilterId In case if there's a rule applied to this DNS request, this is ID of the filter list that the rule belongs to.
|
||||||
// Deprecated: use `rules[*].filter_list_id` instead.
|
// Deprecated: use `rules[*].filter_list_id` instead.
|
||||||
// Deprecated:
|
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
|
||||||
FilterId *int `json:"filter_id,omitempty"`
|
FilterId *int `json:"filter_id,omitempty"`
|
||||||
|
|
||||||
// IpAddrs Set if reason=Rewrite
|
// IpAddrs Set if reason=Rewrite
|
||||||
@@ -620,7 +636,7 @@ type FilterCheckHostResponse struct {
|
|||||||
|
|
||||||
// Rule Filtering rule applied to the request (if any).
|
// Rule Filtering rule applied to the request (if any).
|
||||||
// Deprecated: use `rules[*].text` instead.
|
// Deprecated: use `rules[*].text` instead.
|
||||||
// Deprecated:
|
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
|
||||||
Rule *string `json:"rule,omitempty"`
|
Rule *string `json:"rule,omitempty"`
|
||||||
|
|
||||||
// Rules Applied rules.
|
// Rules Applied rules.
|
||||||
@@ -812,8 +828,8 @@ type QueryLogItem struct {
|
|||||||
ClientId *string `json:"client_id,omitempty"`
|
ClientId *string `json:"client_id,omitempty"`
|
||||||
|
|
||||||
// ClientInfo Client information for a query log item.
|
// ClientInfo Client information for a query log item.
|
||||||
ClientInfo *QueryLogItemClient `json:"client_info,omitempty"`
|
ClientInfo *QueryLogItemClient `json:"client_info,omitempty"`
|
||||||
ClientProto *interface{} `json:"client_proto,omitempty"`
|
ClientProto *QueryLogItemClientProto `json:"client_proto,omitempty"`
|
||||||
|
|
||||||
// Ecs The IP network defined by an EDNS Client-Subnet option in the request message if any.
|
// Ecs The IP network defined by an EDNS Client-Subnet option in the request message if any.
|
||||||
Ecs *string `json:"ecs,omitempty"`
|
Ecs *string `json:"ecs,omitempty"`
|
||||||
@@ -821,7 +837,7 @@ type QueryLogItem struct {
|
|||||||
|
|
||||||
// FilterId In case if there's a rule applied to this DNS request, this is ID of the filter list that the rule belongs to.
|
// FilterId In case if there's a rule applied to this DNS request, this is ID of the filter list that the rule belongs to.
|
||||||
// Deprecated: use `rules[*].filter_list_id` instead.
|
// Deprecated: use `rules[*].filter_list_id` instead.
|
||||||
// Deprecated:
|
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
|
||||||
FilterId *int `json:"filterId,omitempty"`
|
FilterId *int `json:"filterId,omitempty"`
|
||||||
|
|
||||||
// OriginalAnswer Answer from upstream server (optional)
|
// OriginalAnswer Answer from upstream server (optional)
|
||||||
@@ -835,7 +851,7 @@ type QueryLogItem struct {
|
|||||||
|
|
||||||
// Rule Filtering rule applied to the request (if any).
|
// Rule Filtering rule applied to the request (if any).
|
||||||
// Deprecated: use `rules[*].text` instead.
|
// Deprecated: use `rules[*].text` instead.
|
||||||
// Deprecated:
|
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
|
||||||
Rule *string `json:"rule,omitempty"`
|
Rule *string `json:"rule,omitempty"`
|
||||||
|
|
||||||
// Rules Applied rules.
|
// Rules Applied rules.
|
||||||
@@ -854,6 +870,9 @@ type QueryLogItem struct {
|
|||||||
Upstream *string `json:"upstream,omitempty"`
|
Upstream *string `json:"upstream,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryLogItemClientProto defines model for QueryLogItem.ClientProto.
|
||||||
|
type QueryLogItemClientProto string
|
||||||
|
|
||||||
// QueryLogItemReason Request filtering status.
|
// QueryLogItemReason Request filtering status.
|
||||||
type QueryLogItemReason string
|
type QueryLogItemReason string
|
||||||
|
|
||||||
@@ -989,8 +1008,8 @@ type SetRulesRequest struct {
|
|||||||
type Stats struct {
|
type Stats struct {
|
||||||
// AvgProcessingTime Average time in seconds on processing a DNS request
|
// AvgProcessingTime Average time in seconds on processing a DNS request
|
||||||
AvgProcessingTime *float32 `json:"avg_processing_time,omitempty"`
|
AvgProcessingTime *float32 `json:"avg_processing_time,omitempty"`
|
||||||
BlockedFiltering *[]int `json:"blocked_filtering,omitempty"`
|
BlockedFiltering *[]int `faker:"slice_len=24" json:"blocked_filtering,omitempty"`
|
||||||
DnsQueries *[]int `json:"dns_queries,omitempty"`
|
DnsQueries *[]int `faker:"slice_len=24" json:"dns_queries,omitempty"`
|
||||||
|
|
||||||
// NumBlockedFiltering Number of requests blocked by filtering rules
|
// NumBlockedFiltering Number of requests blocked by filtering rules
|
||||||
NumBlockedFiltering *int `json:"num_blocked_filtering,omitempty"`
|
NumBlockedFiltering *int `json:"num_blocked_filtering,omitempty"`
|
||||||
@@ -1006,8 +1025,8 @@ type Stats struct {
|
|||||||
|
|
||||||
// NumReplacedSafesearch Number of requests blocked by safesearch module
|
// NumReplacedSafesearch Number of requests blocked by safesearch module
|
||||||
NumReplacedSafesearch *int `json:"num_replaced_safesearch,omitempty"`
|
NumReplacedSafesearch *int `json:"num_replaced_safesearch,omitempty"`
|
||||||
ReplacedParental *[]int `json:"replaced_parental,omitempty"`
|
ReplacedParental *[]int `faker:"slice_len=24" json:"replaced_parental,omitempty"`
|
||||||
ReplacedSafebrowsing *[]int `json:"replaced_safebrowsing,omitempty"`
|
ReplacedSafebrowsing *[]int `faker:"slice_len=24" json:"replaced_safebrowsing,omitempty"`
|
||||||
|
|
||||||
// TimeUnits Time units
|
// TimeUnits Time units
|
||||||
TimeUnits *StatsTimeUnits `json:"time_units,omitempty"`
|
TimeUnits *StatsTimeUnits `json:"time_units,omitempty"`
|
||||||
@@ -1187,7 +1206,13 @@ type ClientsFindParams struct {
|
|||||||
// FilteringCheckHostParams defines parameters for FilteringCheckHost.
|
// FilteringCheckHostParams defines parameters for FilteringCheckHost.
|
||||||
type FilteringCheckHostParams struct {
|
type FilteringCheckHostParams struct {
|
||||||
// Name Filter by host name
|
// Name Filter by host name
|
||||||
Name *string `form:"name,omitempty" json:"name,omitempty"`
|
Name string `form:"name" json:"name"`
|
||||||
|
|
||||||
|
// Client Optional ClientID or client IP address
|
||||||
|
Client *string `form:"client,omitempty" json:"client,omitempty"`
|
||||||
|
|
||||||
|
// Qtype Optional DNS type
|
||||||
|
Qtype *string `form:"qtype,omitempty" json:"qtype,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryLogParams defines parameters for QueryLog.
|
// QueryLogParams defines parameters for QueryLog.
|
||||||
@@ -4244,9 +4269,37 @@ func NewFilteringCheckHostRequest(server string, params *FilteringCheckHostParam
|
|||||||
if params != nil {
|
if params != nil {
|
||||||
queryValues := queryURL.Query()
|
queryValues := queryURL.Query()
|
||||||
|
|
||||||
if params.Name != nil {
|
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
for k, v := range parsed {
|
||||||
|
for _, v2 := range v {
|
||||||
|
queryValues.Add(k, v2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, *params.Name); err != nil {
|
if params.Client != nil {
|
||||||
|
|
||||||
|
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "client", runtime.ParamLocationQuery, *params.Client); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
for k, v := range parsed {
|
||||||
|
for _, v2 := range v {
|
||||||
|
queryValues.Add(k, v2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Qtype != nil {
|
||||||
|
|
||||||
|
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "qtype", runtime.ParamLocationQuery, *params.Qtype); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
|
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
"github.com/onsi/gomega"
|
"github.com/onsi/gomega"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Types", func() {
|
var _ = Describe("Types", func() {
|
||||||
@@ -4,12 +4,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Types", func() {
|
var _ = Describe("Types", func() {
|
||||||
@@ -36,6 +36,9 @@
|
|||||||
"webURL": {
|
"webURL": {
|
||||||
"format": "uri",
|
"format": "uri",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requestHeaders": {
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -147,6 +150,9 @@
|
|||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"tlsConfig": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -4,9 +4,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/caarlos0/env/v11"
|
"github.com/caarlos0/env/v11"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -14,14 +15,32 @@ var (
|
|||||||
logger = log.GetLogger("config")
|
logger = log.GetLogger("config")
|
||||||
)
|
)
|
||||||
|
|
||||||
func Get(configFile string, flags Flags) (*types.Config, string, string, error) {
|
type AppConfig struct {
|
||||||
|
cfg *types.Config
|
||||||
|
filePath string
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AppConfig) PrintConfigOnly() bool {
|
||||||
|
return ac.cfg.PrintConfigOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AppConfig) Get() *types.Config {
|
||||||
|
return ac.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AppConfig) Init() error {
|
||||||
|
return ac.cfg.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(configFile string, flags Flags) (*AppConfig, error) {
|
||||||
path, err := configFilePath(configFile)
|
path, err := configFilePath(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateSchema(path); err != nil {
|
if err := validateSchema(path); err != nil {
|
||||||
return nil, "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := initialConfig()
|
cfg := initialConfig()
|
||||||
@@ -29,12 +48,12 @@ func Get(configFile string, flags Flags) (*types.Config, string, string, error)
|
|||||||
// read yaml config
|
// read yaml config
|
||||||
var content string
|
var content string
|
||||||
if content, err = readFile(cfg, path); err != nil {
|
if content, err = readFile(cfg, path); err != nil {
|
||||||
return nil, "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwrite from command flags
|
// overwrite from command flags
|
||||||
if err := readFlags(cfg, flags); err != nil {
|
if err := readFlags(cfg, flags); err != nil {
|
||||||
return nil, "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// *bool field creates issues when already not nil
|
// *bool field creates issues when already not nil
|
||||||
@@ -44,18 +63,27 @@ func Get(configFile string, flags Flags) (*types.Config, string, string, error)
|
|||||||
replicaDhcpServer := cfg.Replica.DHCPServerEnabled
|
replicaDhcpServer := cfg.Replica.DHCPServerEnabled
|
||||||
cfg.Replica.DHCPServerEnabled = nil
|
cfg.Replica.DHCPServerEnabled = nil
|
||||||
|
|
||||||
// ignore replicas form env parsing as they are handled separately
|
// ignore origin and replicas form env parsing as they are handled separately
|
||||||
replicas := cfg.Replicas
|
replicas := cfg.Replicas
|
||||||
cfg.Replicas = nil
|
cfg.Replicas = nil
|
||||||
|
replica := cfg.Replica
|
||||||
|
cfg.Replica = nil
|
||||||
|
origin := cfg.Origin
|
||||||
|
cfg.Origin = nil
|
||||||
|
|
||||||
// overwrite from env vars
|
// overwrite from env vars
|
||||||
if err = env.Parse(cfg); err != nil {
|
if err := env.Parse(cfg); err != nil {
|
||||||
return nil, "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = env.ParseWithOptions(cfg.Replica, env.Options{Prefix: "REPLICA_"}); err != nil {
|
if err := env.ParseWithOptions(origin, env.Options{Prefix: "ORIGIN_"}); err != nil {
|
||||||
return nil, "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
// restore the replica
|
if err := env.ParseWithOptions(replica, env.Options{Prefix: "REPLICA_"}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// restore origin and replica
|
||||||
|
cfg.Origin = origin
|
||||||
|
cfg.Replica = replica
|
||||||
cfg.Replicas = replicas
|
cfg.Replicas = replicas
|
||||||
|
|
||||||
// if not set from env, use previous value
|
// if not set from env, use previous value
|
||||||
@@ -63,10 +91,6 @@ func Get(configFile string, flags Flags) (*types.Config, string, string, error)
|
|||||||
cfg.Replica.DHCPServerEnabled = replicaDhcpServer
|
cfg.Replica.DHCPServerEnabled = replicaDhcpServer
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = env.ParseWithOptions(&cfg.Origin, env.Options{Prefix: "ORIGIN_"}); err != nil {
|
|
||||||
return nil, "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Replica != nil &&
|
if cfg.Replica != nil &&
|
||||||
cfg.Replica.URL == "" &&
|
cfg.Replica.URL == "" &&
|
||||||
cfg.Replica.Username == "" {
|
cfg.Replica.Username == "" {
|
||||||
@@ -74,12 +98,10 @@ func Get(configFile string, flags Flags) (*types.Config, string, string, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Replicas) > 0 && cfg.Replica != nil {
|
if len(cfg.Replicas) > 0 && cfg.Replica != nil {
|
||||||
return nil, "", "", errors.New("mixed replica config in use. " +
|
return nil, errors.New("mixed replica config in use. " +
|
||||||
"Do not use single replica and numbered (list) replica config combined")
|
"Do not use single replica and numbered (list) replica config combined")
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeprecatedEnvVars(cfg)
|
|
||||||
|
|
||||||
if cfg.Replica != nil {
|
if cfg.Replica != nil {
|
||||||
cfg.Replicas = []types.AdGuardInstance{*cfg.Replica}
|
cfg.Replicas = []types.AdGuardInstance{*cfg.Replica}
|
||||||
cfg.Replica = nil
|
cfg.Replica = nil
|
||||||
@@ -87,13 +109,13 @@ func Get(configFile string, flags Flags) (*types.Config, string, string, error)
|
|||||||
|
|
||||||
cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas)
|
cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas)
|
||||||
|
|
||||||
return cfg, path, content, err
|
return &AppConfig{cfg: cfg, filePath: path, content: content}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func initialConfig() *types.Config {
|
func initialConfig() *types.Config {
|
||||||
return &types.Config{
|
return &types.Config{
|
||||||
RunOnStart: true,
|
RunOnStart: true,
|
||||||
Origin: types.AdGuardInstance{
|
Origin: &types.AdGuardInstance{
|
||||||
APIPath: "/control",
|
APIPath: "/control",
|
||||||
},
|
},
|
||||||
Replica: &types.AdGuardInstance{
|
Replica: &types.AdGuardInstance{
|
||||||
@@ -5,9 +5,11 @@ import (
|
|||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCmd(t *testing.T) {
|
func TestCmd(t *testing.T) {
|
||||||
|
format.TruncatedDiff = false
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "Config Suite")
|
RunSpecs(t, "Config Suite")
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,12 @@ package config_test
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/config"
|
|
||||||
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
gm "go.uber.org/mock/gomock"
|
gm "go.uber.org/mock/gomock"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/config"
|
||||||
|
flagsmock "github.com/bakito/adguardhome-sync/internal/mocks/flags"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Config", func() {
|
var _ = Describe("Config", func() {
|
||||||
@@ -16,7 +17,7 @@ var _ = Describe("Config", func() {
|
|||||||
flags *flagsmock.MockFlags
|
flags *flagsmock.MockFlags
|
||||||
mockCtrl *gm.Controller
|
mockCtrl *gm.Controller
|
||||||
changedEnvVars []string
|
changedEnvVars []string
|
||||||
setEnv = func(name string, value string) {
|
setEnv = func(name, value string) {
|
||||||
_ = os.Setenv(name, value)
|
_ = os.Setenv(name, value)
|
||||||
changedEnvVars = append(changedEnvVars, name)
|
changedEnvVars = append(changedEnvVars, name)
|
||||||
}
|
}
|
||||||
@@ -38,29 +39,39 @@ var _ = Describe("Config", func() {
|
|||||||
It("should have the origin URL from the config file", func() {
|
It("should have the origin URL from the config file", func() {
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
_, _, _, err := config.Get("../../testdata/config_test_replicas_and_replica.yaml", flags)
|
_, err := config.Get("../../testdata/config_test_replicas_and_replica.yaml", flags)
|
||||||
Ω(err).Should(HaveOccurred())
|
Ω(err).Should(HaveOccurred())
|
||||||
Ω(err.Error()).Should(ContainSubstring("mixed replica config in use"))
|
Ω(err.Error()).Should(ContainSubstring("mixed replica config in use"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Context("Env Var Clash", func() {
|
||||||
|
It("should not use USERNAME env variable if it is defined (#570)", func() {
|
||||||
|
incorrect := "ThisIsNotTheCorrectUsername"
|
||||||
|
setEnv("USERNAME", incorrect)
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
c, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(c.Get().Origin.Username).ShouldNot(Equal(incorrect))
|
||||||
|
Ω(c.Get().Replicas[0].Username).ShouldNot(Equal(incorrect))
|
||||||
|
})
|
||||||
|
})
|
||||||
Context("Origin Url", func() {
|
Context("Origin Url", func() {
|
||||||
It("should have the origin URL from the config file", func() {
|
It("should have the origin URL from the config file", func() {
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
cfg, path, content, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Origin.URL).Should(Equal("https://origin-file:443"))
|
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-file:443"))
|
||||||
Ω(path).Should(Equal("../../testdata/config_test_replicas.yaml"))
|
|
||||||
Ω(content).ShouldNot(BeEmpty())
|
|
||||||
})
|
})
|
||||||
It("should have the origin URL from the config flags", func() {
|
It("should have the origin URL from the config flags", func() {
|
||||||
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
|
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
|
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Origin.URL).Should(Equal("https://origin-flag:443"))
|
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-flag:443"))
|
||||||
})
|
})
|
||||||
It("should have the origin URL from the config env var", func() {
|
It("should have the origin URL from the config env var", func() {
|
||||||
setEnv("ORIGIN_URL", "https://origin-env:443")
|
setEnv("ORIGIN_URL", "https://origin-env:443")
|
||||||
@@ -68,27 +79,27 @@ var _ = Describe("Config", func() {
|
|||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
|
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Origin.URL).Should(Equal("https://origin-env:443"))
|
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-env:443"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("Replica insecure skip verify", func() {
|
Context("Replica insecure skip verify", func() {
|
||||||
It("should have the insecure skip verify from the config file", func() {
|
It("should have the insecure skip verify from the config file", func() {
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
||||||
})
|
})
|
||||||
It("should have the insecure skip verify from the config flags", func() {
|
It("should have the insecure skip verify from the config flags", func() {
|
||||||
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
|
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
|
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue())
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
|
||||||
})
|
})
|
||||||
It("should have the insecure skip verify from the config env var", func() {
|
It("should have the insecure skip verify from the config env var", func() {
|
||||||
setEnv("REPLICA_INSECURE_SKIP_VERIFY", "false")
|
setEnv("REPLICA_INSECURE_SKIP_VERIFY", "false")
|
||||||
@@ -96,9 +107,9 @@ var _ = Describe("Config", func() {
|
|||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
|
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -106,44 +117,44 @@ var _ = Describe("Config", func() {
|
|||||||
It("should have the insecure skip verify from the config file", func() {
|
It("should have the insecure skip verify from the config file", func() {
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
||||||
})
|
})
|
||||||
It("should have the insecure skip verify from the config env var", func() {
|
It("should have the insecure skip verify from the config env var", func() {
|
||||||
setEnv("REPLICA1_INSECURE_SKIP_VERIFY", "true")
|
setEnv("REPLICA1_INSECURE_SKIP_VERIFY", "true")
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue())
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("API Port", func() {
|
Context("API Port", func() {
|
||||||
It("should have the api port from the config file", func() {
|
It("should have the api port from the config file", func() {
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.API.Port).Should(Equal(9090))
|
Ω(cfg.Get().API.Port).Should(Equal(9090))
|
||||||
})
|
})
|
||||||
It("should have the api port from the config flags", func() {
|
It("should have the api port from the config flags", func() {
|
||||||
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
|
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
|
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.API.Port).Should(Equal(9990))
|
Ω(cfg.Get().API.Port).Should(Equal(9990))
|
||||||
})
|
})
|
||||||
It("should have the api port from the config env var", func() {
|
It("should have the api port from the config env var", func() {
|
||||||
setEnv("API_PORT", "9999")
|
setEnv("API_PORT", "9999")
|
||||||
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
|
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
|
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.API.Port).Should(Equal(9999))
|
Ω(cfg.Get().API.Port).Should(Equal(9999))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -151,10 +162,10 @@ var _ = Describe("Config", func() {
|
|||||||
It("should have the dhcp server enabled from the config file", func() {
|
It("should have the dhcp server enabled from the config file", func() {
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -162,75 +173,86 @@ var _ = Describe("Config", func() {
|
|||||||
It("should have the dhcp server enabled from the config file", func() {
|
It("should have the dhcp server enabled from the config file", func() {
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("API Port", func() {
|
Context("API Port", func() {
|
||||||
It("should have the api port from the config file", func() {
|
It("should have the api port from the config file", func() {
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.API.Port).Should(Equal(9090))
|
Ω(cfg.Get().API.Port).Should(Equal(9090))
|
||||||
})
|
})
|
||||||
It("should have the api port from the config flags", func() {
|
It("should have the api port from the config flags", func() {
|
||||||
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
|
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
|
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.API.Port).Should(Equal(9990))
|
Ω(cfg.Get().API.Port).Should(Equal(9990))
|
||||||
})
|
})
|
||||||
It("should have the api port from the config env var", func() {
|
It("should have the api port from the config env var", func() {
|
||||||
setEnv("API_PORT", "9999")
|
setEnv("API_PORT", "9999")
|
||||||
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
|
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
|
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.API.Port).Should(Equal(9999))
|
Ω(cfg.Get().API.Port).Should(Equal(9999))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("Feature DNS Server Config", func() {
|
Context("Feature DNS Server Config", func() {
|
||||||
It("should have the feature dns server config from the config file", func() {
|
It("should have the feature dns server config from the config file", func() {
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
|
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
|
||||||
})
|
})
|
||||||
It("should have the feature dns server config from the config flags", func() {
|
It("should have the feature dns server config from the config flags", func() {
|
||||||
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
|
flags.EXPECT().Changed(config.FlagFeatureDNSServerConfig).Return(true).AnyTimes()
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes()
|
flags.EXPECT().GetBool(config.FlagFeatureDNSServerConfig).Return(true, nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Features.DNS.ServerConfig).Should(BeTrue())
|
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeTrue())
|
||||||
})
|
})
|
||||||
It("should have the feature dns server config from the config env var", func() {
|
It("should have the feature dns server config from the config env var", func() {
|
||||||
setEnv("FEATURES_DNS_SERVER_CONFIG", "false")
|
setEnv("FEATURES_DNS_SERVER_CONFIG", "false")
|
||||||
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
|
flags.EXPECT().Changed(config.FlagFeatureDNSServerConfig).Return(true).AnyTimes()
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes()
|
flags.EXPECT().GetBool(config.FlagFeatureDNSServerConfig).Return(true, nil).AnyTimes()
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
|
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
|
||||||
})
|
})
|
||||||
It("should have the feature dns server config from the config DEPRECATED env var", func() {
|
})
|
||||||
setEnv("FEATURES_DNS_SERVERCONFIG", "false")
|
|
||||||
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
|
|
||||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
|
||||||
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes()
|
|
||||||
|
|
||||||
cfg, _, _, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
Context("Headers", func() {
|
||||||
|
It("have headers from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
|
Ω(cfg.Get().Replicas[0].RequestHeaders).Should(HaveLen(2))
|
||||||
|
Ω(cfg.Get().Replicas[0].RequestHeaders["FOO"]).Should(Equal("bar"))
|
||||||
|
Ω(cfg.Get().Replicas[0].RequestHeaders["Client-ID"]).Should(Equal("xxxx"))
|
||||||
|
})
|
||||||
|
It("have headers from the config file will be replaced when defined as ENV", func() {
|
||||||
|
setEnv("REPLICA1_REQUEST_HEADERS", "AAA:bbb")
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].RequestHeaders).Should(HaveLen(1))
|
||||||
|
Ω(cfg.Get().Replicas[0].RequestHeaders["AAA"]).Should(Equal("bbb"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
57
internal/config/env.go
Normal file
57
internal/config/env.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/caarlos0/env/v11"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manually collect replicas from env.
|
||||||
|
func enrichReplicasFromEnv(initialReplicas []types.AdGuardInstance) ([]types.AdGuardInstance, error) {
|
||||||
|
var replicas []types.AdGuardInstance
|
||||||
|
for _, v := range os.Environ() {
|
||||||
|
if envReplicasURLPattern.MatchString(v) {
|
||||||
|
sm := envReplicasURLPattern.FindStringSubmatch(v)
|
||||||
|
id, _ := strconv.Atoi(sm[1])
|
||||||
|
|
||||||
|
if id <= 0 {
|
||||||
|
return nil, fmt.Errorf("numbered replica env variables must have a number id >= 1, got %q", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id > len(initialReplicas) {
|
||||||
|
replicas = append(replicas, types.AdGuardInstance{URL: sm[2]})
|
||||||
|
} else {
|
||||||
|
re := initialReplicas[id-1]
|
||||||
|
re.URL = sm[2]
|
||||||
|
replicas = append(replicas, re)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(replicas) == 0 {
|
||||||
|
replicas = initialReplicas
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range replicas {
|
||||||
|
reID := i + 1
|
||||||
|
|
||||||
|
// keep the previously set value
|
||||||
|
replicaDhcpServer := replicas[i].DHCPServerEnabled
|
||||||
|
replicas[i].DHCPServerEnabled = nil
|
||||||
|
if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if replicas[i].DHCPServerEnabled == nil {
|
||||||
|
replicas[i].DHCPServerEnabled = replicaDhcpServer
|
||||||
|
}
|
||||||
|
if replicas[i].APIPath == "" {
|
||||||
|
replicas[i].APIPath = "/control"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return replicas, nil
|
||||||
|
}
|
||||||
@@ -4,8 +4,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readFile(cfg *types.Config, path string) (string, error) {
|
func readFile(cfg *types.Config, path string) (string, error) {
|
||||||
@@ -10,9 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Config", func() {
|
var _ = Describe("Config", func() {
|
||||||
var ()
|
|
||||||
BeforeEach(func() {
|
|
||||||
})
|
|
||||||
Context("configFilePath", func() {
|
Context("configFilePath", func() {
|
||||||
It("should return the same value", func() {
|
It("should return the same value", func() {
|
||||||
path := uuid.NewString()
|
path := uuid.NewString()
|
||||||
@@ -22,11 +19,12 @@ var _ = Describe("Config", func() {
|
|||||||
Ω(result).Should(Equal(path))
|
Ω(result).Should(Equal(path))
|
||||||
})
|
})
|
||||||
It("should the file in HOME dir", func() {
|
It("should the file in HOME dir", func() {
|
||||||
home := os.Getenv("HOME")
|
home, err := os.UserHomeDir()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
result, err := configFilePath("")
|
result, err := configFilePath("")
|
||||||
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(result).Should(Equal(filepath.Join(home, "/.adguardhome-sync.yaml")))
|
Ω(result).Should(Equal(filepath.Join(home, ".adguardhome-sync.yaml")))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -6,26 +6,27 @@ const (
|
|||||||
FlagPrintConfigOnly = "printConfigOnly"
|
FlagPrintConfigOnly = "printConfigOnly"
|
||||||
FlagContinueOnError = "continueOnError"
|
FlagContinueOnError = "continueOnError"
|
||||||
|
|
||||||
FlagApiPort = "api-port"
|
FlagAPIPort = "api-port"
|
||||||
FlagApiUsername = "api-username"
|
FlagAPIUsername = "api-username"
|
||||||
FlagApiPassword = "api-password"
|
FlagAPIPassword = "api-password"
|
||||||
FlagApiDarkMode = "api-dark-mode"
|
FlagAPIDarkMode = "api-dark-mode"
|
||||||
|
|
||||||
FlagFeatureDhcpServerConfig = "feature-dhcp-server-config"
|
FlagFeatureDhcpServerConfig = "feature-dhcp-server-config"
|
||||||
FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases"
|
FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases"
|
||||||
FlagFeatureDnsServerConfig = "feature-dns-server-config"
|
FlagFeatureDNSServerConfig = "feature-dns-server-config"
|
||||||
FlagFeatureDnsAccessLists = "feature-dns-access-lists"
|
FlagFeatureDNSAccessLists = "feature-dns-access-lists"
|
||||||
FlagFeatureDnsRewrites = "feature-dns-rewrites"
|
FlagFeatureDNSRewrites = "feature-dns-rewrites"
|
||||||
FlagFeatureGeneral = "feature-general-settings"
|
FlagFeatureGeneral = "feature-general-settings"
|
||||||
FlagFeatureQueryLog = "feature-query-log-config"
|
FlagFeatureQueryLog = "feature-query-log-config"
|
||||||
FlagFeatureStats = "feature-stats-config"
|
FlagFeatureStats = "feature-stats-config"
|
||||||
FlagFeatureClient = "feature-client-settings"
|
FlagFeatureClient = "feature-client-settings"
|
||||||
FlagFeatureServices = "feature-services"
|
FlagFeatureServices = "feature-services"
|
||||||
FlagFeatureFilters = "feature-filters"
|
FlagFeatureFilters = "feature-filters"
|
||||||
|
FlagFeatureTLSConfig = "feature-tls-config"
|
||||||
|
|
||||||
FlagOriginURL = "origin-url"
|
FlagOriginURL = "origin-url"
|
||||||
FlagOriginWebURL = "origin-web-url"
|
FlagOriginWebURL = "origin-web-url"
|
||||||
FlagOriginApiPath = "origin-api-path"
|
FlagOriginAPIPath = "origin-api-path"
|
||||||
FlagOriginUsername = "origin-username"
|
FlagOriginUsername = "origin-username"
|
||||||
|
|
||||||
FlagOriginPassword = "origin-password"
|
FlagOriginPassword = "origin-password"
|
||||||
@@ -34,7 +35,7 @@ const (
|
|||||||
|
|
||||||
FlagReplicaURL = "replica-url"
|
FlagReplicaURL = "replica-url"
|
||||||
FlagReplicaWebURL = "replica-web-url"
|
FlagReplicaWebURL = "replica-web-url"
|
||||||
FlagReplicaApiPath = "replica-api-path"
|
FlagReplicaAPIPath = "replica-api-path"
|
||||||
FlagReplicaUsername = "replica-username"
|
FlagReplicaUsername = "replica-username"
|
||||||
FlagReplicaPassword = "replica-password"
|
FlagReplicaPassword = "replica-password"
|
||||||
FlagReplicaCookie = "replica-cookie"
|
FlagReplicaCookie = "replica-cookie"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readFlags(cfg *types.Config, flags Flags) error {
|
func readFlags(cfg *types.Config, flags Flags) error {
|
||||||
@@ -18,7 +18,7 @@ func readFlags(cfg *types.Config, flags Flags) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fr.readApiFlags(); err != nil {
|
if err := fr.readAPIFlags(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,11 +30,7 @@ func readFlags(cfg *types.Config, flags Flags) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fr.readReplicaFlags(); err != nil {
|
return fr.readReplicaFlags()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type flagReader struct {
|
type flagReader struct {
|
||||||
@@ -53,7 +49,7 @@ func (fr *flagReader) readReplicaFlags() error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fr.setStringFlag(FlagReplicaApiPath, func(cgf *types.Config, value string) {
|
if err := fr.setStringFlag(FlagReplicaAPIPath, func(cgf *types.Config, value string) {
|
||||||
fr.cfg.Replica.APIPath = value
|
fr.cfg.Replica.APIPath = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -83,12 +79,9 @@ func (fr *flagReader) readReplicaFlags() error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) {
|
return fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) {
|
||||||
fr.cfg.Replica.InterfaceName = value
|
fr.cfg.Replica.InterfaceName = value
|
||||||
}); err != nil {
|
})
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr *flagReader) readOriginFlags() error {
|
func (fr *flagReader) readOriginFlags() error {
|
||||||
@@ -102,7 +95,7 @@ func (fr *flagReader) readOriginFlags() error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fr.setStringFlag(FlagOriginApiPath, func(cgf *types.Config, value string) {
|
if err := fr.setStringFlag(FlagOriginAPIPath, func(cgf *types.Config, value string) {
|
||||||
fr.cfg.Origin.APIPath = value
|
fr.cfg.Origin.APIPath = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -122,12 +115,9 @@ func (fr *flagReader) readOriginFlags() error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) {
|
return fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) {
|
||||||
fr.cfg.Origin.InsecureSkipVerify = value
|
fr.cfg.Origin.InsecureSkipVerify = value
|
||||||
}); err != nil {
|
})
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr *flagReader) readFeatureFlags() error {
|
func (fr *flagReader) readFeatureFlags() error {
|
||||||
@@ -142,17 +132,17 @@ func (fr *flagReader) readFeatureFlags() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fr.setBoolFlag(FlagFeatureDnsServerConfig, func(cgf *types.Config, value bool) {
|
if err := fr.setBoolFlag(FlagFeatureDNSServerConfig, func(cgf *types.Config, value bool) {
|
||||||
fr.cfg.Features.DNS.ServerConfig = value
|
fr.cfg.Features.DNS.ServerConfig = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fr.setBoolFlag(FlagFeatureDnsAccessLists, func(cgf *types.Config, value bool) {
|
if err := fr.setBoolFlag(FlagFeatureDNSAccessLists, func(cgf *types.Config, value bool) {
|
||||||
fr.cfg.Features.DNS.AccessLists = value
|
fr.cfg.Features.DNS.AccessLists = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := fr.setBoolFlag(FlagFeatureDnsRewrites, func(cgf *types.Config, value bool) {
|
if err := fr.setBoolFlag(FlagFeatureDNSRewrites, func(cgf *types.Config, value bool) {
|
||||||
fr.cfg.Features.DNS.Rewrites = value
|
fr.cfg.Features.DNS.Rewrites = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -188,56 +178,51 @@ func (fr *flagReader) readFeatureFlags() error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return fr.setBoolFlag(FlagFeatureTLSConfig, func(cgf *types.Config, value bool) {
|
||||||
return nil
|
fr.cfg.Features.TLSConfig = value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr *flagReader) readApiFlags() (err error) {
|
func (fr *flagReader) readAPIFlags() error {
|
||||||
if err = fr.setIntFlag(FlagApiPort, func(cgf *types.Config, value int) {
|
if err := fr.setIntFlag(FlagAPIPort, func(cgf *types.Config, value int) {
|
||||||
fr.cfg.API.Port = value
|
fr.cfg.API.Port = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
if err = fr.setStringFlag(FlagApiUsername, func(cgf *types.Config, value string) {
|
if err := fr.setStringFlag(FlagAPIUsername, func(cgf *types.Config, value string) {
|
||||||
fr.cfg.API.Username = value
|
fr.cfg.API.Username = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
if err = fr.setStringFlag(FlagApiPassword, func(cgf *types.Config, value string) {
|
if err := fr.setStringFlag(FlagAPIPassword, func(cgf *types.Config, value string) {
|
||||||
fr.cfg.API.Password = value
|
fr.cfg.API.Password = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
if err = fr.setBoolFlag(FlagApiDarkMode, func(cgf *types.Config, value bool) {
|
return fr.setBoolFlag(FlagAPIDarkMode, func(cgf *types.Config, value bool) {
|
||||||
fr.cfg.API.DarkMode = value
|
fr.cfg.API.DarkMode = value
|
||||||
}); err != nil {
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr *flagReader) readRootFlags() (err error) {
|
func (fr *flagReader) readRootFlags() error {
|
||||||
if err = fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) {
|
if err := fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) {
|
||||||
fr.cfg.Cron = value
|
fr.cfg.Cron = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
if err = fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) {
|
if err := fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) {
|
||||||
fr.cfg.RunOnStart = value
|
fr.cfg.RunOnStart = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
if err = fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) {
|
if err := fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) {
|
||||||
fr.cfg.PrintConfigOnly = value
|
fr.cfg.PrintConfigOnly = value
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
if err = fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) {
|
return fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) {
|
||||||
fr.cfg.ContinueOnError = value
|
fr.cfg.ContinueOnError = value
|
||||||
}); err != nil {
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Flags interface {
|
type Flags interface {
|
||||||
@@ -249,33 +234,33 @@ type Flags interface {
|
|||||||
|
|
||||||
func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) {
|
func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) {
|
||||||
if fr.flags.Changed(name) {
|
if fr.flags.Changed(name) {
|
||||||
if value, err := fr.flags.GetString(name); err != nil {
|
var value string
|
||||||
|
if value, err = fr.flags.GetString(name); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
cb(fr.cfg, value)
|
|
||||||
}
|
}
|
||||||
|
cb(fr.cfg, value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) {
|
func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) {
|
||||||
if fr.flags.Changed(name) {
|
if fr.flags.Changed(name) {
|
||||||
if value, err := fr.flags.GetBool(name); err != nil {
|
var value bool
|
||||||
|
if value, err = fr.flags.GetBool(name); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
cb(fr.cfg, value)
|
|
||||||
}
|
}
|
||||||
|
cb(fr.cfg, value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) {
|
func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) {
|
||||||
if fr.flags.Changed(name) {
|
if fr.flags.Changed(name) {
|
||||||
if value, err := fr.flags.GetInt(name); err != nil {
|
var value int
|
||||||
|
if value, err = fr.flags.GetInt(name); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
cb(fr.cfg, value)
|
|
||||||
}
|
}
|
||||||
|
cb(fr.cfg, value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,12 @@ package config
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
gm "go.uber.org/mock/gomock"
|
gm "go.uber.org/mock/gomock"
|
||||||
|
|
||||||
|
flagsmock "github.com/bakito/adguardhome-sync/internal/mocks/flags"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Config", func() {
|
var _ = Describe("Config", func() {
|
||||||
@@ -18,6 +19,7 @@ var _ = Describe("Config", func() {
|
|||||||
)
|
)
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
cfg = &types.Config{
|
cfg = &types.Config{
|
||||||
|
Origin: &types.AdGuardInstance{},
|
||||||
Replica: &types.AdGuardInstance{},
|
Replica: &types.AdGuardInstance{},
|
||||||
Features: types.Features{
|
Features: types.Features{
|
||||||
DNS: types.DNS{
|
DNS: types.DNS{
|
||||||
@@ -88,7 +90,7 @@ var _ = Describe("Config", func() {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("readApiFlags", func() {
|
Context("readAPIFlags", func() {
|
||||||
It("should change all values", func() {
|
It("should change all values", func() {
|
||||||
cfg.API = types.API{
|
cfg.API = types.API{
|
||||||
Port: 1111,
|
Port: 1111,
|
||||||
@@ -99,10 +101,10 @@ var _ = Describe("Config", func() {
|
|||||||
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
|
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
|
||||||
return strings.HasPrefix(name, "api")
|
return strings.HasPrefix(name, "api")
|
||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
flags.EXPECT().GetInt(FlagApiPort).Return(9999, nil)
|
flags.EXPECT().GetInt(FlagAPIPort).Return(9999, nil)
|
||||||
flags.EXPECT().GetString(FlagApiUsername).Return("aaaa", nil)
|
flags.EXPECT().GetString(FlagAPIUsername).Return("aaaa", nil)
|
||||||
flags.EXPECT().GetString(FlagApiPassword).Return("bbbb", nil)
|
flags.EXPECT().GetString(FlagAPIPassword).Return("bbbb", nil)
|
||||||
flags.EXPECT().GetBool(FlagApiDarkMode).Return(true, nil)
|
flags.EXPECT().GetBool(FlagAPIDarkMode).Return(true, nil)
|
||||||
err := readFlags(cfg, flags)
|
err := readFlags(cfg, flags)
|
||||||
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
@@ -142,7 +144,7 @@ var _ = Describe("Config", func() {
|
|||||||
})
|
})
|
||||||
Context("readOriginFlags", func() {
|
Context("readOriginFlags", func() {
|
||||||
It("should change all values", func() {
|
It("should change all values", func() {
|
||||||
cfg.Origin = types.AdGuardInstance{
|
cfg.Origin = &types.AdGuardInstance{
|
||||||
URL: "1",
|
URL: "1",
|
||||||
WebURL: "2",
|
WebURL: "2",
|
||||||
APIPath: "3",
|
APIPath: "3",
|
||||||
@@ -154,7 +156,7 @@ var _ = Describe("Config", func() {
|
|||||||
|
|
||||||
flags.EXPECT().Changed(FlagOriginURL).Return(true)
|
flags.EXPECT().Changed(FlagOriginURL).Return(true)
|
||||||
flags.EXPECT().Changed(FlagOriginWebURL).Return(true)
|
flags.EXPECT().Changed(FlagOriginWebURL).Return(true)
|
||||||
flags.EXPECT().Changed(FlagOriginApiPath).Return(true)
|
flags.EXPECT().Changed(FlagOriginAPIPath).Return(true)
|
||||||
flags.EXPECT().Changed(FlagOriginUsername).Return(true)
|
flags.EXPECT().Changed(FlagOriginUsername).Return(true)
|
||||||
flags.EXPECT().Changed(FlagOriginPassword).Return(true)
|
flags.EXPECT().Changed(FlagOriginPassword).Return(true)
|
||||||
flags.EXPECT().Changed(FlagOriginCookie).Return(true)
|
flags.EXPECT().Changed(FlagOriginCookie).Return(true)
|
||||||
@@ -163,7 +165,7 @@ var _ = Describe("Config", func() {
|
|||||||
|
|
||||||
flags.EXPECT().GetString(FlagOriginURL).Return("a", nil)
|
flags.EXPECT().GetString(FlagOriginURL).Return("a", nil)
|
||||||
flags.EXPECT().GetString(FlagOriginWebURL).Return("b", nil)
|
flags.EXPECT().GetString(FlagOriginWebURL).Return("b", nil)
|
||||||
flags.EXPECT().GetString(FlagOriginApiPath).Return("c", nil)
|
flags.EXPECT().GetString(FlagOriginAPIPath).Return("c", nil)
|
||||||
flags.EXPECT().GetString(FlagOriginUsername).Return("d", nil)
|
flags.EXPECT().GetString(FlagOriginUsername).Return("d", nil)
|
||||||
flags.EXPECT().GetString(FlagOriginPassword).Return("e", nil)
|
flags.EXPECT().GetString(FlagOriginPassword).Return("e", nil)
|
||||||
flags.EXPECT().GetString(FlagOriginCookie).Return("f", nil)
|
flags.EXPECT().GetString(FlagOriginCookie).Return("f", nil)
|
||||||
@@ -171,7 +173,7 @@ var _ = Describe("Config", func() {
|
|||||||
err := readFlags(cfg, flags)
|
err := readFlags(cfg, flags)
|
||||||
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(cfg.Origin).Should(Equal(types.AdGuardInstance{
|
Ω(cfg.Origin).Should(Equal(&types.AdGuardInstance{
|
||||||
URL: "a",
|
URL: "a",
|
||||||
WebURL: "b",
|
WebURL: "b",
|
||||||
APIPath: "c",
|
APIPath: "c",
|
||||||
@@ -198,7 +200,7 @@ var _ = Describe("Config", func() {
|
|||||||
|
|
||||||
flags.EXPECT().Changed(FlagReplicaURL).Return(true)
|
flags.EXPECT().Changed(FlagReplicaURL).Return(true)
|
||||||
flags.EXPECT().Changed(FlagReplicaWebURL).Return(true)
|
flags.EXPECT().Changed(FlagReplicaWebURL).Return(true)
|
||||||
flags.EXPECT().Changed(FlagReplicaApiPath).Return(true)
|
flags.EXPECT().Changed(FlagReplicaAPIPath).Return(true)
|
||||||
flags.EXPECT().Changed(FlagReplicaUsername).Return(true)
|
flags.EXPECT().Changed(FlagReplicaUsername).Return(true)
|
||||||
flags.EXPECT().Changed(FlagReplicaPassword).Return(true)
|
flags.EXPECT().Changed(FlagReplicaPassword).Return(true)
|
||||||
flags.EXPECT().Changed(FlagReplicaCookie).Return(true)
|
flags.EXPECT().Changed(FlagReplicaCookie).Return(true)
|
||||||
@@ -209,7 +211,7 @@ var _ = Describe("Config", func() {
|
|||||||
|
|
||||||
flags.EXPECT().GetString(FlagReplicaURL).Return("a", nil)
|
flags.EXPECT().GetString(FlagReplicaURL).Return("a", nil)
|
||||||
flags.EXPECT().GetString(FlagReplicaWebURL).Return("b", nil)
|
flags.EXPECT().GetString(FlagReplicaWebURL).Return("b", nil)
|
||||||
flags.EXPECT().GetString(FlagReplicaApiPath).Return("c", nil)
|
flags.EXPECT().GetString(FlagReplicaAPIPath).Return("c", nil)
|
||||||
flags.EXPECT().GetString(FlagReplicaUsername).Return("d", nil)
|
flags.EXPECT().GetString(FlagReplicaUsername).Return("d", nil)
|
||||||
flags.EXPECT().GetString(FlagReplicaPassword).Return("e", nil)
|
flags.EXPECT().GetString(FlagReplicaPassword).Return("e", nil)
|
||||||
flags.EXPECT().GetString(FlagReplicaCookie).Return("f", nil)
|
flags.EXPECT().GetString(FlagReplicaCookie).Return("f", nil)
|
||||||
89
internal/config/print-config.go
Normal file
89
internal/config/print-config.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed print-config.md
|
||||||
|
var printConfigTemplate string
|
||||||
|
|
||||||
|
func (ac *AppConfig) Print() error {
|
||||||
|
originVersion := aghVersion(*ac.cfg.Origin)
|
||||||
|
var replicaVersions []string
|
||||||
|
for _, replica := range ac.cfg.Replicas {
|
||||||
|
replicaVersions = append(replicaVersions, aghVersion(replica))
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := ac.printInternal(os.Environ(), originVersion, replicaVersions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof(
|
||||||
|
"Printing adguardhome-sync aggregated config (THE APPLICATION WILL NOT START IN THIS MODE):\n%s",
|
||||||
|
out,
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func aghVersion(i types.AdGuardInstance) string {
|
||||||
|
cl, err := client.New(i)
|
||||||
|
if err != nil {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
stats, err := cl.Status()
|
||||||
|
if err != nil {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
return stats.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AppConfig) printInternal(env []string, originVersion string, replicaVersions []string) (string, error) {
|
||||||
|
config, err := yaml.Marshal(ac.Get())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
funcMap := template.FuncMap{
|
||||||
|
// The name "inc" is what the function will be called in the template text.
|
||||||
|
"inc": func(i int) int {
|
||||||
|
return i + 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := template.New("printConfigTemplate").Funcs(funcMap).Parse(printConfigTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(env)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
err = t.Execute(&buf, map[string]any{
|
||||||
|
"Version": version.Version,
|
||||||
|
"Build": version.Build,
|
||||||
|
"OperatingSystem": runtime.GOOS,
|
||||||
|
"Architecture": runtime.GOARCH,
|
||||||
|
"AggregatedConfig": string(config),
|
||||||
|
"ConfigFilePath": ac.filePath,
|
||||||
|
"ConfigFileContent": ac.content,
|
||||||
|
"EnvironmentVariables": strings.Join(env, "\n"),
|
||||||
|
"OriginVersion": originVersion,
|
||||||
|
"ReplicaVersions": replicaVersions,
|
||||||
|
})
|
||||||
|
return buf.String(), err
|
||||||
|
}
|
||||||
36
internal/config/print-config.md
Normal file
36
internal/config/print-config.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!-- PLEASE COPY THE FOLLOWING OUTPUT AS IS INTO THE GITHUB ISSUE (Don't forget to mask your usernames, passwords, IPs and other sensitive information when using this in an issue ) -->
|
||||||
|
|
||||||
|
### Runtime
|
||||||
|
|
||||||
|
AdguardHome-Sync Version: {{ .Version }}
|
||||||
|
Build: {{ .Build }}
|
||||||
|
OperatingSystem: {{ .OperatingSystem }}
|
||||||
|
Architecture: {{ .Architecture }}
|
||||||
|
OriginVersion: {{ .OriginVersion }}
|
||||||
|
ReplicaVersions:
|
||||||
|
{{- range $i,$rep := .ReplicaVersions }}
|
||||||
|
- Replica {{ inc $i }}: {{ $rep }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
### AdGuardHome sync aggregated config
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
{{ .AggregatedConfig }}
|
||||||
|
```
|
||||||
|
{{- if .ConfigFilePath }}
|
||||||
|
### AdGuardHome sync unmodified config file
|
||||||
|
|
||||||
|
Config file path: {{ .ConfigFilePath }}
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
{{ .ConfigFileContent }}
|
||||||
|
```
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```ini
|
||||||
|
{{ .EnvironmentVariables }}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- END OF GITHUB ISSUE CONTENT -->
|
||||||
60
internal/config/print-config_test.go
Normal file
60
internal/config/print-config_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/test/matchers"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("AppConfig", func() {
|
||||||
|
var (
|
||||||
|
ac *AppConfig
|
||||||
|
env []string
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
ac = &AppConfig{
|
||||||
|
cfg: &types.Config{
|
||||||
|
Origin: &types.AdGuardInstance{
|
||||||
|
URL: "https://ha.xxxx.net:3000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
content: `
|
||||||
|
origin:
|
||||||
|
url: https://ha.xxxx.net:3000
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
env = []string{"FOO=foo", "BAR=bar"}
|
||||||
|
})
|
||||||
|
Context("printInternal", func() {
|
||||||
|
It("should printInternal config without file", func() {
|
||||||
|
out, err := ac.printInternal(env, "v0.0.1", []string{"v0.0.2"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(out).
|
||||||
|
Should(matchers.EqualIgnoringLineEndings(fmt.Sprintf(expected(1), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
|
||||||
|
})
|
||||||
|
It("should printInternal config with file", func() {
|
||||||
|
ac.filePath = "config.yaml"
|
||||||
|
out, err := ac.printInternal(env, "v0.0.1", []string{"v0.0.2"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(out).
|
||||||
|
Should(matchers.EqualIgnoringLineEndings(fmt.Sprintf(expected(2), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
func expected(id int) string {
|
||||||
|
b, err := os.ReadFile(
|
||||||
|
filepath.Join("..", "..", "testdata", "config", fmt.Sprintf("print-config_test_expected%d.md", id)),
|
||||||
|
)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -17,20 +18,26 @@ var schemaData string
|
|||||||
func validateSchema(cfgFile string) error {
|
func validateSchema(cfgFile string) error {
|
||||||
// ignore if file not exists
|
// ignore if file not exists
|
||||||
if _, err := os.Stat(cfgFile); err != nil {
|
if _, err := os.Stat(cfgFile); err != nil {
|
||||||
|
// Config file does not exist or is not readable - ignore it
|
||||||
|
//nolint:nilerr
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Load YAML file
|
// Load YAML file
|
||||||
yamlContent, err := os.ReadFile(cfgFile)
|
yamlContent, err := os.ReadFile(cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("config file %q is invalid: %w", cfgFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return validateYAML(yamlContent)
|
return validateYAML(yamlContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateYAML(yamlContent []byte) error {
|
func validateYAML(yamlContent []byte) error {
|
||||||
|
if yamlContent == nil || strings.TrimSpace(string(yamlContent)) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Convert YAML to JSON
|
// Convert YAML to JSON
|
||||||
var yamlData interface{}
|
var yamlData any
|
||||||
err := yaml.Unmarshal(yamlContent, &yamlData)
|
err := yaml.Unmarshal(yamlContent, &yamlData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/go-faker/faker/v4"
|
"github.com/go-faker/faker/v4"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Config", func() {
|
var _ = Describe("Config", func() {
|
||||||
@@ -35,5 +36,10 @@ var _ = Describe("Config", func() {
|
|||||||
err = validateYAML(data)
|
err = validateYAML(data)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
It("validate config with empty file", func() {
|
||||||
|
var data []byte
|
||||||
|
err := validateYAML(data)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -18,7 +18,7 @@ var (
|
|||||||
logs []string
|
logs []string
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetLogger returns a named logger
|
// GetLogger returns a named logger.
|
||||||
func GetLogger(name string) *zap.SugaredLogger {
|
func GetLogger(name string) *zap.SugaredLogger {
|
||||||
return rootLogger.Named(name).Sugar()
|
return rootLogger.Named(name).Sugar()
|
||||||
}
|
}
|
||||||
@@ -101,12 +101,12 @@ func (l *logList) Sync() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logs get the current logs
|
// Logs get the current logs.
|
||||||
func Logs() []string {
|
func Logs() []string {
|
||||||
return logs
|
return logs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the current logs
|
// Clear the current logs.
|
||||||
func Clear() {
|
func Clear() {
|
||||||
logs = nil
|
logs = nil
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const StatsTotal = "total"
|
const StatsTotal = "total"
|
||||||
@@ -11,7 +12,7 @@ const StatsTotal = "total"
|
|||||||
var (
|
var (
|
||||||
l = log.GetLogger("metrics")
|
l = log.GetLogger("metrics")
|
||||||
|
|
||||||
// avgProcessingTime - Average processing time for a DNS query
|
// avgProcessingTime - Average processing time for a DNS query.
|
||||||
avgProcessingTime = prometheus.NewGaugeVec(
|
avgProcessingTime = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "avg_processing_time",
|
Name: "avg_processing_time",
|
||||||
@@ -21,7 +22,7 @@ var (
|
|||||||
[]string{"hostname"},
|
[]string{"hostname"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// dnsQueries - Number of DNS queries
|
// dnsQueries - Number of DNS queries.
|
||||||
dnsQueries = prometheus.NewGaugeVec(
|
dnsQueries = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "num_dns_queries",
|
Name: "num_dns_queries",
|
||||||
@@ -31,7 +32,7 @@ var (
|
|||||||
[]string{"hostname"},
|
[]string{"hostname"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// blockedFiltering - Number of DNS queries blocked
|
// blockedFiltering - Number of DNS queries blocked.
|
||||||
blockedFiltering = prometheus.NewGaugeVec(
|
blockedFiltering = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "num_blocked_filtering",
|
Name: "num_blocked_filtering",
|
||||||
@@ -41,7 +42,7 @@ var (
|
|||||||
[]string{"hostname"},
|
[]string{"hostname"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// parentalFiltering - Number of DNS queries replaced by parental control
|
// parentalFiltering - Number of DNS queries replaced by parental control.
|
||||||
parentalFiltering = prometheus.NewGaugeVec(
|
parentalFiltering = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "num_replaced_parental",
|
Name: "num_replaced_parental",
|
||||||
@@ -51,7 +52,7 @@ var (
|
|||||||
[]string{"hostname"},
|
[]string{"hostname"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// safeBrowsingFiltering - Number of DNS queries replaced by safe browsing
|
// safeBrowsingFiltering - Number of DNS queries replaced by safe browsing.
|
||||||
safeBrowsingFiltering = prometheus.NewGaugeVec(
|
safeBrowsingFiltering = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "num_replaced_safebrowsing",
|
Name: "num_replaced_safebrowsing",
|
||||||
@@ -61,7 +62,7 @@ var (
|
|||||||
[]string{"hostname"},
|
[]string{"hostname"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// safeSearchFiltering - Number of DNS queries replaced by safe search
|
// safeSearchFiltering - Number of DNS queries replaced by safe search.
|
||||||
safeSearchFiltering = prometheus.NewGaugeVec(
|
safeSearchFiltering = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "num_replaced_safesearch",
|
Name: "num_replaced_safesearch",
|
||||||
@@ -71,7 +72,7 @@ var (
|
|||||||
[]string{"hostname"},
|
[]string{"hostname"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// topQueries - The number of top queries
|
// topQueries - The number of top queries.
|
||||||
topQueries = prometheus.NewGaugeVec(
|
topQueries = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "top_queried_domains",
|
Name: "top_queried_domains",
|
||||||
@@ -81,7 +82,7 @@ var (
|
|||||||
[]string{"hostname", "domain"},
|
[]string{"hostname", "domain"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// topBlocked - The number of top domains blocked
|
// topBlocked - The number of top domains blocked.
|
||||||
topBlocked = prometheus.NewGaugeVec(
|
topBlocked = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "top_blocked_domains",
|
Name: "top_blocked_domains",
|
||||||
@@ -91,7 +92,7 @@ var (
|
|||||||
[]string{"hostname", "domain"},
|
[]string{"hostname", "domain"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// topClients - The number of top clients
|
// topClients - The number of top clients.
|
||||||
topClients = prometheus.NewGaugeVec(
|
topClients = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "top_clients",
|
Name: "top_clients",
|
||||||
@@ -111,7 +112,7 @@ var (
|
|||||||
[]string{"hostname", "type"},
|
[]string{"hostname", "type"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// running - If Adguard is running
|
// running - If Adguard is running.
|
||||||
running = prometheus.NewGaugeVec(
|
running = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "running",
|
Name: "running",
|
||||||
@@ -121,7 +122,7 @@ var (
|
|||||||
[]string{"hostname"},
|
[]string{"hostname"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// protectionEnabled - If Adguard protection is enabled
|
// protectionEnabled - If Adguard protection is enabled.
|
||||||
protectionEnabled = prometheus.NewGaugeVec(
|
protectionEnabled = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "protection_enabled",
|
Name: "protection_enabled",
|
||||||
@@ -130,7 +131,24 @@ var (
|
|||||||
},
|
},
|
||||||
[]string{"hostname"},
|
[]string{"hostname"},
|
||||||
)
|
)
|
||||||
|
// aghsSyncDuration - the sync curation in seconds.
|
||||||
|
aghsSyncDuration = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "sync_duration_seconds",
|
||||||
|
Namespace: "adguard_home_sync",
|
||||||
|
Help: "This represents the duration of the last sync in seconds",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
// aghsSyncSuccessful - the sync result.
|
||||||
|
aghsSyncSuccessful = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "sync_successful",
|
||||||
|
Namespace: "adguard_home_sync",
|
||||||
|
Help: "This represents the whether the last sync was successful",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
stats = OverallStats{}
|
stats = OverallStats{}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -148,6 +166,8 @@ func Init() {
|
|||||||
initMetric("query_types", queryTypes)
|
initMetric("query_types", queryTypes)
|
||||||
initMetric("running", running)
|
initMetric("running", running)
|
||||||
initMetric("protection_enabled", protectionEnabled)
|
initMetric("protection_enabled", protectionEnabled)
|
||||||
|
initMetric("sync_duration_seconds", aghsSyncDuration)
|
||||||
|
initMetric("sync_successful", aghsSyncSuccessful)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initMetric(name string, metric *prometheus.GaugeVec) {
|
func initMetric(name string, metric *prometheus.GaugeVec) {
|
||||||
@@ -155,24 +175,33 @@ func initMetric(name string, metric *prometheus.GaugeVec) {
|
|||||||
l.With("name", name).Info("New Prometheus metric registered")
|
l.With("name", name).Info("New Prometheus metric registered")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Update(ims ...InstanceMetrics) {
|
func UpdateInstances(iml InstanceMetricsList) {
|
||||||
for _, im := range ims {
|
for _, im := range iml.Metrics {
|
||||||
update(im)
|
updateMetrics(im)
|
||||||
stats[im.HostName] = im.Stats
|
stats[im.HostName] = im.Stats
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Debug("updated")
|
l.Debug("updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(im InstanceMetrics) {
|
func UpdateResult(host string, ok bool, duration float64) {
|
||||||
|
if ok {
|
||||||
|
aghsSyncSuccessful.WithLabelValues(host).Set(1)
|
||||||
|
} else {
|
||||||
|
aghsSyncSuccessful.WithLabelValues(host).Set(0)
|
||||||
|
}
|
||||||
|
aghsSyncDuration.WithLabelValues(host).Set(duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMetrics(im InstanceMetrics) {
|
||||||
// Status
|
// Status
|
||||||
var isRunning int = 0
|
isRunning := 0
|
||||||
if im.Status.Running {
|
if im.Status.Running {
|
||||||
isRunning = 1
|
isRunning = 1
|
||||||
}
|
}
|
||||||
running.WithLabelValues(im.HostName).Set(float64(isRunning))
|
running.WithLabelValues(im.HostName).Set(float64(isRunning))
|
||||||
|
|
||||||
var isProtected int = 0
|
isProtected := 0
|
||||||
if im.Status.ProtectionEnabled {
|
if im.Status.ProtectionEnabled {
|
||||||
isProtected = 1
|
isProtected = 1
|
||||||
}
|
}
|
||||||
@@ -218,7 +247,7 @@ func update(im InstanceMetrics) {
|
|||||||
if len(dnsanswer) > 0 {
|
if len(dnsanswer) > 0 {
|
||||||
for _, dnsa := range dnsanswer {
|
for _, dnsa := range dnsanswer {
|
||||||
dnsType := *dnsa.Type
|
dnsType := *dnsa.Type
|
||||||
m[dnsType] += 1
|
m[dnsType]++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,6 +259,10 @@ func update(im InstanceMetrics) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InstanceMetricsList struct {
|
||||||
|
Metrics []InstanceMetrics `faker:"slice_len=5"`
|
||||||
|
}
|
||||||
|
|
||||||
type InstanceMetrics struct {
|
type InstanceMetrics struct {
|
||||||
HostName string
|
HostName string
|
||||||
Status *model.ServerStatus
|
Status *model.ServerStatus
|
||||||
@@ -255,7 +288,7 @@ func safeMetric[T int | float64 | float32](v *T) float64 {
|
|||||||
return float64(*v)
|
return float64(*v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetStats() OverallStats {
|
func getStats() OverallStats {
|
||||||
return stats.consolidate()
|
return stats.consolidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
13
internal/metrics/metrics_suite_test.go
Normal file
13
internal/metrics/metrics_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package metrics_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSync(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Metrics Suite")
|
||||||
|
}
|
||||||
102
internal/metrics/metrics_test.go
Normal file
102
internal/metrics/metrics_test.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-faker/faker/v4"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Metrics", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
stats = make(OverallStats)
|
||||||
|
})
|
||||||
|
Context("UpdateInstances / getStats", func() {
|
||||||
|
It("generate correct stats", func() {
|
||||||
|
UpdateInstances(InstanceMetricsList{[]InstanceMetrics{
|
||||||
|
{HostName: "foo", Status: &model.ServerStatus{}, Stats: &model.Stats{
|
||||||
|
NumDnsQueries: ptr.To(100),
|
||||||
|
DnsQueries: ptr.To(
|
||||||
|
[]int{10, 20, 30, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
{HostName: "bar", Status: &model.ServerStatus{}, Stats: &model.Stats{
|
||||||
|
NumDnsQueries: ptr.To(200),
|
||||||
|
DnsQueries: ptr.To(
|
||||||
|
[]int{20, 40, 60, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
{HostName: "aaa", Status: &model.ServerStatus{}, Stats: &model.Stats{
|
||||||
|
NumDnsQueries: ptr.To(300),
|
||||||
|
DnsQueries: ptr.To(
|
||||||
|
[]int{30, 60, 90, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
}})
|
||||||
|
Ω(stats).Should(HaveKey("foo"))
|
||||||
|
Ω(stats["foo"].NumDnsQueries).Should(Equal(ptr.To(100)))
|
||||||
|
Ω(stats).Should(HaveKey("bar"))
|
||||||
|
Ω(stats["bar"].NumDnsQueries).Should(Equal(ptr.To(200)))
|
||||||
|
Ω(stats).Should(HaveKey("aaa"))
|
||||||
|
Ω(stats["aaa"].NumDnsQueries).Should(Equal(ptr.To(300)))
|
||||||
|
|
||||||
|
os := getStats()
|
||||||
|
tot := os.Total()
|
||||||
|
Ω(*tot.NumDnsQueries).Should(Equal(600))
|
||||||
|
Ω(
|
||||||
|
*tot.DnsQueries,
|
||||||
|
).Should(Equal([]int{60, 120, 180, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
|
||||||
|
|
||||||
|
foo := os["foo"]
|
||||||
|
bar := os["bar"]
|
||||||
|
aaa := os["aaa"]
|
||||||
|
|
||||||
|
Ω(*foo.NumDnsQueries).Should(Equal(100))
|
||||||
|
Ω(*bar.NumDnsQueries).Should(Equal(200))
|
||||||
|
Ω(*aaa.NumDnsQueries).Should(Equal(300))
|
||||||
|
Ω(
|
||||||
|
*foo.DnsQueries,
|
||||||
|
).Should(Equal([]int{10, 20, 30, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
|
||||||
|
Ω(
|
||||||
|
*bar.DnsQueries,
|
||||||
|
).Should(Equal([]int{20, 40, 60, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
|
||||||
|
Ω(
|
||||||
|
*aaa.DnsQueries,
|
||||||
|
).Should(Equal([]int{30, 60, 90, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("StatsGraph", func() {
|
||||||
|
var metrics InstanceMetricsList
|
||||||
|
BeforeEach(func() {
|
||||||
|
err := faker.FakeData(&metrics)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should provide correct results with faked values", func() {
|
||||||
|
UpdateInstances(metrics)
|
||||||
|
|
||||||
|
_, dns, blocked, malware, adult := StatsGraph()
|
||||||
|
|
||||||
|
verifyStats(dns)
|
||||||
|
verifyStats(blocked)
|
||||||
|
verifyStats(malware)
|
||||||
|
verifyStats(adult)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
func verifyStats(lines []Line) {
|
||||||
|
var total Line
|
||||||
|
sum := make([]int, len(lines[0].Data))
|
||||||
|
for _, l := range lines {
|
||||||
|
if l.Title == labelTotal {
|
||||||
|
total = l
|
||||||
|
} else {
|
||||||
|
for i, d := range l.Data {
|
||||||
|
sum[i] += d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ω(sum).Should(Equal(total.Data))
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
package sync
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/metrics"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const labelTotal = "Total"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
blue = []int{78, 141, 245}
|
blue = []int{78, 141, 245}
|
||||||
blueAlternatives = [][]int{
|
blueAlternatives = [][]int{
|
||||||
@@ -46,19 +47,19 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func statsGraph() (*model.Stats, []line, []line, []line, []line) {
|
func StatsGraph() (t *model.Stats, dns, blocked, malware, adult []Line) {
|
||||||
s := metrics.GetStats()
|
s := getStats()
|
||||||
t := s.Total()
|
t = s.Total()
|
||||||
dns := graphLines(t, s, blue, blueAlternatives, func(s *model.Stats) []int {
|
dns = graphLines(t, s, blue, blueAlternatives, func(s *model.Stats) []int {
|
||||||
return safeStats(s.DnsQueries)
|
return safeStats(s.DnsQueries)
|
||||||
})
|
})
|
||||||
blocked := graphLines(t, s, red, redAlternatives, func(s *model.Stats) []int {
|
blocked = graphLines(t, s, red, redAlternatives, func(s *model.Stats) []int {
|
||||||
return safeStats(s.BlockedFiltering)
|
return safeStats(s.BlockedFiltering)
|
||||||
})
|
})
|
||||||
malware := graphLines(t, s, green, greenAlternatives, func(s *model.Stats) []int {
|
malware = graphLines(t, s, green, greenAlternatives, func(s *model.Stats) []int {
|
||||||
return safeStats(s.ReplacedSafebrowsing)
|
return safeStats(s.ReplacedSafebrowsing)
|
||||||
})
|
})
|
||||||
adult := graphLines(t, s, yellow, yellowAlternatives, func(s *model.Stats) []int {
|
adult = graphLines(t, s, yellow, yellowAlternatives, func(s *model.Stats) []int {
|
||||||
return safeStats(s.ReplacedParental)
|
return safeStats(s.ReplacedParental)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -74,15 +75,15 @@ func safeStats(stats *[]int) []int {
|
|||||||
|
|
||||||
func graphLines(
|
func graphLines(
|
||||||
t *model.Stats,
|
t *model.Stats,
|
||||||
s metrics.OverallStats,
|
s OverallStats,
|
||||||
baseColor []int,
|
baseColor []int,
|
||||||
altColors [][]int,
|
altColors [][]int,
|
||||||
dataCB func(s *model.Stats) []int,
|
dataCB func(s *model.Stats) []int,
|
||||||
) []line {
|
) []Line {
|
||||||
g := &graph{
|
g := &graph{
|
||||||
total: line{
|
total: Line{
|
||||||
Fill: true,
|
Fill: true,
|
||||||
Title: "Total",
|
Title: labelTotal,
|
||||||
Data: dataCB(t),
|
Data: dataCB(t),
|
||||||
R: baseColor[0],
|
R: baseColor[0],
|
||||||
G: baseColor[1],
|
G: baseColor[1],
|
||||||
@@ -92,8 +93,8 @@ func graphLines(
|
|||||||
|
|
||||||
var i int
|
var i int
|
||||||
for name, data := range s {
|
for name, data := range s {
|
||||||
if name != metrics.StatsTotal {
|
if name != StatsTotal {
|
||||||
g.replicas = append(g.replicas, line{
|
g.replicas = append(g.replicas, Line{
|
||||||
Fill: false,
|
Fill: false,
|
||||||
Title: name,
|
Title: name,
|
||||||
Data: dataCB(data),
|
Data: dataCB(data),
|
||||||
@@ -105,9 +106,9 @@ func graphLines(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := []line{g.total}
|
lines := []Line{g.total}
|
||||||
|
|
||||||
slices.SortFunc(g.replicas, func(a, b line) int {
|
slices.SortFunc(g.replicas, func(a, b Line) int {
|
||||||
return strings.Compare(a.Title, b.Title)
|
return strings.Compare(a.Title, b.Title)
|
||||||
})
|
})
|
||||||
lines = append(lines, g.replicas...)
|
lines = append(lines, g.replicas...)
|
||||||
@@ -115,11 +116,11 @@ func graphLines(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type graph struct {
|
type graph struct {
|
||||||
total line
|
total Line
|
||||||
replicas []line
|
replicas []Line
|
||||||
}
|
}
|
||||||
|
|
||||||
type line struct {
|
type Line struct {
|
||||||
Data []int `json:"data"`
|
Data []int `json:"data"`
|
||||||
R int `json:"r"`
|
R int `json:"r"`
|
||||||
G int `json:"g"`
|
G int `json:"g"`
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/bakito/adguardhome-sync/pkg/client (interfaces: Client)
|
// Source: github.com/bakito/adguardhome-sync/internal/client (interfaces: Client)
|
||||||
//
|
//
|
||||||
// Generated by this command:
|
// Generated by this command:
|
||||||
//
|
//
|
||||||
// mockgen -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
|
// mockgen -package client -destination internal/mocks/client/mock.go github.com/bakito/adguardhome-sync/internal/client Client
|
||||||
//
|
//
|
||||||
|
|
||||||
// Package client is a generated GoMock package.
|
// Package client is a generated GoMock package.
|
||||||
@@ -12,7 +12,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
model "github.com/bakito/adguardhome-sync/pkg/client/model"
|
model "github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "go.uber.org/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -384,17 +384,17 @@ func (mr *MockClientMockRecorder) SafeSearchConfig() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetAccessList mocks base method.
|
// SetAccessList mocks base method.
|
||||||
func (m *MockClient) SetAccessList(arg0 *model.AccessList) error {
|
func (m *MockClient) SetAccessList(accessList *model.AccessList) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetAccessList", arg0)
|
ret := m.ctrl.Call(m, "SetAccessList", accessList)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAccessList indicates an expected call of SetAccessList.
|
// SetAccessList indicates an expected call of SetAccessList.
|
||||||
func (mr *MockClientMockRecorder) SetAccessList(arg0 any) *gomock.Call {
|
func (mr *MockClientMockRecorder) SetAccessList(accessList any) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), accessList)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBlockedServicesSchedule mocks base method.
|
// SetBlockedServicesSchedule mocks base method.
|
||||||
@@ -426,31 +426,31 @@ func (mr *MockClientMockRecorder) SetCustomRules(rules any) *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetDNSConfig mocks base method.
|
// SetDNSConfig mocks base method.
|
||||||
func (m *MockClient) SetDNSConfig(arg0 *model.DNSConfig) error {
|
func (m *MockClient) SetDNSConfig(config *model.DNSConfig) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetDNSConfig", arg0)
|
ret := m.ctrl.Call(m, "SetDNSConfig", config)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDNSConfig indicates an expected call of SetDNSConfig.
|
// SetDNSConfig indicates an expected call of SetDNSConfig.
|
||||||
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 any) *gomock.Call {
|
func (mr *MockClientMockRecorder) SetDNSConfig(config any) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDhcpConfig mocks base method.
|
// SetDhcpConfig mocks base method.
|
||||||
func (m *MockClient) SetDhcpConfig(arg0 *model.DhcpStatus) error {
|
func (m *MockClient) SetDhcpConfig(status *model.DhcpStatus) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetDhcpConfig", arg0)
|
ret := m.ctrl.Call(m, "SetDhcpConfig", status)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDhcpConfig indicates an expected call of SetDhcpConfig.
|
// SetDhcpConfig indicates an expected call of SetDhcpConfig.
|
||||||
func (mr *MockClientMockRecorder) SetDhcpConfig(arg0 any) *gomock.Call {
|
func (mr *MockClientMockRecorder) SetDhcpConfig(status any) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetProfileInfo mocks base method.
|
// SetProfileInfo mocks base method.
|
||||||
@@ -468,17 +468,17 @@ func (mr *MockClientMockRecorder) SetProfileInfo(settings any) *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetQueryLogConfig mocks base method.
|
// SetQueryLogConfig mocks base method.
|
||||||
func (m *MockClient) SetQueryLogConfig(arg0 *model.QueryLogConfigWithIgnored) error {
|
func (m *MockClient) SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0)
|
ret := m.ctrl.Call(m, "SetQueryLogConfig", ql)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
|
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
|
||||||
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0 any) *gomock.Call {
|
func (mr *MockClientMockRecorder) SetQueryLogConfig(ql any) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), ql)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSafeSearchConfig mocks base method.
|
// SetSafeSearchConfig mocks base method.
|
||||||
@@ -496,7 +496,7 @@ func (mr *MockClientMockRecorder) SetSafeSearchConfig(settings any) *gomock.Call
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetStatsConfig mocks base method.
|
// SetStatsConfig mocks base method.
|
||||||
func (m *MockClient) SetStatsConfig(sc *model.GetStatsConfigResponse) error {
|
func (m *MockClient) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetStatsConfig", sc)
|
ret := m.ctrl.Call(m, "SetStatsConfig", sc)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@@ -509,6 +509,20 @@ func (mr *MockClientMockRecorder) SetStatsConfig(sc any) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), sc)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), sc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTLSConfig mocks base method.
|
||||||
|
func (m *MockClient) SetTLSConfig(tls *model.TlsConfig) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetTLSConfig", tls)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTLSConfig indicates an expected call of SetTLSConfig.
|
||||||
|
func (mr *MockClientMockRecorder) SetTLSConfig(tls any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTLSConfig", reflect.TypeOf((*MockClient)(nil).SetTLSConfig), tls)
|
||||||
|
}
|
||||||
|
|
||||||
// Setup mocks base method.
|
// Setup mocks base method.
|
||||||
func (m *MockClient) Setup() error {
|
func (m *MockClient) Setup() error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -568,6 +582,21 @@ func (mr *MockClientMockRecorder) Status() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TLSConfig mocks base method.
|
||||||
|
func (m *MockClient) TLSConfig() (*model.TlsConfig, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "TLSConfig")
|
||||||
|
ret0, _ := ret[0].(*model.TlsConfig)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig indicates an expected call of TLSConfig.
|
||||||
|
func (mr *MockClientMockRecorder) TLSConfig() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSConfig", reflect.TypeOf((*MockClient)(nil).TLSConfig))
|
||||||
|
}
|
||||||
|
|
||||||
// ToggleFiltering mocks base method.
|
// ToggleFiltering mocks base method.
|
||||||
func (m *MockClient) ToggleFiltering(enabled bool, interval int) error {
|
func (m *MockClient) ToggleFiltering(enabled bool, interval int) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/bakito/adguardhome-sync/pkg/config (interfaces: Flags)
|
// Source: github.com/bakito/adguardhome-sync/internal/config (interfaces: Flags)
|
||||||
//
|
//
|
||||||
// Generated by this command:
|
// Generated by this command:
|
||||||
//
|
//
|
||||||
// mockgen -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags
|
// mockgen -package client -destination internal/mocks/flags/mock.go github.com/bakito/adguardhome-sync/internal/config Flags
|
||||||
//
|
//
|
||||||
|
|
||||||
// Package client is a generated GoMock package.
|
// Package client is a generated GoMock package.
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
package sync
|
package sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -235,6 +236,22 @@ var (
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
tlsConfig = func(ac *actionContext) error {
|
||||||
|
tlsc, err := ac.client.TLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tlsc.Equals(ac.origin.tlsConfig) {
|
||||||
|
if err := ac.client.SetTLSConfig(ac.origin.tlsConfig); err != nil {
|
||||||
|
ac.rl.With("enabled", ac.origin.tlsConfig.Enabled, "error", err).Error("error setting tls config")
|
||||||
|
if !ac.cfg.ContinueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func syncFilterType(
|
func syncFilterType(
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
package sync
|
package sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupActions(cfg *types.Config) (actions []syncAction) {
|
func setupActions(cfg *types.Config) (actions []syncAction) {
|
||||||
@@ -67,6 +68,11 @@ func setupActions(cfg *types.Config) (actions []syncAction) {
|
|||||||
action("DHCP static leases", actionDHCPStaticLeases),
|
action("DHCP static leases", actionDHCPStaticLeases),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if cfg.Features.TLSConfig {
|
||||||
|
actions = append(actions,
|
||||||
|
action("TLS config", tlsConfig),
|
||||||
|
)
|
||||||
|
}
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,17 +14,12 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/metrics"
|
|
||||||
"github.com/bakito/adguardhome-sync/version"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
//go:embed index.html
|
"github.com/bakito/adguardhome-sync/internal/metrics"
|
||||||
index []byte
|
"github.com/bakito/adguardhome-sync/internal/sync/static"
|
||||||
//go:embed favicon.ico
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
favicon []byte
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (w *worker) handleSync(c *gin.Context) {
|
func (w *worker) handleSync(c *gin.Context) {
|
||||||
@@ -33,44 +28,37 @@ func (w *worker) handleSync(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) handleRoot(c *gin.Context) {
|
func (w *worker) handleRoot(c *gin.Context) {
|
||||||
total, dns, blocked, malware, adult := statsGraph()
|
total, dns, blocked, malware, adult := metrics.StatsGraph()
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "index.html", map[string]interface{}{
|
c.HTML(http.StatusOK, "index.html", map[string]any{
|
||||||
"DarkMode": w.cfg.API.DarkMode,
|
"DarkMode": w.cfg.API.DarkMode,
|
||||||
"Metrics": w.cfg.API.Metrics.Enabled,
|
"Metrics": w.cfg.API.Metrics.Enabled,
|
||||||
"Version": version.Version,
|
"Version": version.Version,
|
||||||
"Build": version.Build,
|
"Build": version.Build,
|
||||||
"SyncStatus": w.status(),
|
"SyncStatus": w.status(),
|
||||||
"Stats": map[string]interface{}{
|
"Stats": map[string]any{
|
||||||
"Labels": getLast24Hours(),
|
"Labels": getLast24Hours(),
|
||||||
"DNS": dns,
|
"DNS": dns,
|
||||||
"Blocked": blocked,
|
"Blocked": blocked,
|
||||||
"BlockedPercentage": fmt.Sprintf(
|
"BlockedPercentage": percent(total.NumBlockedFiltering, total.NumDnsQueries),
|
||||||
"%.2f",
|
"Malware": malware,
|
||||||
(float64(*total.NumBlockedFiltering)*100.0)/float64(*total.NumDnsQueries),
|
"MalwarePercentage": percent(total.NumReplacedSafebrowsing, total.NumDnsQueries),
|
||||||
),
|
"Adult": adult,
|
||||||
"Malware": malware,
|
"AdultPercentage": percent(total.NumReplacedParental, total.NumDnsQueries),
|
||||||
"MalwarePercentage": fmt.Sprintf(
|
"TotalDNS": total.NumDnsQueries,
|
||||||
"%.2f",
|
"TotalBlocked": total.NumBlockedFiltering,
|
||||||
(float64(*total.NumReplacedSafebrowsing)*100.0)/float64(*total.NumDnsQueries),
|
"TotalMalware": total.NumReplacedSafebrowsing,
|
||||||
),
|
"TotalAdult": total.NumReplacedParental,
|
||||||
"Adult": adult,
|
|
||||||
"AdultPercentage": fmt.Sprintf(
|
|
||||||
"%.2f",
|
|
||||||
(float64(*total.NumReplacedParental)*100.0)/float64(*total.NumDnsQueries),
|
|
||||||
),
|
|
||||||
|
|
||||||
"TotalDNS": total.NumDnsQueries,
|
|
||||||
"TotalBlocked": total.NumBlockedFiltering,
|
|
||||||
"TotalMalware": total.NumReplacedSafebrowsing,
|
|
||||||
"TotalAdult": total.NumReplacedParental,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) handleFavicon(c *gin.Context) {
|
func percent(a, b *int) string {
|
||||||
c.Data(http.StatusOK, "image/x-icon", favicon)
|
if a == nil || b == nil || *b == 0 {
|
||||||
|
return "0.00"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.2f", (float64(*a)*100.0)/float64(*b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) handleLogs(c *gin.Context) {
|
func (w *worker) handleLogs(c *gin.Context) {
|
||||||
@@ -86,6 +74,24 @@ func (w *worker) handleStatus(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, w.status())
|
c.JSON(http.StatusOK, w.status())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *worker) handleHealthz(c *gin.Context) {
|
||||||
|
status := w.status()
|
||||||
|
|
||||||
|
if status.Origin.Status != "success" {
|
||||||
|
c.Status(http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, replica := range status.Replicas {
|
||||||
|
if replica.Status != "success" {
|
||||||
|
c.Status(http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *worker) listenAndServe() {
|
func (w *worker) listenAndServe() {
|
||||||
sl := l.With("port", w.cfg.API.Port)
|
sl := l.With("port", w.cfg.API.Port)
|
||||||
if w.cfg.API.TLS.Enabled() {
|
if w.cfg.API.TLS.Enabled() {
|
||||||
@@ -99,9 +105,27 @@ func (w *worker) listenAndServe() {
|
|||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
r.Use(gin.Recovery())
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
r.HEAD("/healthz", w.handleHealthz)
|
||||||
|
r.GET("/healthz", w.handleHealthz)
|
||||||
|
|
||||||
|
var group gin.IRouter = r
|
||||||
if w.cfg.API.Username != "" && w.cfg.API.Password != "" {
|
if w.cfg.API.Username != "" && w.cfg.API.Password != "" {
|
||||||
r.Use(gin.BasicAuth(map[string]string{w.cfg.API.Username: w.cfg.API.Password}))
|
group = r.Group("/", gin.BasicAuth(map[string]string{w.cfg.API.Username: w.cfg.API.Password}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
group.POST("/api/v1/sync", w.handleSync)
|
||||||
|
group.GET("/api/v1/logs", w.handleLogs)
|
||||||
|
group.POST("/api/v1/clear-logs", w.handleClearLogs)
|
||||||
|
group.GET("/api/v1/status", w.handleStatus)
|
||||||
|
static.HandleResources(group, w.cfg.API.DarkMode)
|
||||||
|
group.GET("/", w.handleRoot)
|
||||||
|
if w.cfg.API.Metrics.Enabled {
|
||||||
|
group.GET("/metrics", metrics.Handler())
|
||||||
|
|
||||||
|
go w.startScraping()
|
||||||
|
}
|
||||||
|
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
|
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
|
||||||
Handler: r,
|
Handler: r,
|
||||||
@@ -109,18 +133,7 @@ func (w *worker) listenAndServe() {
|
|||||||
ReadHeaderTimeout: 1 * time.Second,
|
ReadHeaderTimeout: 1 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(string(index))))
|
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(static.Index())))
|
||||||
r.POST("/api/v1/sync", w.handleSync)
|
|
||||||
r.GET("/api/v1/logs", w.handleLogs)
|
|
||||||
r.POST("/api/v1/clear-logs", w.handleClearLogs)
|
|
||||||
r.GET("/api/v1/status", w.handleStatus)
|
|
||||||
r.GET("/favicon.ico", w.handleFavicon)
|
|
||||||
r.GET("/", w.handleRoot)
|
|
||||||
if w.cfg.API.Metrics.Enabled {
|
|
||||||
r.GET("/metrics", metrics.Handler())
|
|
||||||
|
|
||||||
go w.startScraping()
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var err error
|
var err error
|
||||||
@@ -169,8 +182,6 @@ func (w *worker) listenAndServe() {
|
|||||||
|
|
||||||
// manually cancel context if not using httpServer.RegisterOnShutdown(cancel)
|
// manually cancel context if not using httpServer.RegisterOnShutdown(cancel)
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
defer os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type syncStatus struct {
|
type syncStatus struct {
|
||||||
@@ -192,7 +203,7 @@ func getLast24Hours() []string {
|
|||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
|
|
||||||
// Loop to get the last 24 hours
|
// Loop to get the last 24 hours
|
||||||
for i := 0; i < 24; i++ {
|
for i := range 24 {
|
||||||
// Calculate the time for the current hour in the loop
|
// Calculate the time for the current hour in the loop
|
||||||
timeInstance := currentTime.Add(time.Duration(-i) * time.Hour)
|
timeInstance := currentTime.Add(time.Duration(-i) * time.Hour)
|
||||||
timeInstance = timeInstance.Truncate(time.Hour)
|
timeInstance = timeInstance.Truncate(time.Hour)
|
||||||
30
internal/sync/http_test.go
Normal file
30
internal/sync/http_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Percent", func() {
|
||||||
|
DescribeTable("calculating percentage",
|
||||||
|
func(a, b *int, want string) {
|
||||||
|
Expect(percent(a, b)).To(Equal(want))
|
||||||
|
},
|
||||||
|
Entry("both inputs are nil", nil, nil, "0.00"),
|
||||||
|
Entry("a is nil, b is non-zero", nil, intPtr(10), "0.00"),
|
||||||
|
Entry("b is nil, a is non-zero", intPtr(10), nil, "0.00"),
|
||||||
|
Entry("b is zero", intPtr(10), intPtr(0), "0.00"),
|
||||||
|
Entry("normal case with positive int values", intPtr(25), intPtr(100), "25.00"),
|
||||||
|
Entry("a and b are equal", intPtr(50), intPtr(50), "100.00"),
|
||||||
|
Entry("a is zero, b is positive", intPtr(0), intPtr(50), "0.00"),
|
||||||
|
Entry("large positive values", intPtr(1000), intPtr(4000), "25.00"),
|
||||||
|
Entry("a greater than b", intPtr(150), intPtr(100), "150.00"),
|
||||||
|
Entry("negative values for a and b", intPtr(-25), intPtr(-50), "50.00"),
|
||||||
|
Entry("a is positive, b is negative", intPtr(25), intPtr(-50), "-50.00"),
|
||||||
|
Entry("a is negative, b is positive", intPtr(-25), intPtr(50), "-50.00"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
func intPtr(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ package sync
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/metrics"
|
"github.com/bakito/adguardhome-sync/internal/metrics"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (w *worker) startScraping() {
|
func (w *worker) startScraping() {
|
||||||
@@ -26,25 +26,26 @@ func (w *worker) startScraping() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) scrape() {
|
func (w *worker) scrape() {
|
||||||
var ims []metrics.InstanceMetrics
|
var iml metrics.InstanceMetricsList
|
||||||
|
|
||||||
ims = append(ims, w.getMetrics(w.cfg.Origin))
|
iml.Metrics = append(iml.Metrics, w.getMetrics(*w.cfg.Origin))
|
||||||
for _, replica := range w.cfg.Replicas {
|
for _, replica := range w.cfg.Replicas {
|
||||||
ims = append(ims, w.getMetrics(replica))
|
iml.Metrics = append(iml.Metrics, w.getMetrics(replica))
|
||||||
}
|
}
|
||||||
metrics.Update(ims...)
|
metrics.UpdateInstances(iml)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) getMetrics(inst types.AdGuardInstance) (im metrics.InstanceMetrics) {
|
func (w *worker) getMetrics(inst types.AdGuardInstance) metrics.InstanceMetrics {
|
||||||
|
var im metrics.InstanceMetrics
|
||||||
client, err := w.createClient(inst)
|
client, err := w.createClient(inst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
||||||
return
|
return im
|
||||||
}
|
}
|
||||||
|
|
||||||
im.HostName = inst.Host
|
im.HostName = inst.Host
|
||||||
im.Status, _ = client.Status()
|
im.Status, _ = client.Status()
|
||||||
im.Stats, _ = client.Stats()
|
im.Stats, _ = client.Stats()
|
||||||
im.QueryLog, _ = client.QueryLog(w.cfg.API.Metrics.QueryLogLimit)
|
im.QueryLog, _ = client.QueryLog(w.cfg.API.Metrics.QueryLogLimit)
|
||||||
return
|
return im
|
||||||
}
|
}
|
||||||
6
internal/sync/static/bootstrap.min-5.3.3.css
vendored
Normal file
6
internal/sync/static/bootstrap.min-5.3.3.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
internal/sync/static/bootstrap.min-5.3.3.js
vendored
Normal file
7
internal/sync/static/bootstrap.min-5.3.3.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
internal/sync/static/bootstrap.min-darkly-5.3.css
vendored
Normal file
12
internal/sync/static/bootstrap.min-darkly-5.3.css
vendored
Normal file
File diff suppressed because one or more lines are too long
20
internal/sync/static/chart.umd.min-4.4.7.js
Normal file
20
internal/sync/static/chart.umd.min-4.4.7.js
Normal file
File diff suppressed because one or more lines are too long
BIN
internal/sync/static/favicon.ico
Normal file
BIN
internal/sync/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -1,16 +1,8 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>AdGuardHome sync</title>
|
<title>AdGuard Home sync</title>
|
||||||
<script type="text/javascript" src="https://code.jquery.com/jquery-3.7.1.min.js">
|
<script type="text/javascript" src="lib/jquery.js"></script>
|
||||||
</script>
|
<link rel="stylesheet" href="lib/bootstrap.css">
|
||||||
{{- if .DarkMode }}
|
|
||||||
<link rel="stylesheet" href="https://bootswatch.com/5/darkly/bootstrap.min.css"
|
|
||||||
crossorigin="anonymous">
|
|
||||||
{{- else }}
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
|
||||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
|
||||||
crossorigin="anonymous">
|
|
||||||
{{- end }}
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$("#showLogs").click(function () {
|
$("#showLogs").click(function () {
|
||||||
@@ -86,10 +78,13 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container-fluid px-4">
|
<div class="container-fluid px-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<p class="h1">
|
<div class="d-flex align-items-center mb-3">
|
||||||
AdGuardHome sync
|
<img src="logo.svg" alt="Logo" class="me-3" style="height: 4em;">
|
||||||
<p class="h6">{{ .Version }} ({{ .Build }})</p>
|
<div>
|
||||||
</p>
|
<h1 class="mb-0">AdGuard Home sync</h1>
|
||||||
|
<p class="h6 text-muted mb-0">{{ .Version }} ({{ .Build }})</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{- if .Metrics }}
|
{{- if .Metrics }}
|
||||||
<div class="row g-4 d-flex">
|
<div class="row g-4 d-flex">
|
||||||
@@ -165,16 +160,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- openssl dgst -sha384 -binary popper.min.js | openssl base64 -A -->
|
<!-- openssl dgst -sha384 -binary popper.min.js | openssl base64 -A -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
|
<script src="lib/popper.js" ></script>
|
||||||
integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous">
|
<script src="lib/bootstrap.js" ></script>
|
||||||
</script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
|
|
||||||
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous">
|
|
||||||
</script>
|
|
||||||
{{- if .Metrics }}
|
{{- if .Metrics }}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"ŝ
|
<script src="lib/chart.js" ></script>
|
||||||
integrity="sha384-vsrfeLOOY6KuIYKDlmVH5UiBmgIdB1oEf7p01YgWHuqmOHfZr374+odEv96n9tNC" crossorigin="anonymous">
|
|
||||||
</script>
|
|
||||||
<script>
|
<script>
|
||||||
// Function to create minimal line charts
|
// Function to create minimal line charts
|
||||||
function createChart(canvasId, data) {
|
function createChart(canvasId, data) {
|
||||||
2
internal/sync/static/jquery-3.7.1.min.js
vendored
Normal file
2
internal/sync/static/jquery-3.7.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
internal/sync/static/logo.svg
Normal file
1
internal/sync/static/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" id="svg3" version="1.2" viewBox="0 0 1000 1000"><defs id="defs3"><linearGradient id="swatch26"><stop style="stop-color:#000;stop-opacity:1" id="stop26" offset="0"/></linearGradient><linearGradient id="swatch25"><stop style="stop-color:#407b28;stop-opacity:1" id="stop25" offset="0"/></linearGradient></defs><path id="path1" fill="#68bc71" d="m 993.75002,114.1 c 0,171.8 3.1,595.3 -493.8,885.9 C 3.0500233,709.4 6.2500233,285.9 6.2500233,114.1 159.35002,35.9 345.25002,0 499.95002,0 c 154.7,0 340.6,35.9 493.8,114.1 z"/><path id="path2" fill="#67b279" d="M500 1000C3.1 709.4 6.2 285.9 6.2 114.1 159.4 35.9 345.3 0 500 0z"/><path id="path3" fill="#fff" d="m 225,449.6 c 15,-11.7 80,-53.4 128.3,1.6 L 453.3,569.6 720,297.9 c 11.7,-10 31.7,-23.3 55,-5 L 455,716.2 Z" style="display:none"/><circle style="display:none;fill:#fff;fill-opacity:.5;stroke:#fff;stroke-width:8e-08;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:.502604" id="path18" cx="-500" cy="426" r="400" transform="scale(-1,1)"/><path id="path12-8" style="fill:#fff;fill-opacity:1;stroke:#fff;stroke-width:4.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:1" d="m -75.867249,555.75073 c -1.469322,-39.92741 -12.296964,-76.43624 -30.823861,-104.61661 -21.05517,28.18037 -47.89663,45.38022 -75.18829,53.68483 l 42.51192,20.42415 c -33.35434,61.94297 -101.60764,105.3289 -167.51689,101.23666 -42.42317,-1.43193 -82.3318,-22.18867 -112.01355,-52.33871 -21.09944,-21.43232 -53.37423,-31.44115 -79.38397,-16.1329 l -10.3958,6.11853 c 43.49744,68.86447 120.54256,113.80911 201.79332,112.04834 87.97224,-4.41047 162.60077,-62.73987 194.96448,-137.74518 z M -537.90127,346.51705 c 1.46924,39.92741 12.29681,76.43626 30.82364,104.61667 21.05523,-28.18031 47.89674,-45.38011 75.18842,-53.68467 l -42.51189,-20.42423 c 33.35441,-61.94281 101.60759,-105.32857 167.51673,-101.23635 42.4233,1.43194 82.33203,22.18879 112.01381,52.33897 21.0994,21.43235 53.37417,31.44124 79.38393,16.13305 l 10.39581,-6.11852 c -43.49739,-68.86468 -120.54264,-113.8095 -201.79355,-112.04872 -87.97208,4.41046 -162.60049,62.73965 -194.96429,137.74477 z" transform="matrix(0,-1.3167476,1.3167476,0,-94.029776,21.910887)"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
6
internal/sync/static/popper.min-2.9.2.js
Normal file
6
internal/sync/static/popper.min-2.9.2.js
Normal file
File diff suppressed because one or more lines are too long
80
internal/sync/static/static.go
Normal file
80
internal/sync/static/static.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package static
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed index.html
|
||||||
|
index string
|
||||||
|
|
||||||
|
//go:embed favicon.ico
|
||||||
|
favicon []byte
|
||||||
|
|
||||||
|
//go:embed logo.svg
|
||||||
|
logo []byte
|
||||||
|
|
||||||
|
//go:embed bootstrap.min-5.3.3.js
|
||||||
|
bootstrapJS []byte
|
||||||
|
|
||||||
|
// https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css
|
||||||
|
//go:embed bootstrap.min-5.3.3.css
|
||||||
|
bootstrapCSS []byte
|
||||||
|
|
||||||
|
// https://bootswatch.com/5/darkly/bootstrap.min.css
|
||||||
|
//go:embed bootstrap.min-darkly-5.3.css
|
||||||
|
bootstrapDarkCSS []byte
|
||||||
|
|
||||||
|
// https://code.jquery.com/jquery-3.7.1.min.js
|
||||||
|
//go:embed jquery-3.7.1.min.js
|
||||||
|
jquery []byte
|
||||||
|
|
||||||
|
// https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js
|
||||||
|
//go:embed popper.min-2.9.2.js
|
||||||
|
popper []byte
|
||||||
|
|
||||||
|
// https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js
|
||||||
|
//go:embed chart.umd.min-4.4.7.js
|
||||||
|
chart []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleFavicon(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "image/x-icon", favicon)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleLogo(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "image/svg+xml", logo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Index() string {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleResources(group gin.IRouter, dark bool) {
|
||||||
|
group.GET("/favicon.ico", handleFavicon)
|
||||||
|
group.GET("/logo.svg", handleLogo)
|
||||||
|
group.GET("/lib/jquery.js", handleJS(jquery))
|
||||||
|
group.GET("/lib/popper.js", handleJS(popper))
|
||||||
|
group.GET("/lib/chart.js", handleJS(chart))
|
||||||
|
group.GET("/lib/bootstrap.js", handleJS(bootstrapJS))
|
||||||
|
if dark {
|
||||||
|
group.GET("/lib/bootstrap.css", handleCSS(bootstrapDarkCSS))
|
||||||
|
} else {
|
||||||
|
group.GET("/lib/bootstrap.css", handleCSS(bootstrapCSS))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleJS(bytes []byte) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "application/javascript", bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCSS(bytes []byte) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "text/css", bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,31 +3,34 @@ package sync
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/versions"
|
|
||||||
"github.com/bakito/adguardhome-sync/version"
|
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/metrics"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/versions"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var l = log.GetLogger("sync")
|
var l = log.GetLogger("sync")
|
||||||
|
|
||||||
// Sync config from origin to replica
|
// Sync config from origin to replica.
|
||||||
func Sync(cfg *types.Config) error {
|
func Sync(cfg *types.Config) error {
|
||||||
if cfg.Origin.URL == "" {
|
if cfg.Origin.URL == "" {
|
||||||
return fmt.Errorf("origin URL is required")
|
return errors.New("origin URL is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.UniqueReplicas()) == 0 {
|
if len(cfg.UniqueReplicas()) == 0 {
|
||||||
return fmt.Errorf("no replicas configured")
|
return errors.New("no replicas configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
l.With(
|
l.With(
|
||||||
@@ -41,10 +44,8 @@ func Sync(cfg *types.Config) error {
|
|||||||
cfg.Origin.AutoSetup = false
|
cfg.Origin.AutoSetup = false
|
||||||
|
|
||||||
w := &worker{
|
w := &worker{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
createClient: func(ai types.AdGuardInstance) (client.Client, error) {
|
createClient: client.New,
|
||||||
return client.New(ai)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if cfg.Cron != "" {
|
if cfg.Cron != "" {
|
||||||
w.cron = cron.New()
|
w.cron = cron.New()
|
||||||
@@ -100,7 +101,7 @@ type worker struct {
|
|||||||
|
|
||||||
func (w *worker) status() *syncStatus {
|
func (w *worker) status() *syncStatus {
|
||||||
syncStatus := &syncStatus{
|
syncStatus := &syncStatus{
|
||||||
Origin: w.getStatus(w.cfg.Origin),
|
Origin: w.getStatus(*w.cfg.Origin),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, replica := range w.cfg.Replicas {
|
for _, replica := range w.cfg.Replicas {
|
||||||
@@ -120,15 +121,15 @@ func (w *worker) status() *syncStatus {
|
|||||||
return syncStatus
|
return syncStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) {
|
func (w *worker) getStatus(inst types.AdGuardInstance) replicaStatus {
|
||||||
st = replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
|
st := replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
|
||||||
|
|
||||||
oc, err := w.createClient(inst)
|
oc, err := w.createClient(inst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
||||||
st.Status = "danger"
|
st.Status = "danger"
|
||||||
st.Error = err.Error()
|
st.Error = err.Error()
|
||||||
return
|
return st
|
||||||
}
|
}
|
||||||
sl := l.With("from", inst.WebHost)
|
sl := l.With("from", inst.WebHost)
|
||||||
status, err := oc.Status()
|
status, err := oc.Status()
|
||||||
@@ -136,16 +137,16 @@ func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) {
|
|||||||
if errors.Is(err, client.ErrSetupNeeded) {
|
if errors.Is(err, client.ErrSetupNeeded) {
|
||||||
st.Status = "warning"
|
st.Status = "warning"
|
||||||
st.Error = err.Error()
|
st.Error = err.Error()
|
||||||
return
|
return st
|
||||||
}
|
}
|
||||||
sl.With("error", err).Error("Error getting origin status")
|
sl.With("error", err).Error("Error getting origin status")
|
||||||
st.Status = "danger"
|
st.Status = "danger"
|
||||||
st.Error = err.Error()
|
st.Error = err.Error()
|
||||||
return
|
return st
|
||||||
}
|
}
|
||||||
st.Status = "success"
|
st.Status = "success"
|
||||||
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
|
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
|
||||||
return
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) sync() {
|
func (w *worker) sync() {
|
||||||
@@ -154,9 +155,11 @@ func (w *worker) sync() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.running = true
|
w.running = true
|
||||||
defer func() { w.running = false }()
|
defer func() {
|
||||||
|
w.running = false
|
||||||
|
}()
|
||||||
|
|
||||||
oc, err := w.createClient(w.cfg.Origin)
|
oc, err := w.createClient(*w.cfg.Origin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
||||||
return
|
return
|
||||||
@@ -182,7 +185,14 @@ func (w *worker) sync() {
|
|||||||
o.profileInfo, err = oc.ProfileInfo()
|
o.profileInfo, err = oc.ProfileInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sl.With("error", err).Error("Error getting profileInfo info")
|
sl.With("error", err).Error("Error getting profileInfo info")
|
||||||
return
|
|
||||||
|
// Workaround for https://github.com/AdguardTeam/AdGuardHome/issues/7987
|
||||||
|
// and https://github.com/AdguardTeam/AdGuardHome/issues/7985
|
||||||
|
|
||||||
|
clientErr := &client.Error{}
|
||||||
|
if !w.cfg.ContinueOnError || !errors.As(err, &clientErr) || clientErr.Code() != http.StatusUnauthorized {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
o.parental, err = oc.Parental()
|
o.parental, err = oc.Parental()
|
||||||
@@ -254,6 +264,14 @@ func (w *worker) sync() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if w.cfg.Features.TLSConfig {
|
||||||
|
o.tlsConfig, err = oc.TLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting tls config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
w.actions = setupActions(w.cfg)
|
w.actions = setupActions(w.cfg)
|
||||||
|
|
||||||
replicas := w.cfg.UniqueReplicas()
|
replicas := w.cfg.UniqueReplicas()
|
||||||
@@ -271,10 +289,23 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
|
|||||||
|
|
||||||
rl := l.With("to", rc.Host())
|
rl := l.With("to", rc.Host())
|
||||||
rl.Info("Start sync")
|
rl.Info("Start sync")
|
||||||
|
start := time.Now()
|
||||||
|
withError := false
|
||||||
|
delta := time.Since(start).Seconds()
|
||||||
|
defer func() {
|
||||||
|
metrics.UpdateResult(rc.Host(), !withError, delta)
|
||||||
|
doneLog := rl.With("duration", fmt.Sprintf("%vs", delta))
|
||||||
|
if withError {
|
||||||
|
doneLog.Error("Sync done")
|
||||||
|
} else {
|
||||||
|
doneLog.Info("Sync done")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
replicaStatus, err := w.statusWithSetup(rl, replica, rc)
|
replicaStatus, err := w.statusWithSetup(rl, replica, rc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rl.With("error", err).Error("Error getting replica status")
|
rl.With("error", err).Error("Error getting replica status")
|
||||||
|
withError = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +314,7 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
|
|||||||
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
|
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
|
||||||
rl.With("error", err, "version", replicaStatus.Version).
|
rl.With("error", err, "version", replicaStatus.Version).
|
||||||
Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
|
Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
|
||||||
|
withError = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,16 +331,16 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
|
|||||||
client: rc,
|
client: rc,
|
||||||
replica: replica,
|
replica: replica,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, action := range w.actions {
|
for _, action := range w.actions {
|
||||||
if err := action.sync(ac); err != nil {
|
if err := action.sync(ac); err != nil {
|
||||||
rl.With("error", err).Errorf("Error syncing %s", action.name())
|
rl.With("error", err).Errorf("Error syncing %s", action.name())
|
||||||
|
withError = true
|
||||||
if !w.cfg.ContinueOnError {
|
if !w.cfg.ContinueOnError {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.Info("Sync done")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) statusWithSetup(
|
func (w *worker) statusWithSetup(
|
||||||
@@ -345,4 +377,5 @@ type origin struct {
|
|||||||
safeSearch *model.SafeSearchConfig
|
safeSearch *model.SafeSearchConfig
|
||||||
profileInfo *model.ProfileInfo
|
profileInfo *model.ProfileInfo
|
||||||
safeBrowsing bool
|
safeBrowsing bool
|
||||||
|
tlsConfig *model.TlsConfig
|
||||||
}
|
}
|
||||||
@@ -3,16 +3,17 @@ package sync
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
||||||
clientmock "github.com/bakito/adguardhome-sync/pkg/mocks/client"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/versions"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
gm "go.uber.org/mock/gomock"
|
gm "go.uber.org/mock/gomock"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
clientmock "github.com/bakito/adguardhome-sync/internal/mocks/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/versions"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Sync", func() {
|
var _ = Describe("Sync", func() {
|
||||||
@@ -575,7 +576,7 @@ var _ = Describe("Sync", func() {
|
|||||||
Context("sync", func() {
|
Context("sync", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
w.cfg = &types.Config{
|
w.cfg = &types.Config{
|
||||||
Origin: types.AdGuardInstance{},
|
Origin: &types.AdGuardInstance{},
|
||||||
Replica: &types.AdGuardInstance{URL: "foo"},
|
Replica: &types.AdGuardInstance{URL: "foo"},
|
||||||
Features: types.Features{
|
Features: types.Features{
|
||||||
DHCP: types.DHCP{
|
DHCP: types.DHCP{
|
||||||
@@ -593,12 +594,13 @@ var _ = Describe("Sync", func() {
|
|||||||
GeneralSettings: true,
|
GeneralSettings: true,
|
||||||
StatsConfig: true,
|
StatsConfig: true,
|
||||||
QueryLogConfig: true,
|
QueryLogConfig: true,
|
||||||
|
TLSConfig: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
It("should have no changes", func() {
|
It("should have no changes", func() {
|
||||||
// origin
|
// origin
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host().Times(2)
|
||||||
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
cl.EXPECT().Parental()
|
cl.EXPECT().Parental()
|
||||||
@@ -613,6 +615,7 @@ var _ = Describe("Sync", func() {
|
|||||||
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
|
|
||||||
// replica
|
// replica
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host()
|
||||||
@@ -632,13 +635,14 @@ var _ = Describe("Sync", func() {
|
|||||||
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
w.sync()
|
w.sync()
|
||||||
})
|
})
|
||||||
It("should not sync DHCP", func() {
|
It("should not sync DHCP", func() {
|
||||||
w.cfg.Features.DHCP.ServerConfig = false
|
w.cfg.Features.DHCP.ServerConfig = false
|
||||||
w.cfg.Features.DHCP.StaticLeases = false
|
w.cfg.Features.DHCP.StaticLeases = false
|
||||||
// origin
|
// origin
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host().Times(2)
|
||||||
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
cl.EXPECT().Parental()
|
cl.EXPECT().Parental()
|
||||||
@@ -652,6 +656,7 @@ var _ = Describe("Sync", func() {
|
|||||||
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
|
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
|
||||||
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
|
|
||||||
// replica
|
// replica
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host()
|
||||||
@@ -670,6 +675,7 @@ var _ = Describe("Sync", func() {
|
|||||||
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
w.sync()
|
w.sync()
|
||||||
})
|
})
|
||||||
It("origin version is too small", func() {
|
It("origin version is too small", func() {
|
||||||
@@ -695,9 +701,10 @@ var _ = Describe("Sync", func() {
|
|||||||
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
|
|
||||||
// replica
|
// replica
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host().Times(2)
|
||||||
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
|
||||||
w.sync()
|
w.sync()
|
||||||
})
|
})
|
||||||
44
internal/test/matchers/matchers.go
Normal file
44
internal/test/matchers/matchers.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package matchers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/onsi/gomega/format"
|
||||||
|
"github.com/onsi/gomega/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type equalIgnoringLineEndingsMatcher struct {
|
||||||
|
expected string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (matcher *equalIgnoringLineEndingsMatcher) Match(actual any) (success bool, err error) {
|
||||||
|
actualStr, ok := actual.(string)
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedActual := strings.ReplaceAll(actualStr, "\r\n", "\n")
|
||||||
|
normalizedExpected := strings.ReplaceAll(matcher.expected, "\r\n", "\n")
|
||||||
|
|
||||||
|
return normalizedActual == normalizedExpected, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (matcher *equalIgnoringLineEndingsMatcher) FailureMessage(actual any) (message string) {
|
||||||
|
actualString, actualOK := actual.(string)
|
||||||
|
if actualOK {
|
||||||
|
return format.MessageWithDiff(actualString, "to equal", matcher.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
return format.Message(actual, "to equal", matcher.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (matcher *equalIgnoringLineEndingsMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||||
|
return format.Message(actual, "not to equal", matcher.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EqualIgnoringLineEndings returns a new matcher.
|
||||||
|
func EqualIgnoringLineEndings(expected string) types.GomegaMatcher {
|
||||||
|
return &equalIgnoringLineEndingsMatcher{
|
||||||
|
expected: expected,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,36 +22,38 @@ func NewFeatures(enabled bool) Features {
|
|||||||
Services: enabled,
|
Services: enabled,
|
||||||
Filters: enabled,
|
Filters: enabled,
|
||||||
Theme: enabled,
|
Theme: enabled,
|
||||||
|
TLSConfig: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Features feature flags
|
// Features feature flags.
|
||||||
type Features struct {
|
type Features struct {
|
||||||
DNS DNS `json:"dns" yaml:"dns"`
|
DNS DNS `json:"dns" yaml:"dns"`
|
||||||
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
|
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
|
||||||
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" env:"FEATURES_GENERAL_SETTINGS"`
|
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" documentation:"Sync general settings" env:"FEATURES_GENERAL_SETTINGS"`
|
||||||
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" env:"FEATURES_QUERY_LOG_CONFIG"`
|
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" documentation:"Sync query log config" env:"FEATURES_QUERY_LOG_CONFIG"`
|
||||||
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" env:"FEATURES_STATS_CONFIG"`
|
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" documentation:"Sync stats config" env:"FEATURES_STATS_CONFIG"`
|
||||||
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" env:"FEATURES_CLIENT_SETTINGS"`
|
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" documentation:"Sync client settings" env:"FEATURES_CLIENT_SETTINGS"`
|
||||||
Services bool `json:"services" yaml:"services" env:"FEATURES_SERVICES"`
|
Services bool `json:"services" yaml:"services" documentation:"Sync services" env:"FEATURES_SERVICES"`
|
||||||
Filters bool `json:"filters" yaml:"filters" env:"FEATURES_FILTERS"`
|
Filters bool `json:"filters" yaml:"filters" documentation:"Sync filters" env:"FEATURES_FILTERS"`
|
||||||
Theme bool `json:"theme" yaml:"theme" env:"FEATURES_THEME"`
|
Theme bool `json:"theme" yaml:"theme" documentation:"Sync the web UI theme" env:"FEATURES_THEME"`
|
||||||
|
TLSConfig bool `json:"tlsConfig" yaml:"tlsConfig" documentation:"Sync the TLS config" env:"FEATURES_TLS_CONFIG"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DHCP features
|
// DHCP features.
|
||||||
type DHCP struct {
|
type DHCP struct {
|
||||||
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DHCP_SERVER_CONFIG"`
|
ServerConfig bool `documentation:"Sync DHCP server config" env:"FEATURES_DHCP_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
|
||||||
StaticLeases bool `json:"staticLeases" yaml:"staticLeases" env:"FEATURES_DHCP_STATIC_LEASES"`
|
StaticLeases bool `documentation:"Sync DHCP static leases" env:"FEATURES_DHCP_STATIC_LEASES" json:"staticLeases" yaml:"staticLeases"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS features
|
// DNS features.
|
||||||
type DNS struct {
|
type DNS struct {
|
||||||
AccessLists bool `json:"accessLists" yaml:"accessLists" env:"FEATURES_DNS_ACCESS_LISTS"`
|
AccessLists bool `documentation:"Sync DNS access lists" env:"FEATURES_DNS_ACCESS_LISTS" json:"accessLists" yaml:"accessLists"`
|
||||||
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DNS_SERVER_CONFIG"`
|
ServerConfig bool `documentation:"Sync DNS server config" env:"FEATURES_DNS_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
|
||||||
Rewrites bool `json:"rewrites" yaml:"rewrites" env:"FEATURES_DNS_REWRITES"`
|
Rewrites bool `documentation:"Sync DNS rewrites" env:"FEATURES_DNS_REWRITES" json:"rewrites" yaml:"rewrites"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogDisabled log all disabled features
|
// LogDisabled log all disabled features.
|
||||||
func (f *Features) LogDisabled(l *zap.SugaredLogger) {
|
func (f *Features) LogDisabled(l *zap.SugaredLogger) {
|
||||||
features := f.collectDisabled()
|
features := f.collectDisabled()
|
||||||
|
|
||||||
@@ -95,5 +97,8 @@ func (f *Features) collectDisabled() []string {
|
|||||||
if !f.Filters {
|
if !f.Filters {
|
||||||
features = append(features, "Filters")
|
features = append(features, "Filters")
|
||||||
}
|
}
|
||||||
|
if !f.TLSConfig {
|
||||||
|
features = append(features, "TLSConfig")
|
||||||
|
}
|
||||||
return features
|
return features
|
||||||
}
|
}
|
||||||
222
internal/types/types.go
Normal file
222
internal/types/types.go
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
// Package types
|
||||||
|
// +kubebuilder:object:generate=true
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultAPIPath default api path.
|
||||||
|
DefaultAPIPath = "/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config application configuration struct
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
|
type Config struct {
|
||||||
|
// Origin adguardhome instance
|
||||||
|
Origin *AdGuardInstance `json:"origin" yaml:"origin"`
|
||||||
|
// One single replica adguardhome instance
|
||||||
|
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
|
||||||
|
// Multiple replica instances
|
||||||
|
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty" faker:"slice_len=2"`
|
||||||
|
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" documentation:"Cron expression for the sync interval" env:"CRON"`
|
||||||
|
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" documentation:"Run the sync on startup" env:"RUN_ON_START"`
|
||||||
|
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" documentation:"Print current config only and stop the application" env:"PRINT_CONFIG_ONLY"`
|
||||||
|
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" documentation:"Continue sync on errors" env:"CONTINUE_ON_ERROR"`
|
||||||
|
API API `json:"api,omitempty" yaml:"api,omitempty"`
|
||||||
|
Features Features `json:"features,omitempty" yaml:"features,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// API configuration.
|
||||||
|
type API struct {
|
||||||
|
Port int `documentation:"API port (API is disabled if port is set to 0)" env:"API_PORT" json:"port,omitempty" yaml:"port,omitempty"`
|
||||||
|
Username string `documentation:"API username" env:"API_USERNAME" json:"username,omitempty" yaml:"username,omitempty"`
|
||||||
|
Password string `documentation:"API password" env:"API_PASSWORD" json:"password,omitempty" yaml:"password,omitempty"`
|
||||||
|
DarkMode bool `documentation:"API dark mode" env:"API_DARK_MODE" json:"darkMode,omitempty" yaml:"darkMode,omitempty"`
|
||||||
|
Metrics Metrics ` json:"metrics,omitempty" yaml:"metrics,omitempty"`
|
||||||
|
TLS TLS ` json:"tls,omitempty" yaml:"tls,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metrics configuration.
|
||||||
|
type Metrics struct {
|
||||||
|
Enabled bool `documentation:"Enable metrics" env:"API_METRICS_ENABLED" json:"enabled,omitempty" yaml:"enabled,omitempty"`
|
||||||
|
ScrapeInterval time.Duration `documentation:"Interval for metrics scraping" env:"API_METRICS_SCRAPE_INTERVAL" json:"scrapeInterval,omitempty" yaml:"scrapeInterval,omitempty"`
|
||||||
|
QueryLogLimit int `documentation:"Metrics log query limit" env:"API_METRICS_QUERY_LOG_LIMIT" json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS configuration.
|
||||||
|
type TLS struct {
|
||||||
|
CertDir string `documentation:"API TLS certificate directory" env:"API_TLS_CERT_DIR" json:"certDir,omitempty" yaml:"certDir,omitempty"`
|
||||||
|
CertName string `documentation:"API TLS certificate file name" env:"API_TLS_CERT_NAME" json:"certName,omitempty" yaml:"certName,omitempty"`
|
||||||
|
KeyName string `documentation:"API TLS key file name" env:"API_TLS_KEY_NAME" json:"keyName,omitempty" yaml:"keyName,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TLS) Enabled() bool {
|
||||||
|
return strings.TrimSpace(t.CertDir) != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TLS) Certs() (cert, key string) {
|
||||||
|
cert = filepath.Join(t.CertDir, defaultIfEmpty(t.CertName, "tls.crt"))
|
||||||
|
key = filepath.Join(t.CertDir, defaultIfEmpty(t.KeyName, "tls.key"))
|
||||||
|
return cert, key
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultIfEmpty(val, fallback string) string {
|
||||||
|
if strings.TrimSpace(val) == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask maks username and password.
|
||||||
|
func (a *API) Mask() {
|
||||||
|
a.Username = mask(a.Username)
|
||||||
|
a.Password = mask(a.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueReplicas get unique replication instances.
|
||||||
|
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
|
||||||
|
dedup := make(map[string]AdGuardInstance)
|
||||||
|
if cfg.Replica != nil && cfg.Replica.URL != "" {
|
||||||
|
if cfg.Replica.APIPath == "" {
|
||||||
|
cfg.Replica.APIPath = DefaultAPIPath
|
||||||
|
}
|
||||||
|
dedup[cfg.Replica.Key()] = *cfg.Replica
|
||||||
|
}
|
||||||
|
for _, replica := range cfg.Replicas {
|
||||||
|
if replica.APIPath == "" {
|
||||||
|
replica.APIPath = DefaultAPIPath
|
||||||
|
}
|
||||||
|
if replica.URL != "" {
|
||||||
|
dedup[replica.Key()] = replica
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r []AdGuardInstance
|
||||||
|
for _, replica := range dedup {
|
||||||
|
r = append(r, replica)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the current config.
|
||||||
|
func (cfg *Config) Log(l *zap.SugaredLogger) {
|
||||||
|
c := cfg.mask()
|
||||||
|
l.With("config", c).Debug("Using config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) mask() *Config {
|
||||||
|
c := cfg.DeepCopy()
|
||||||
|
c.Origin.Mask()
|
||||||
|
if c.Replica != nil {
|
||||||
|
if c.Replica.URL == "" {
|
||||||
|
c.Replica = nil
|
||||||
|
} else {
|
||||||
|
c.Replica.Mask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range c.Replicas {
|
||||||
|
c.Replicas[i].Mask()
|
||||||
|
}
|
||||||
|
c.API.Mask()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) Init() error {
|
||||||
|
if err := cfg.Origin.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range cfg.Replicas {
|
||||||
|
replica := &cfg.Replicas[i]
|
||||||
|
if err := replica.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdGuardInstance AdguardHome config instance
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
|
type AdGuardInstance struct {
|
||||||
|
URL string `documentation:"URL of adguardhome instance" env:"URL" faker:"url" json:"url" yaml:"url"`
|
||||||
|
WebURL string `documentation:"Web URL of adguardhome instance" env:"WEB_URL" faker:"url" json:"webURL" yaml:"webURL"`
|
||||||
|
APIPath string `documentation:"API Path" env:"API_PATH" json:"apiPath,omitempty" yaml:"apiPath,omitempty"`
|
||||||
|
Username string `documentation:"Adguardhome username" env:"USERNAME" json:"username,omitempty" yaml:"username,omitempty"`
|
||||||
|
Password string `documentation:"Adguardhome password" env:"PASSWORD" json:"password,omitempty" yaml:"password,omitempty"`
|
||||||
|
Cookie string `documentation:"Adguardhome cookie" env:"COOKIE" json:"cookie,omitempty" yaml:"cookie,omitempty"`
|
||||||
|
RequestHeaders map[string]string `documentation:"Request Headers 'key1:value1,key2:value2'" env:"REQUEST_HEADERS" json:"requestHeaders,omitempty" yaml:"requestHeaders,omitempty"`
|
||||||
|
InsecureSkipVerify bool `documentation:"Skip TLS verification" env:"INSECURE_SKIP_VERIFY" json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
|
||||||
|
AutoSetup bool `documentation:"Automatically setup the instance if it is not initialized" env:"AUTO_SETUP" json:"autoSetup" yaml:"autoSetup"`
|
||||||
|
InterfaceName string `documentation:"Network interface name" env:"INTERFACE_NAME" json:"interfaceName,omitempty" yaml:"interfaceName,omitempty"`
|
||||||
|
DHCPServerEnabled *bool `documentation:"Enable DHCP server" env:"DHCP_SERVER_ENABLED" json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty"`
|
||||||
|
|
||||||
|
Host string `json:"-" yaml:"-"`
|
||||||
|
WebHost string `json:"-" yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key AdGuardInstance key.
|
||||||
|
func (i *AdGuardInstance) Key() string {
|
||||||
|
return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask maks username and password.
|
||||||
|
func (i *AdGuardInstance) Mask() {
|
||||||
|
i.Username = mask(i.Username)
|
||||||
|
i.Password = mask(i.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *AdGuardInstance) Init() error {
|
||||||
|
u, err := url.Parse(i.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Host = u.Host
|
||||||
|
|
||||||
|
if i.WebURL == "" {
|
||||||
|
i.WebHost = i.Host
|
||||||
|
i.WebURL = i.URL
|
||||||
|
} else {
|
||||||
|
u, err := url.Parse(i.WebURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.WebHost = u.Host
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mask(s string) string {
|
||||||
|
if len(s) < 3 {
|
||||||
|
return strings.Repeat("*", len(s))
|
||||||
|
}
|
||||||
|
mask := strings.Repeat("*", len(s)-2)
|
||||||
|
return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protection API struct.
|
||||||
|
type Protection struct {
|
||||||
|
ProtectionEnabled bool `json:"protection_enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallConfig AdguardHome install config.
|
||||||
|
type InstallConfig struct {
|
||||||
|
Web InstallPort `json:"web"`
|
||||||
|
DNS InstallPort `json:"dns"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallPort AdguardHome install config port.
|
||||||
|
type InstallPort struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CanAutofix bool `json:"can_autofix"`
|
||||||
|
}
|
||||||
15
internal/types/types_fuzz_test.go
Normal file
15
internal/types/types_fuzz_test.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FuzzMask(f *testing.F) {
|
||||||
|
testcases := []string{"", "a", "ab", "abc", "abcd"}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
f.Add(tc)
|
||||||
|
}
|
||||||
|
f.Fuzz(func(t *testing.T, value string) {
|
||||||
|
_ = mask(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
@@ -39,6 +41,7 @@ var _ = Describe("Types", func() {
|
|||||||
Context("Config", func() {
|
Context("Config", func() {
|
||||||
Context("init", func() {
|
Context("init", func() {
|
||||||
cfg := Config{
|
cfg := Config{
|
||||||
|
Origin: &AdGuardInstance{},
|
||||||
Replicas: []AdGuardInstance{
|
Replicas: []AdGuardInstance{
|
||||||
{URL: "https://localhost:3000"},
|
{URL: "https://localhost:3000"},
|
||||||
},
|
},
|
||||||
@@ -53,6 +56,7 @@ var _ = Describe("Types", func() {
|
|||||||
Context("UniqueReplicas", func() {
|
Context("UniqueReplicas", func() {
|
||||||
It("should return unique replicas in the array", func() {
|
It("should return unique replicas in the array", func() {
|
||||||
cfg := Config{
|
cfg := Config{
|
||||||
|
Origin: &AdGuardInstance{},
|
||||||
Replicas: []AdGuardInstance{
|
Replicas: []AdGuardInstance{
|
||||||
{URL: "a"},
|
{URL: "a"},
|
||||||
{URL: "a", APIPath: DefaultAPIPath},
|
{URL: "a", APIPath: DefaultAPIPath},
|
||||||
@@ -68,6 +72,7 @@ var _ = Describe("Types", func() {
|
|||||||
Context("mask", func() {
|
Context("mask", func() {
|
||||||
It("should mask all names and passwords", func() {
|
It("should mask all names and passwords", func() {
|
||||||
cfg := Config{
|
cfg := Config{
|
||||||
|
Origin: &AdGuardInstance{},
|
||||||
Replicas: []AdGuardInstance{
|
Replicas: []AdGuardInstance{
|
||||||
{URL: "a", Username: "user", Password: "pass"},
|
{URL: "a", Username: "user", Password: "pass"},
|
||||||
},
|
},
|
||||||
@@ -83,16 +88,25 @@ var _ = Describe("Types", func() {
|
|||||||
Ω(masked.API.Password).Should(Equal("p**s"))
|
Ω(masked.API.Password).Should(Equal("p**s"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
DescribeTable("mask should work correctly",
|
||||||
|
func(value, expected string) {
|
||||||
|
Ω(mask(value)).Should(Equal(expected))
|
||||||
|
},
|
||||||
|
Entry(`Empty password`, "", ""),
|
||||||
|
Entry(`1 char password`, "a", "*"),
|
||||||
|
Entry(`2 char password`, "ab", "**"),
|
||||||
|
Entry(`3 char password`, "abc", "a*c"),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
Context("Feature", func() {
|
Context("Feature", func() {
|
||||||
Context("LogDisabled", func() {
|
Context("LogDisabled", func() {
|
||||||
It("should log all features", func() {
|
It("should log all features", func() {
|
||||||
f := NewFeatures(false)
|
f := NewFeatures(false)
|
||||||
Ω(f.collectDisabled()).Should(HaveLen(11))
|
Ω(f.collectDisabled()).Should(HaveLen(12))
|
||||||
})
|
})
|
||||||
It("should log no features", func() {
|
It("should log no features", func() {
|
||||||
f := NewFeatures(true)
|
f := NewFeatures(true)
|
||||||
Ω(f.collectDisabled()).Should(BeEmpty())
|
Ω(f.collectDisabled()).Should(HaveLen(1))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -115,6 +129,8 @@ var _ = Describe("Types", func() {
|
|||||||
Context("Certs", func() {
|
Context("Certs", func() {
|
||||||
It("should use default crt and key", func() {
|
It("should use default crt and key", func() {
|
||||||
crt, key := t.Certs()
|
crt, key := t.Certs()
|
||||||
|
crt = normalizePath(crt)
|
||||||
|
key = normalizePath(key)
|
||||||
Ω(crt).Should(Equal("/path/to/certs/tls.crt"))
|
Ω(crt).Should(Equal("/path/to/certs/tls.crt"))
|
||||||
Ω(key).Should(Equal("/path/to/certs/tls.key"))
|
Ω(key).Should(Equal("/path/to/certs/tls.key"))
|
||||||
})
|
})
|
||||||
@@ -122,9 +138,15 @@ var _ = Describe("Types", func() {
|
|||||||
t.CertName = "foo.crt"
|
t.CertName = "foo.crt"
|
||||||
t.KeyName = "bar.key"
|
t.KeyName = "bar.key"
|
||||||
crt, key := t.Certs()
|
crt, key := t.Certs()
|
||||||
|
crt = normalizePath(crt)
|
||||||
|
key = normalizePath(key)
|
||||||
Ω(crt).Should(Equal("/path/to/certs/foo.crt"))
|
Ω(crt).Should(Equal("/path/to/certs/foo.crt"))
|
||||||
Ω(key).Should(Equal("/path/to/certs/bar.key"))
|
Ω(key).Should(Equal("/path/to/certs/bar.key"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
func normalizePath(path string) string {
|
||||||
|
return strings.ReplaceAll(path, "\\", "/")
|
||||||
|
}
|
||||||
209
internal/types/zz_generated.deepcopy.go
Normal file
209
internal/types/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
|
||||||
|
// Code generated by controller-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import ()
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *API) DeepCopyInto(out *API) {
|
||||||
|
*out = *in
|
||||||
|
out.Metrics = in.Metrics
|
||||||
|
out.TLS = in.TLS
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new API.
|
||||||
|
func (in *API) DeepCopy() *API {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(API)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AdGuardInstance) DeepCopyInto(out *AdGuardInstance) {
|
||||||
|
*out = *in
|
||||||
|
if in.RequestHeaders != nil {
|
||||||
|
in, out := &in.RequestHeaders, &out.RequestHeaders
|
||||||
|
*out = make(map[string]string, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.DHCPServerEnabled != nil {
|
||||||
|
in, out := &in.DHCPServerEnabled, &out.DHCPServerEnabled
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdGuardInstance.
|
||||||
|
func (in *AdGuardInstance) DeepCopy() *AdGuardInstance {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AdGuardInstance)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Config) DeepCopyInto(out *Config) {
|
||||||
|
*out = *in
|
||||||
|
if in.Origin != nil {
|
||||||
|
in, out := &in.Origin, &out.Origin
|
||||||
|
*out = new(AdGuardInstance)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.Replica != nil {
|
||||||
|
in, out := &in.Replica, &out.Replica
|
||||||
|
*out = new(AdGuardInstance)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.Replicas != nil {
|
||||||
|
in, out := &in.Replicas, &out.Replicas
|
||||||
|
*out = make([]AdGuardInstance, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.API = in.API
|
||||||
|
out.Features = in.Features
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
|
||||||
|
func (in *Config) DeepCopy() *Config {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Config)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *DHCP) DeepCopyInto(out *DHCP) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DHCP.
|
||||||
|
func (in *DHCP) DeepCopy() *DHCP {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(DHCP)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *DNS) DeepCopyInto(out *DNS) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNS.
|
||||||
|
func (in *DNS) DeepCopy() *DNS {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(DNS)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Features) DeepCopyInto(out *Features) {
|
||||||
|
*out = *in
|
||||||
|
out.DNS = in.DNS
|
||||||
|
out.DHCP = in.DHCP
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Features.
|
||||||
|
func (in *Features) DeepCopy() *Features {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Features)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *InstallConfig) DeepCopyInto(out *InstallConfig) {
|
||||||
|
*out = *in
|
||||||
|
out.Web = in.Web
|
||||||
|
out.DNS = in.DNS
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallConfig.
|
||||||
|
func (in *InstallConfig) DeepCopy() *InstallConfig {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(InstallConfig)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *InstallPort) DeepCopyInto(out *InstallPort) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallPort.
|
||||||
|
func (in *InstallPort) DeepCopy() *InstallPort {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(InstallPort)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Metrics) DeepCopyInto(out *Metrics) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Metrics.
|
||||||
|
func (in *Metrics) DeepCopy() *Metrics {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Metrics)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Protection) DeepCopyInto(out *Protection) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Protection.
|
||||||
|
func (in *Protection) DeepCopy() *Protection {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Protection)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TLS) DeepCopyInto(out *TLS) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS.
|
||||||
|
func (in *TLS) DeepCopy() *TLS {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TLS)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
18
internal/utils/clone.go
Normal file
18
internal/utils/clone.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Clone[I any](in, out I) I {
|
||||||
|
b, _ := json.Marshal(in)
|
||||||
|
_ = json.Unmarshal(b, out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func JSONEquals(a, b any) bool {
|
||||||
|
ja, _ := json.Marshal(a)
|
||||||
|
jb, _ := json.Marshal(b)
|
||||||
|
return bytes.Equal(ja, jb)
|
||||||
|
}
|
||||||
@@ -2,18 +2,18 @@ package utils
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func Ptr[I interface{}](i I) *I {
|
func Ptr[I any](i I) *I {
|
||||||
return &i
|
return &i
|
||||||
}
|
}
|
||||||
|
|
||||||
func PtrToString[I interface{}](i *I) string {
|
func PtrToString[I any](i *I) string {
|
||||||
if i == nil {
|
if i == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%v", i)
|
return fmt.Sprintf("%v", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PtrEquals[I comparable](a *I, b *I) bool {
|
func PtrEquals[I comparable](a, b *I) bool {
|
||||||
if a == nil && b == nil {
|
if a == nil && b == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -3,15 +3,15 @@ package versions
|
|||||||
import "golang.org/x/mod/semver"
|
import "golang.org/x/mod/semver"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// MinAgh minimal adguardhome version
|
// MinAgh minimal adguardhome version.
|
||||||
MinAgh = "v0.107.40"
|
MinAgh = "v0.107.40"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsNewerThan(v1 string, v2 string) bool {
|
func IsNewerThan(v1, v2 string) bool {
|
||||||
return semver.Compare(sanitize(v1), sanitize(v2)) == 1
|
return semver.Compare(sanitize(v1), sanitize(v2)) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsSame(v1 string, v2 string) bool {
|
func IsSame(v1, v2 string) bool {
|
||||||
return semver.Compare(sanitize(v1), sanitize(v2)) == 0
|
return semver.Compare(sanitize(v1), sanitize(v2)) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package versions_test
|
package versions_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bakito/adguardhome-sync/pkg/versions"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/versions"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Versions", func() {
|
var _ = Describe("Versions", func() {
|
||||||
@@ -13,6 +14,11 @@ var _ = Describe("Versions", func() {
|
|||||||
Ω(versions.IsNewerThan("v0.106.9", "v0.106.10")).Should(BeFalse())
|
Ω(versions.IsNewerThan("v0.106.9", "v0.106.10")).Should(BeFalse())
|
||||||
Ω(versions.IsNewerThan("v0.106.10", "0.106.9")).Should(BeTrue())
|
Ω(versions.IsNewerThan("v0.106.10", "0.106.9")).Should(BeTrue())
|
||||||
Ω(versions.IsNewerThan("v0.106.9", "0.106.10")).Should(BeFalse())
|
Ω(versions.IsNewerThan("v0.106.9", "0.106.10")).Should(BeFalse())
|
||||||
|
// tests for #607
|
||||||
|
Ω(versions.IsNewerThan("v0.108.0-b.72", versions.MinAgh)).Should(BeTrue())
|
||||||
|
Ω(versions.IsNewerThan("0.108.0-b.72", versions.MinAgh)).Should(BeTrue())
|
||||||
|
Ω(versions.IsNewerThan(versions.MinAgh, "v0.108.0-b.72")).Should(BeFalse())
|
||||||
|
Ω(versions.IsNewerThan(versions.MinAgh, "0.108.0-b.72")).Should(BeFalse())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("IsSame", func() {
|
Context("IsSame", func() {
|
||||||
1
media/adguardhome-sync.svg
Normal file
1
media/adguardhome-sync.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" id="svg3" version="1.2" viewBox="0 0 1000 1000"><defs id="defs3"><linearGradient id="swatch26"><stop style="stop-color:#000;stop-opacity:1" id="stop26" offset="0"/></linearGradient><linearGradient id="swatch25"><stop style="stop-color:#407b28;stop-opacity:1" id="stop25" offset="0"/></linearGradient></defs><path id="path1" fill="#68bc71" d="m 993.75002,114.1 c 0,171.8 3.1,595.3 -493.8,885.9 C 3.0500233,709.4 6.2500233,285.9 6.2500233,114.1 159.35002,35.9 345.25002,0 499.95002,0 c 154.7,0 340.6,35.9 493.8,114.1 z"/><path id="path2" fill="#67b279" d="M500 1000C3.1 709.4 6.2 285.9 6.2 114.1 159.4 35.9 345.3 0 500 0z"/><path id="path3" fill="#fff" d="m 225,449.6 c 15,-11.7 80,-53.4 128.3,1.6 L 453.3,569.6 720,297.9 c 11.7,-10 31.7,-23.3 55,-5 L 455,716.2 Z" style="display:none"/><circle style="display:none;fill:#fff;fill-opacity:.5;stroke:#fff;stroke-width:8e-08;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:.502604" id="path18" cx="-500" cy="426" r="400" transform="scale(-1,1)"/><path id="path12-8" style="fill:#fff;fill-opacity:1;stroke:#fff;stroke-width:4.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:1" d="m -75.867249,555.75073 c -1.469322,-39.92741 -12.296964,-76.43624 -30.823861,-104.61661 -21.05517,28.18037 -47.89663,45.38022 -75.18829,53.68483 l 42.51192,20.42415 c -33.35434,61.94297 -101.60764,105.3289 -167.51689,101.23666 -42.42317,-1.43193 -82.3318,-22.18867 -112.01355,-52.33871 -21.09944,-21.43232 -53.37423,-31.44115 -79.38397,-16.1329 l -10.3958,6.11853 c 43.49744,68.86447 120.54256,113.80911 201.79332,112.04834 87.97224,-4.41047 162.60077,-62.73987 194.96448,-137.74518 z M -537.90127,346.51705 c 1.46924,39.92741 12.29681,76.43626 30.82364,104.61667 21.05523,-28.18031 47.89674,-45.38011 75.18842,-53.68467 l -42.51189,-20.42423 c 33.35441,-61.94281 101.60759,-105.32857 167.51673,-101.23635 42.4233,1.43194 82.33203,22.18879 112.01381,52.33897 21.0994,21.43235 53.37417,31.44124 79.38393,16.13305 l 10.39581,-6.11852 c -43.49739,-68.86468 -120.54264,-113.8095 -201.79355,-112.04872 -87.97208,4.41046 -162.60049,62.73965 -194.96429,137.74477 z" transform="matrix(0,-1.3167476,1.3167476,0,-94.029776,21.910887)"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,139 +0,0 @@
|
|||||||
package config_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/config"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
var envVars = []string{
|
|
||||||
"FEATURES_GENERAL_SETTINGS",
|
|
||||||
"FEATURES_QUERY_LOG_CONFIG",
|
|
||||||
"FEATURES_STATS_CONFIG",
|
|
||||||
"FEATURES_CLIENT_SETTINGS",
|
|
||||||
"FEATURES_SERVICES",
|
|
||||||
"FEATURES_FILTERS",
|
|
||||||
"FEATURES_THEME",
|
|
||||||
"FEATURES_DHCP_SERVER_CONFIG",
|
|
||||||
"FEATURES_DHCP_STATIC_LEASES",
|
|
||||||
"FEATURES_DNS_SERVER_CONFIG",
|
|
||||||
"FEATURES_DNS_ACCESS_LISTS",
|
|
||||||
"FEATURES_DNS_REWRITES",
|
|
||||||
"REPLICA1_INTERFACE_NAME",
|
|
||||||
"REPLICA1_DHCP_SERVER_ENABLED",
|
|
||||||
}
|
|
||||||
|
|
||||||
var deprecatedEnvVars = []string{
|
|
||||||
"FEATURES_GENERALSETTINGS",
|
|
||||||
"FEATURES_QUERYLOGCONFIG",
|
|
||||||
"FEATURES_STATSCONFIG",
|
|
||||||
"FEATURES_CLIENTSETTINGS",
|
|
||||||
"FEATURES_SERVICES",
|
|
||||||
"FEATURES_FILTERS",
|
|
||||||
"FEATURES_DHCP_SERVERCONFIG",
|
|
||||||
"FEATURES_DHCP_STATICLEASES",
|
|
||||||
"FEATURES_DNS_SERVERCONFIG",
|
|
||||||
"FEATURES_DNS_ACCESSLISTS",
|
|
||||||
"FEATURES_DNS_REWRITES",
|
|
||||||
"REPLICA1_INTERFACENAME",
|
|
||||||
"REPLICA1_DHCPSERVERENABLED",
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Describe("Config", func() {
|
|
||||||
Context("deprecated", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
for _, envVar := range deprecatedEnvVars {
|
|
||||||
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
AfterEach(func() {
|
|
||||||
for _, envVar := range deprecatedEnvVars {
|
|
||||||
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Context("Get", func() {
|
|
||||||
It("features should be false", func() {
|
|
||||||
cfg, _, _, err := config.Get("", nil)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
verifyFeatures(cfg, false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("current", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
for _, envVar := range envVars {
|
|
||||||
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
AfterEach(func() {
|
|
||||||
for _, envVar := range envVars {
|
|
||||||
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Context("Get", func() {
|
|
||||||
It("features should be true by default", func() {
|
|
||||||
cfg, _, _, err := config.Get("", nil)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
verifyFeatures(cfg, true)
|
|
||||||
})
|
|
||||||
It("features should be true by default", func() {
|
|
||||||
cfg, _, _, err := config.Get("", nil)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
verifyFeatures(cfg, true)
|
|
||||||
})
|
|
||||||
It("features should be false", func() {
|
|
||||||
for _, envVar := range envVars {
|
|
||||||
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
|
|
||||||
}
|
|
||||||
cfg, _, _, err := config.Get("", nil)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
verifyFeatures(cfg, false)
|
|
||||||
})
|
|
||||||
Context("interface name", func() {
|
|
||||||
It("should set interface name of replica 1", func() {
|
|
||||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
|
||||||
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_INTERFACE_NAME", "1"), "eth0")).ShouldNot(HaveOccurred())
|
|
||||||
cfg, _, _, err := config.Get("", nil)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("dhcp server", func() {
|
|
||||||
It("should enable the dhcp server of replica 1", func() {
|
|
||||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
|
||||||
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "true")).ShouldNot(HaveOccurred())
|
|
||||||
cfg, _, _, err := config.Get("", nil)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
|
||||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
|
|
||||||
})
|
|
||||||
It("should disable the dhcp server of replica 1", func() {
|
|
||||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
|
||||||
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "false")).ShouldNot(HaveOccurred())
|
|
||||||
cfg, _, _, err := config.Get("", nil)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
|
||||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func verifyFeatures(cfg *types.Config, value bool) {
|
|
||||||
Ω(cfg.Features.GeneralSettings).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.QueryLogConfig).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.StatsConfig).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.ClientSettings).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.Services).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.Filters).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.DHCP.ServerConfig).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.DHCP.StaticLeases).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.DNS.ServerConfig).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.DNS.AccessLists).Should(Equal(value))
|
|
||||||
Ω(cfg.Features.DNS.Rewrites).Should(Equal(value))
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
|
||||||
"github.com/caarlos0/env/v11"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleDeprecatedEnvVars(cfg *types.Config) {
|
|
||||||
if val, ok := checkDeprecatedEnvVar("RUNONSTART", "RUN_ON_START"); ok {
|
|
||||||
cfg.RunOnStart, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("API_DARKMODE", "API_DARK_MODE"); ok {
|
|
||||||
cfg.API.DarkMode, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("FEATURES_GENERALSETTINGS", "FEATURES_GENERAL_SETTINGS"); ok {
|
|
||||||
cfg.Features.GeneralSettings, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("FEATURES_QUERYLOGCONFIG", "FEATURES_QUERY_LOG_CONFIG"); ok {
|
|
||||||
cfg.Features.QueryLogConfig, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("FEATURES_STATSCONFIG", "FEATURES_STATS_CONFIG"); ok {
|
|
||||||
cfg.Features.StatsConfig, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("FEATURES_CLIENTSETTINGS", "FEATURES_CLIENT_SETTINGS"); ok {
|
|
||||||
cfg.Features.ClientSettings, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_SERVERCONFIG", "FEATURES_DHCP_SERVER_CONFIG"); ok {
|
|
||||||
cfg.Features.DHCP.ServerConfig, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_STATICLEASES", "FEATURES_DHCP_STATIC_LEASES"); ok {
|
|
||||||
cfg.Features.DHCP.StaticLeases, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_ACCESSLISTS", "FEATURES_DNS_ACCESS_LISTS"); ok {
|
|
||||||
cfg.Features.DNS.AccessLists, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_SERVERCONFIG", "FEATURES_DNS_SERVER_CONFIG"); ok {
|
|
||||||
cfg.Features.DNS.ServerConfig, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Replica != nil {
|
|
||||||
if val, ok := checkDeprecatedEnvVar("REPLICA_WEBURL", "REPLICA_WEB_URL"); ok {
|
|
||||||
cfg.Replica.WebURL = val
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("REPLICA_AUTOSETUP", "REPLICA_AUTO_SETUP"); ok {
|
|
||||||
cfg.Replica.AutoSetup, _ = strconv.ParseBool(val)
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("REPLICA_INTERFACENAME", "REPLICA_INTERFACE_NAME"); ok {
|
|
||||||
cfg.Replica.InterfaceName = val
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedEnvVar("REPLICA_DHCPSERVERENABLED", "REPLICA_DHCP_SERVER_ENABLED"); ok {
|
|
||||||
if b, err := strconv.ParseBool(val); err != nil {
|
|
||||||
cfg.Replica.DHCPServerEnabled = utils.Ptr(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkDeprecatedEnvVar(oldName string, newName string) (string, bool) {
|
|
||||||
old, oldOK := os.LookupEnv(oldName)
|
|
||||||
if oldOK {
|
|
||||||
logger.With("deprecated", oldName, "replacement", newName).
|
|
||||||
Warn("Deprecated env variable is used, please use the correct one")
|
|
||||||
}
|
|
||||||
new, newOK := os.LookupEnv(newName)
|
|
||||||
if newOK {
|
|
||||||
return new, true
|
|
||||||
}
|
|
||||||
return old, oldOK
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkDeprecatedReplicaEnvVar(oldPattern string, newPattern string, replicaID int) (string, bool) {
|
|
||||||
return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manually collect replicas from env.
|
|
||||||
func enrichReplicasFromEnv(initialReplicas []types.AdGuardInstance) ([]types.AdGuardInstance, error) {
|
|
||||||
var replicas []types.AdGuardInstance
|
|
||||||
for _, v := range os.Environ() {
|
|
||||||
if envReplicasURLPattern.MatchString(v) {
|
|
||||||
sm := envReplicasURLPattern.FindStringSubmatch(v)
|
|
||||||
id, _ := strconv.Atoi(sm[1])
|
|
||||||
|
|
||||||
if id <= 0 {
|
|
||||||
return nil, fmt.Errorf("numbered replica env variables must have a number id >= 1, got %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if id > len(initialReplicas) {
|
|
||||||
replicas = append(replicas, types.AdGuardInstance{URL: sm[2]})
|
|
||||||
} else {
|
|
||||||
re := initialReplicas[id-1]
|
|
||||||
re.URL = sm[2]
|
|
||||||
replicas = append(replicas, re)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(replicas) == 0 {
|
|
||||||
replicas = initialReplicas
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range replicas {
|
|
||||||
reID := i + 1
|
|
||||||
|
|
||||||
// keep previously set value
|
|
||||||
replicaDhcpServer := replicas[i].DHCPServerEnabled
|
|
||||||
replicas[i].DHCPServerEnabled = nil
|
|
||||||
if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if replicas[i].DHCPServerEnabled == nil {
|
|
||||||
replicas[i].DHCPServerEnabled = replicaDhcpServer
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_APIPATH", "REPLICA%d_API_PATH", reID); ok {
|
|
||||||
replicas[i].APIPath = val
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INSECURESKIPVERIFY", "REPLICA%d_INSECURE_SKIP_VERIFY", reID); ok {
|
|
||||||
replicas[i].InsecureSkipVerify = strings.EqualFold(val, "true")
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_AUTOSETUP", "REPLICA%d_AUTO_SETUP", reID); ok {
|
|
||||||
replicas[i].AutoSetup = strings.EqualFold(val, "true")
|
|
||||||
}
|
|
||||||
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INTERFACENAME", "REPLICA%d_INTERFACE_NAME", reID); ok {
|
|
||||||
replicas[i].InterfaceName = val
|
|
||||||
}
|
|
||||||
|
|
||||||
if dhcpEnabled, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_DHCPSERVERENABLED", "REPLICA%d_DHCP_SERVER_ENABLED", reID); ok {
|
|
||||||
if strings.EqualFold(dhcpEnabled, "true") {
|
|
||||||
replicas[i].DHCPServerEnabled = utils.Ptr(true)
|
|
||||||
} else if strings.EqualFold(dhcpEnabled, "false") {
|
|
||||||
replicas[i].DHCPServerEnabled = utils.Ptr(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if replicas[i].APIPath == "" {
|
|
||||||
replicas[i].APIPath = "/control"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return replicas, nil
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,58 +0,0 @@
|
|||||||
//go:build !ignore_autogenerated
|
|
||||||
// +build !ignore_autogenerated
|
|
||||||
|
|
||||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *AdGuardInstance) DeepCopyInto(out *AdGuardInstance) {
|
|
||||||
*out = *in
|
|
||||||
if in.DHCPServerEnabled != nil {
|
|
||||||
in, out := &in.DHCPServerEnabled, &out.DHCPServerEnabled
|
|
||||||
*out = new(bool)
|
|
||||||
**out = **in
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdGuardInstance.
|
|
||||||
func (in *AdGuardInstance) DeepCopy() *AdGuardInstance {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(AdGuardInstance)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *Config) DeepCopyInto(out *Config) {
|
|
||||||
*out = *in
|
|
||||||
in.Origin.DeepCopyInto(&out.Origin)
|
|
||||||
if in.Replica != nil {
|
|
||||||
in, out := &in.Replica, &out.Replica
|
|
||||||
*out = new(AdGuardInstance)
|
|
||||||
(*in).DeepCopyInto(*out)
|
|
||||||
}
|
|
||||||
if in.Replicas != nil {
|
|
||||||
in, out := &in.Replicas, &out.Replicas
|
|
||||||
*out = make([]AdGuardInstance, len(*in))
|
|
||||||
for i := range *in {
|
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.API = in.API
|
|
||||||
out.Features = in.Features
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
|
|
||||||
func (in *Config) DeepCopy() *Config {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(Config)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultAPIPath default api path
|
|
||||||
DefaultAPIPath = "/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config application configuration struct
|
|
||||||
// +k8s:deepcopy-gen=true
|
|
||||||
type Config struct {
|
|
||||||
Origin AdGuardInstance `json:"origin" yaml:"origin" env:"ORIGIN"`
|
|
||||||
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" env:"REPLICA"`
|
|
||||||
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty" faker:"slice_len=2"`
|
|
||||||
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" env:"CRON"`
|
|
||||||
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" env:"RUN_ON_START"`
|
|
||||||
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" env:"PRINT_CONFIG_ONLY"`
|
|
||||||
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" env:"CONTINUE_ON_ERROR"`
|
|
||||||
API API `json:"api,omitempty" yaml:"api,omitempty" env:"API"`
|
|
||||||
Features Features `json:"features,omitempty" yaml:"features,omitempty" env:"FEATURES_"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// API configuration
|
|
||||||
type API struct {
|
|
||||||
Port int `json:"port,omitempty" yaml:"port,omitempty" env:"API_PORT"`
|
|
||||||
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"API_USERNAME"`
|
|
||||||
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"API_PASSWORD"`
|
|
||||||
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" env:"API_DARK_MODE"`
|
|
||||||
Metrics Metrics `json:"metrics,omitempty" yaml:"metrics,omitempty" env:"API_METRICS"`
|
|
||||||
TLS TLS `json:"tls,omitempty" yaml:"tls,omitempty" env:"API_TLS"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metrics configuration
|
|
||||||
type Metrics struct {
|
|
||||||
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" env:"API_METRICS_ENABLED"`
|
|
||||||
ScrapeInterval time.Duration `json:"scrapeInterval,omitempty" yaml:"scrapeInterval,omitempty" env:"API_METRICS_SCRAPE_INTERVAL"`
|
|
||||||
QueryLogLimit int `json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty" env:"API_METRICS_QUERY_LOG_LIMIT"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLS configuration
|
|
||||||
type TLS struct {
|
|
||||||
CertDir string `json:"certDir,omitempty" yaml:"certDir,omitempty" env:"API_TLS_CERT_DIR"`
|
|
||||||
CertName string `json:"certName,omitempty" yaml:"certName,omitempty" env:"API_TLS_CERT_NAME"`
|
|
||||||
KeyName string `json:"keyName,omitempty" yaml:"keyName,omitempty" env:"API_TLS_KEY_NAME"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TLS) Enabled() bool {
|
|
||||||
return strings.TrimSpace(t.CertDir) != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t TLS) Certs() (cert string, key string) {
|
|
||||||
cert = filepath.Join(t.CertDir, defaultIfEmpty(t.CertName, "tls.crt"))
|
|
||||||
key = filepath.Join(t.CertDir, defaultIfEmpty(t.KeyName, "tls.key"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultIfEmpty(val string, fallback string) string {
|
|
||||||
if strings.TrimSpace(val) == "" {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mask maks username and password
|
|
||||||
func (a *API) Mask() {
|
|
||||||
a.Username = mask(a.Username)
|
|
||||||
a.Password = mask(a.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniqueReplicas get unique replication instances
|
|
||||||
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
|
|
||||||
dedup := make(map[string]AdGuardInstance)
|
|
||||||
if cfg.Replica != nil && cfg.Replica.URL != "" {
|
|
||||||
if cfg.Replica.APIPath == "" {
|
|
||||||
cfg.Replica.APIPath = DefaultAPIPath
|
|
||||||
}
|
|
||||||
dedup[cfg.Replica.Key()] = *cfg.Replica
|
|
||||||
}
|
|
||||||
for _, replica := range cfg.Replicas {
|
|
||||||
if replica.APIPath == "" {
|
|
||||||
replica.APIPath = DefaultAPIPath
|
|
||||||
}
|
|
||||||
if replica.URL != "" {
|
|
||||||
dedup[replica.Key()] = replica
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var r []AdGuardInstance
|
|
||||||
for _, replica := range dedup {
|
|
||||||
r = append(r, replica)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the current config
|
|
||||||
func (cfg *Config) Log(l *zap.SugaredLogger) {
|
|
||||||
c := cfg.mask()
|
|
||||||
l.With("config", c).Debug("Using config")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *Config) mask() *Config {
|
|
||||||
c := cfg.DeepCopy()
|
|
||||||
c.Origin.Mask()
|
|
||||||
if c.Replica != nil {
|
|
||||||
if c.Replica.URL == "" {
|
|
||||||
c.Replica = nil
|
|
||||||
} else {
|
|
||||||
c.Replica.Mask()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := range c.Replicas {
|
|
||||||
c.Replicas[i].Mask()
|
|
||||||
}
|
|
||||||
c.API.Mask()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *Config) Init() error {
|
|
||||||
if err := cfg.Origin.Init(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i := range cfg.Replicas {
|
|
||||||
replica := &cfg.Replicas[i]
|
|
||||||
if err := replica.Init(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdGuardInstance AdguardHome config instance
|
|
||||||
// +k8s:deepcopy-gen=true
|
|
||||||
type AdGuardInstance struct {
|
|
||||||
URL string `json:"url" yaml:"url" env:"URL" faker:"url"`
|
|
||||||
WebURL string `json:"webURL" yaml:"webURL" env:"WEB_URL" faker:"url"`
|
|
||||||
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" env:"API_PATH"`
|
|
||||||
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"USERNAME"`
|
|
||||||
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"PASSWORD"`
|
|
||||||
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" env:"COOKIE"`
|
|
||||||
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" env:"INSECURE_SKIP_VERIFY"`
|
|
||||||
AutoSetup bool `json:"autoSetup" yaml:"autoSetup" env:"AUTO_SETUP"`
|
|
||||||
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" env:"INTERFACE_NAME"`
|
|
||||||
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" env:"DHCP_SERVER_ENABLED"`
|
|
||||||
|
|
||||||
Host string `json:"-" yaml:"-"`
|
|
||||||
WebHost string `json:"-" yaml:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key AdGuardInstance key
|
|
||||||
func (i *AdGuardInstance) Key() string {
|
|
||||||
return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mask maks username and password
|
|
||||||
func (i *AdGuardInstance) Mask() {
|
|
||||||
i.Username = mask(i.Username)
|
|
||||||
i.Password = mask(i.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *AdGuardInstance) Init() error {
|
|
||||||
u, err := url.Parse(i.URL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.Host = u.Host
|
|
||||||
|
|
||||||
if i.WebURL == "" {
|
|
||||||
i.WebHost = i.Host
|
|
||||||
i.WebURL = i.URL
|
|
||||||
} else {
|
|
||||||
u, err := url.Parse(i.WebURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.WebHost = u.Host
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mask(s string) string {
|
|
||||||
if s == "" {
|
|
||||||
return "***"
|
|
||||||
}
|
|
||||||
mask := strings.Repeat("*", len(s)-2)
|
|
||||||
return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protection API struct
|
|
||||||
type Protection struct {
|
|
||||||
ProtectionEnabled bool `json:"protection_enabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallConfig AdguardHome install config
|
|
||||||
type InstallConfig struct {
|
|
||||||
Web InstallPort `json:"web"`
|
|
||||||
DNS InstallPort `json:"dns"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallPort AdguardHome install config port
|
|
||||||
type InstallPort struct {
|
|
||||||
IP string `json:"ip"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
CanAutofix bool `json:"can_autofix"`
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
func Clone[I interface{}](in I, out I) I {
|
|
||||||
b, _ := json.Marshal(in)
|
|
||||||
_ = json.Unmarshal(b, out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func JsonEquals(a interface{}, b interface{}) bool {
|
|
||||||
ja, _ := json.Marshal(a)
|
|
||||||
jb, _ := json.Marshal(b)
|
|
||||||
return string(ja) == string(jb)
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
"customType": "regex",
|
"customType": "regex",
|
||||||
"datasourceTemplate": "go",
|
"datasourceTemplate": "go",
|
||||||
"description": "Update toolbox tools in .toolbox.mk",
|
"description": "Update toolbox tools in .toolbox.mk",
|
||||||
"fileMatch": [
|
"managerFilePatterns": [
|
||||||
"^\\.toolbox\\.mk$"
|
".toolbox.mk"
|
||||||
],
|
],
|
||||||
"matchStrings": [
|
"matchStrings": [
|
||||||
"# renovate: packageName=(?<packageName>.+?)\\s+.+?_VERSION \\?= (?<currentValue>.+?)\\s"
|
"# renovate: packageName=(?<packageName>.+?)\\s+.+?_VERSION \\?= (?<currentValue>.+?)\\s"
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
"customType": "regex",
|
"customType": "regex",
|
||||||
"datasourceTemplate": "github-releases",
|
"datasourceTemplate": "github-releases",
|
||||||
"description": "Update github _VERSION Makefile",
|
"description": "Update github _VERSION Makefile",
|
||||||
"fileMatch": [
|
"managerFilePatterns": [
|
||||||
"^Makefile$"
|
"Makefile"
|
||||||
],
|
],
|
||||||
"matchStrings": [
|
"matchStrings": [
|
||||||
"# renovate: packageName=(?<packageName>.+?)\\s+.+?_VERSION \\?= (?<currentValue>.+?)\\s"
|
"# renovate: packageName=(?<packageName>.+?)\\s+.+?_VERSION \\?= (?<currentValue>.+?)\\s"
|
||||||
|
|||||||
31
testdata/config/print-config_test_expected1.md
vendored
Normal file
31
testdata/config/print-config_test_expected1.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!-- PLEASE COPY THE FOLLOWING OUTPUT AS IS INTO THE GITHUB ISSUE (Don't forget to mask your usernames, passwords, IPs and other sensitive information when using this in an issue ) -->
|
||||||
|
|
||||||
|
### Runtime
|
||||||
|
|
||||||
|
AdguardHome-Sync Version: %s
|
||||||
|
Build: %s
|
||||||
|
OperatingSystem: %s
|
||||||
|
Architecture: %s
|
||||||
|
OriginVersion: v0.0.1
|
||||||
|
ReplicaVersions:
|
||||||
|
- Replica 1: v0.0.2
|
||||||
|
|
||||||
|
### AdGuardHome sync aggregated config
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
origin:
|
||||||
|
url: https://ha.xxxx.net:3000
|
||||||
|
webURL: ""
|
||||||
|
insecureSkipVerify: false
|
||||||
|
autoSetup: false
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```ini
|
||||||
|
BAR=bar
|
||||||
|
FOO=foo
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- END OF GITHUB ISSUE CONTENT -->
|
||||||
41
testdata/config/print-config_test_expected2.md
vendored
Normal file
41
testdata/config/print-config_test_expected2.md
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!-- PLEASE COPY THE FOLLOWING OUTPUT AS IS INTO THE GITHUB ISSUE (Don't forget to mask your usernames, passwords, IPs and other sensitive information when using this in an issue ) -->
|
||||||
|
|
||||||
|
### Runtime
|
||||||
|
|
||||||
|
AdguardHome-Sync Version: %s
|
||||||
|
Build: %s
|
||||||
|
OperatingSystem: %s
|
||||||
|
Architecture: %s
|
||||||
|
OriginVersion: v0.0.1
|
||||||
|
ReplicaVersions:
|
||||||
|
- Replica 1: v0.0.2
|
||||||
|
|
||||||
|
### AdGuardHome sync aggregated config
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
origin:
|
||||||
|
url: https://ha.xxxx.net:3000
|
||||||
|
webURL: ""
|
||||||
|
insecureSkipVerify: false
|
||||||
|
autoSetup: false
|
||||||
|
|
||||||
|
```
|
||||||
|
### AdGuardHome sync unmodified config file
|
||||||
|
|
||||||
|
Config file path: config.yaml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
|
||||||
|
origin:
|
||||||
|
url: https://ha.xxxx.net:3000
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```ini
|
||||||
|
BAR=bar
|
||||||
|
FOO=foo
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- END OF GITHUB ISSUE CONTENT -->
|
||||||
3
testdata/config_test_replicas.yaml
vendored
3
testdata/config_test_replicas.yaml
vendored
@@ -16,6 +16,9 @@ replicas:
|
|||||||
autoSetup: false
|
autoSetup: false
|
||||||
interfaceName: eth3
|
interfaceName: eth3
|
||||||
dhcpServerEnabled: false
|
dhcpServerEnabled: false
|
||||||
|
requestHeaders:
|
||||||
|
FOO: bar
|
||||||
|
Client-ID: xxxx
|
||||||
cron: '*/15 * * * *'
|
cron: '*/15 * * * *'
|
||||||
runOnStart: true
|
runOnStart: true
|
||||||
printConfigOnly: true
|
printConfigOnly: true
|
||||||
|
|||||||
7
testdata/e2e/resources/AdGuardHome.yaml
vendored
7
testdata/e2e/resources/AdGuardHome.yaml
vendored
@@ -153,7 +153,12 @@ filtering:
|
|||||||
blocking_mode: default
|
blocking_mode: default
|
||||||
parental_block_host: family-block.dns.adguard.com
|
parental_block_host: family-block.dns.adguard.com
|
||||||
safebrowsing_block_host: standard-block.dns.adguard.com
|
safebrowsing_block_host: standard-block.dns.adguard.com
|
||||||
rewrites: []
|
rewrites:
|
||||||
|
- domain: foo.com
|
||||||
|
answer: 1.2.3.4
|
||||||
|
- domain: bar.com
|
||||||
|
answer: 1.2.3.3
|
||||||
|
|
||||||
safebrowsing_cache_size: 1048576
|
safebrowsing_cache_size: 1048576
|
||||||
safesearch_cache_size: 1048576
|
safesearch_cache_size: 1048576
|
||||||
parental_cache_size: 1048576
|
parental_cache_size: 1048576
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user